001 /* 002 * Copyright 2010-2015 JetBrains s.r.o. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.jetbrains.kotlin.cli.jvm.repl; 018 019 import com.google.common.base.Predicates; 020 import com.google.common.base.Throwables; 021 import com.google.common.collect.Lists; 022 import com.intellij.openapi.Disposable; 023 import com.intellij.openapi.project.Project; 024 import com.intellij.openapi.util.Pair; 025 import com.intellij.openapi.vfs.CharsetToolkit; 026 import com.intellij.psi.PsiElement; 027 import com.intellij.psi.PsiFile; 028 import com.intellij.psi.PsiFileFactory; 029 import com.intellij.psi.impl.PsiFileFactoryImpl; 030 import com.intellij.psi.search.ProjectScope; 031 import com.intellij.testFramework.LightVirtualFile; 032 import com.intellij.util.SmartList; 033 import org.jetbrains.annotations.NotNull; 034 import org.jetbrains.annotations.Nullable; 035 import org.jetbrains.kotlin.backend.common.output.OutputFile; 036 import org.jetbrains.kotlin.builtins.KotlinBuiltIns; 037 import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport; 038 import org.jetbrains.kotlin.cli.common.messages.MessageCollector; 039 import org.jetbrains.kotlin.cli.common.messages.MessageCollectorToString; 040 import org.jetbrains.kotlin.cli.jvm.JVMConfigurationKeys; 041 import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport; 042 import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; 043 import org.jetbrains.kotlin.cli.jvm.compiler.JetCoreEnvironment; 044 import org.jetbrains.kotlin.codegen.ClassBuilderFactories; 045 import org.jetbrains.kotlin.codegen.CompilationErrorHandler; 046 import org.jetbrains.kotlin.codegen.KotlinCodegenFacade; 047 import org.jetbrains.kotlin.codegen.state.GenerationState; 048 import org.jetbrains.kotlin.config.CompilerConfiguration; 049 import org.jetbrains.kotlin.context.ContextPackage; 050 import org.jetbrains.kotlin.context.GlobalContextImpl; 051 import org.jetbrains.kotlin.descriptors.ScriptDescriptor; 052 import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider; 053 import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl; 054 import org.jetbrains.kotlin.di.InjectorForReplWithJava; 055 import org.jetbrains.kotlin.idea.JetLanguage; 056 import org.jetbrains.kotlin.name.FqName; 057 import org.jetbrains.kotlin.parsing.JetParserDefinition; 058 import org.jetbrains.kotlin.psi.JetFile; 059 import org.jetbrains.kotlin.psi.JetScript; 060 import org.jetbrains.kotlin.resolve.*; 061 import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo; 062 import org.jetbrains.kotlin.resolve.jvm.JvmClassName; 063 import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM; 064 import org.jetbrains.kotlin.resolve.lazy.ResolveSession; 065 import org.jetbrains.kotlin.resolve.lazy.ScopeProvider; 066 import org.jetbrains.kotlin.resolve.lazy.data.JetClassLikeInfo; 067 import org.jetbrains.kotlin.resolve.lazy.declarations.*; 068 import org.jetbrains.kotlin.resolve.scopes.JetScope; 069 import org.jetbrains.kotlin.types.JetType; 070 import org.jetbrains.kotlin.utils.UtilsPackage; 071 import org.jetbrains.org.objectweb.asm.Type; 072 073 import java.io.File; 074 import java.io.PrintWriter; 075 import java.lang.reflect.Constructor; 076 import java.lang.reflect.Field; 077 import java.net.MalformedURLException; 078 import java.net.URL; 079 import java.net.URLClassLoader; 080 import java.util.ArrayList; 081 import java.util.Arrays; 082 import java.util.Collections; 083 import java.util.List; 084 085 import static org.jetbrains.kotlin.codegen.AsmUtil.asmTypeByFqNameWithoutInnerClasses; 086 import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.registerClassNameForScript; 087 import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration; 088 089 public class ReplInterpreter { 090 private int lineNumber = 0; 091 092 @Nullable 093 private JetScope lastLineScope; 094 private final List<EarlierLine> earlierLines = Lists.newArrayList(); 095 private final List<String> previousIncompleteLines = Lists.newArrayList(); 096 private final ReplClassLoader classLoader; 097 098 private final PsiFileFactoryImpl psiFileFactory; 099 private final BindingTraceContext trace; 100 private final ModuleDescriptorImpl module; 101 102 private final TopDownAnalysisContext topDownAnalysisContext; 103 private final LazyTopDownAnalyzerForTopLevel topDownAnalyzer; 104 private final ResolveSession resolveSession; 105 private final ScriptMutableDeclarationProviderFactory scriptDeclarationFactory; 106 107 public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) { 108 JetCoreEnvironment environment = 109 JetCoreEnvironment.createForProduction(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES); 110 Project project = environment.getProject(); 111 this.psiFileFactory = (PsiFileFactoryImpl) PsiFileFactory.getInstance(project); 112 this.trace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(); 113 this.module = TopDownAnalyzerFacadeForJVM.createJavaModule("<repl>"); 114 115 GlobalContextImpl context = ContextPackage.GlobalContext(); 116 117 TopDownAnalysisParameters topDownAnalysisParameters = TopDownAnalysisParameters.create( 118 context.getStorageManager(), 119 context.getExceptionTracker(), 120 Predicates.<PsiFile>alwaysTrue(), 121 false, 122 true 123 ); 124 125 scriptDeclarationFactory = new ScriptMutableDeclarationProviderFactory(); 126 127 ScopeProvider.AdditionalFileScopeProvider scopeProvider = new ScopeProvider.AdditionalFileScopeProvider() { 128 @NotNull 129 @Override 130 public List<JetScope> scopes(@NotNull JetFile file) { 131 return lastLineScope != null ? new SmartList<JetScope>(lastLineScope) : Collections.<JetScope>emptyList(); 132 } 133 }; 134 135 InjectorForReplWithJava injector = new InjectorForReplWithJava( 136 project, 137 topDownAnalysisParameters, 138 trace, 139 module, 140 scriptDeclarationFactory, 141 ProjectScope.getAllScope(project), 142 scopeProvider 143 ); 144 145 this.topDownAnalysisContext = new TopDownAnalysisContext(topDownAnalysisParameters, DataFlowInfo.EMPTY); 146 this.topDownAnalyzer = injector.getLazyTopDownAnalyzerForTopLevel(); 147 this.resolveSession = injector.getResolveSession(); 148 149 module.initialize(new CompositePackageFragmentProvider( 150 Arrays.asList( 151 injector.getResolveSession().getPackageFragmentProvider(), 152 injector.getJavaDescriptorResolver().getPackageFragmentProvider() 153 ) 154 )); 155 module.addDependencyOnModule(module); 156 module.addDependencyOnModule(KotlinBuiltIns.getInstance().getBuiltInsModule()); 157 module.seal(); 158 159 List<URL> classpath = Lists.newArrayList(); 160 for (File file : configuration.getList(JVMConfigurationKeys.CLASSPATH_KEY)) { 161 try { 162 classpath.add(file.toURI().toURL()); 163 } 164 catch (MalformedURLException e) { 165 throw UtilsPackage.rethrow(e); 166 } 167 } 168 169 this.classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[classpath.size()]))); 170 } 171 172 private static void prepareForTheNextReplLine(@NotNull TopDownAnalysisContext c) { 173 c.getScripts().clear(); 174 } 175 176 public enum LineResultType { 177 SUCCESS, 178 ERROR, 179 INCOMPLETE, 180 } 181 182 public static class LineResult { 183 private final Object value; 184 private final boolean unit; 185 private final String errorText; 186 private final LineResultType type; 187 188 private LineResult(Object value, boolean unit, String errorText, @NotNull LineResultType type) { 189 this.value = value; 190 this.unit = unit; 191 this.errorText = errorText; 192 this.type = type; 193 } 194 195 @NotNull 196 public LineResultType getType() { 197 return type; 198 } 199 200 private void checkSuccessful() { 201 if (getType() != LineResultType.SUCCESS) { 202 throw new IllegalStateException("it is error"); 203 } 204 } 205 206 public Object getValue() { 207 checkSuccessful(); 208 return value; 209 } 210 211 public boolean isUnit() { 212 checkSuccessful(); 213 return unit; 214 } 215 216 @NotNull 217 public String getErrorText() { 218 return errorText; 219 } 220 221 public static LineResult successful(Object value, boolean unit) { 222 return new LineResult(value, unit, null, LineResultType.SUCCESS); 223 } 224 225 public static LineResult error(@NotNull String errorText) { 226 if (errorText.isEmpty()) { 227 errorText = "<unknown error>"; 228 } 229 else if (!errorText.endsWith("\n")) { 230 errorText += "\n"; 231 } 232 return new LineResult(null, false, errorText, LineResultType.ERROR); 233 } 234 235 public static LineResult incomplete() { 236 return new LineResult(null, false, null, LineResultType.INCOMPLETE); 237 } 238 } 239 240 @NotNull 241 public LineResult eval(@NotNull String line) { 242 ++lineNumber; 243 244 FqName scriptFqName = new FqName("Line" + lineNumber); 245 Type scriptClassType = asmTypeByFqNameWithoutInnerClasses(scriptFqName); 246 247 StringBuilder fullText = new StringBuilder(); 248 for (String prevLine : previousIncompleteLines) { 249 fullText.append(prevLine).append("\n"); 250 } 251 fullText.append(line); 252 253 LightVirtualFile virtualFile = new LightVirtualFile("line" + lineNumber + JetParserDefinition.STD_SCRIPT_EXT, JetLanguage.INSTANCE, fullText.toString()); 254 virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET); 255 JetFile psiFile = (JetFile) psiFileFactory.trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false); 256 assert psiFile != null : "Script file not analyzed at line " + lineNumber + ": " + fullText; 257 258 MessageCollectorToString errorCollector = new MessageCollectorToString(); 259 260 AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport = 261 AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector); 262 263 if (syntaxErrorReport.isHasErrors() && syntaxErrorReport.isAllErrorsAtEof()) { 264 previousIncompleteLines.add(line); 265 return LineResult.incomplete(); 266 } 267 268 previousIncompleteLines.clear(); 269 270 if (syntaxErrorReport.isHasErrors()) { 271 return LineResult.error(errorCollector.getString()); 272 } 273 274 prepareForTheNextReplLine(topDownAnalysisContext); 275 trace.clearDiagnostics(); 276 277 //noinspection ConstantConditions 278 psiFile.getScript().putUserData(ScriptHeaderResolver.PRIORITY_KEY, lineNumber); 279 280 ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorCollector); 281 if (scriptDescriptor == null) { 282 return LineResult.error(errorCollector.getString()); 283 } 284 285 List<Pair<ScriptDescriptor, Type>> earlierScripts = Lists.newArrayList(); 286 287 for (EarlierLine earlierLine : earlierLines) { 288 earlierScripts.add(Pair.create(earlierLine.getScriptDescriptor(), earlierLine.getClassType())); 289 } 290 291 GenerationState state = new GenerationState(psiFile.getProject(), ClassBuilderFactories.BINARIES, 292 module, trace.getBindingContext(), Collections.singletonList(psiFile)); 293 294 compileScript(psiFile.getScript(), scriptClassType, earlierScripts, state, CompilationErrorHandler.THROW_EXCEPTION); 295 296 for (OutputFile outputFile : state.getFactory().asList()) { 297 classLoader.addClass(JvmClassName.byInternalName(outputFile.getRelativePath().replaceFirst("\\.class$", "")), outputFile.asByteArray()); 298 } 299 300 try { 301 Class<?> scriptClass = classLoader.loadClass(scriptFqName.asString()); 302 303 Class<?>[] constructorParams = new Class<?>[earlierLines.size()]; 304 Object[] constructorArgs = new Object[earlierLines.size()]; 305 306 for (int i = 0; i < earlierLines.size(); ++i) { 307 constructorParams[i] = earlierLines.get(i).getScriptClass(); 308 constructorArgs[i] = earlierLines.get(i).getScriptInstance(); 309 } 310 311 Constructor<?> scriptInstanceConstructor = scriptClass.getConstructor(constructorParams); 312 Object scriptInstance; 313 try { 314 scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs); 315 } 316 catch (Throwable e) { 317 return LineResult.error(renderStackTrace(e.getCause())); 318 } 319 Field rvField = scriptClass.getDeclaredField("rv"); 320 rvField.setAccessible(true); 321 Object rv = rvField.get(scriptInstance); 322 323 earlierLines.add(new EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance, scriptClassType)); 324 325 JetType returnType = scriptDescriptor.getScriptCodeDescriptor().getReturnType(); 326 return LineResult.successful(rv, returnType != null && KotlinBuiltIns.isUnit(returnType)); 327 } 328 catch (Throwable e) { 329 @SuppressWarnings("UseOfSystemOutOrSystemErr") 330 PrintWriter writer = new PrintWriter(System.err); 331 classLoader.dumpClasses(writer); 332 writer.flush(); 333 throw UtilsPackage.rethrow(e); 334 } 335 } 336 337 @NotNull 338 private static String renderStackTrace(@NotNull Throwable cause) { 339 StackTraceElement[] oldTrace = cause.getStackTrace(); 340 List<StackTraceElement> newTrace = new ArrayList<StackTraceElement>(); 341 boolean skip = true; 342 for (int i = oldTrace.length - 1; i >= 0; i--) { 343 StackTraceElement element = oldTrace[i]; 344 // All our code happens in the script constructor, and no reflection/native code happens in constructors. 345 // So we ignore everything in the stack trace until the first constructor 346 if (element.getMethodName().equals("<init>")) { 347 skip = false; 348 } 349 if (!skip) { 350 newTrace.add(element); 351 } 352 } 353 Collections.reverse(newTrace); 354 cause.setStackTrace(newTrace.toArray(new StackTraceElement[newTrace.size()])); 355 return Throwables.getStackTraceAsString(cause); 356 } 357 358 @Nullable 359 private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull MessageCollector messageCollector) { 360 scriptDeclarationFactory.setDelegateFactory( 361 new FileBasedDeclarationProviderFactory(topDownAnalysisContext.getStorageManager(), Collections.singletonList(psiFile))); 362 363 TopDownAnalysisContext context = topDownAnalyzer.analyzeDeclarations( 364 topDownAnalysisContext.getTopDownAnalysisParameters(), 365 Collections.singletonList(psiFile) 366 ); 367 368 if (trace.get(BindingContext.FILE_TO_PACKAGE_FRAGMENT, psiFile) == null) { 369 trace.record(BindingContext.FILE_TO_PACKAGE_FRAGMENT, psiFile, resolveSession.getPackageFragment(FqName.ROOT)); 370 } 371 372 boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics(trace.getBindingContext().getDiagnostics(), messageCollector); 373 if (hasErrors) { 374 return null; 375 } 376 377 ScriptDescriptor scriptDescriptor = context.getScripts().get(psiFile.getScript()); 378 lastLineScope = trace.get(BindingContext.SCRIPT_SCOPE, scriptDescriptor); 379 if (lastLineScope == null) { 380 throw new IllegalStateException("last line scope is not initialized"); 381 } 382 383 return scriptDescriptor; 384 } 385 386 public void dumpClasses(@NotNull PrintWriter out) { 387 classLoader.dumpClasses(out); 388 } 389 390 private static void registerEarlierScripts( 391 @NotNull GenerationState state, 392 @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts 393 ) { 394 List<ScriptDescriptor> earlierScriptDescriptors = new ArrayList<ScriptDescriptor>(earlierScripts.size()); 395 for (Pair<ScriptDescriptor, Type> pair : earlierScripts) { 396 ScriptDescriptor earlierDescriptor = pair.first; 397 Type earlierClassType = pair.second; 398 399 PsiElement jetScript = descriptorToDeclaration(earlierDescriptor); 400 if (jetScript != null) { 401 registerClassNameForScript(state.getBindingTrace(), (JetScript) jetScript, earlierClassType); 402 earlierScriptDescriptors.add(earlierDescriptor); 403 } 404 } 405 state.setEarlierScriptsForReplInterpreter(earlierScriptDescriptors); 406 } 407 408 public static void compileScript( 409 @NotNull JetScript script, 410 @NotNull Type classType, 411 @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts, 412 @NotNull GenerationState state, 413 @NotNull CompilationErrorHandler errorHandler 414 ) { 415 registerEarlierScripts(state, earlierScripts); 416 registerClassNameForScript(state.getBindingTrace(), script, classType); 417 418 state.beforeCompile(); 419 KotlinCodegenFacade.generatePackage( 420 state, 421 script.getContainingJetFile().getPackageFqName(), 422 Collections.singleton(script.getContainingJetFile()), 423 errorHandler 424 ); 425 } 426 427 private static class ScriptMutableDeclarationProviderFactory implements DeclarationProviderFactory { 428 private DeclarationProviderFactory delegateFactory; 429 private AdaptablePackageMemberDeclarationProvider rootPackageProvider; 430 431 public void setDelegateFactory(DeclarationProviderFactory delegateFactory) { 432 this.delegateFactory = delegateFactory; 433 434 PackageMemberDeclarationProvider provider = delegateFactory.getPackageMemberDeclarationProvider(FqName.ROOT); 435 if (rootPackageProvider == null) { 436 assert provider != null; 437 rootPackageProvider = new AdaptablePackageMemberDeclarationProvider(provider); 438 } 439 else { 440 rootPackageProvider.addDelegateProvider(provider); 441 } 442 } 443 444 @NotNull 445 @Override 446 public ClassMemberDeclarationProvider getClassMemberDeclarationProvider(@NotNull JetClassLikeInfo classLikeInfo) { 447 return delegateFactory.getClassMemberDeclarationProvider(classLikeInfo); 448 } 449 450 @Nullable 451 @Override 452 public PackageMemberDeclarationProvider getPackageMemberDeclarationProvider(@NotNull FqName packageFqName) { 453 if (packageFqName.isRoot()) { 454 return rootPackageProvider; 455 } 456 457 return this.delegateFactory.getPackageMemberDeclarationProvider(packageFqName); 458 } 459 460 public static class AdaptablePackageMemberDeclarationProvider extends DelegatePackageMemberDeclarationProvider { 461 @NotNull 462 private PackageMemberDeclarationProvider delegateProvider; 463 464 public AdaptablePackageMemberDeclarationProvider(@NotNull PackageMemberDeclarationProvider delegateProvider) { 465 super(delegateProvider); 466 this.delegateProvider = delegateProvider; 467 } 468 469 public void addDelegateProvider(PackageMemberDeclarationProvider provider) { 470 delegateProvider = new CombinedPackageMemberDeclarationProvider(Lists.newArrayList(provider, delegateProvider)); 471 472 setDelegate(delegateProvider); 473 } 474 } 475 } 476 }