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.Throwables; 020 import com.google.common.collect.Lists; 021 import com.intellij.openapi.Disposable; 022 import com.intellij.openapi.project.Project; 023 import com.intellij.openapi.util.Pair; 024 import com.intellij.openapi.vfs.CharsetToolkit; 025 import com.intellij.psi.PsiElement; 026 import com.intellij.psi.PsiFileFactory; 027 import com.intellij.psi.impl.PsiFileFactoryImpl; 028 import com.intellij.psi.search.ProjectScope; 029 import com.intellij.testFramework.LightVirtualFile; 030 import org.jetbrains.annotations.NotNull; 031 import org.jetbrains.annotations.Nullable; 032 import org.jetbrains.kotlin.backend.common.output.OutputFile; 033 import org.jetbrains.kotlin.builtins.KotlinBuiltIns; 034 import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport; 035 import org.jetbrains.kotlin.cli.common.messages.DiagnosticMessageReporter; 036 import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport; 037 import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; 038 import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider; 039 import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; 040 import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt; 041 import org.jetbrains.kotlin.cli.jvm.config.ModuleNameKt; 042 import org.jetbrains.kotlin.cli.jvm.repl.di.ContainerForReplWithJava; 043 import org.jetbrains.kotlin.cli.jvm.repl.di.InjectionKt; 044 import org.jetbrains.kotlin.cli.jvm.repl.di.ReplLastLineScopeProvider; 045 import org.jetbrains.kotlin.cli.jvm.repl.messages.DiagnosticMessageHolder; 046 import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplIdeDiagnosticMessageHolder; 047 import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplSystemInWrapper; 048 import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplTerminalDiagnosticMessageHolder; 049 import org.jetbrains.kotlin.codegen.ClassBuilderFactories; 050 import org.jetbrains.kotlin.codegen.CompilationErrorHandler; 051 import org.jetbrains.kotlin.codegen.KotlinCodegenFacade; 052 import org.jetbrains.kotlin.codegen.state.GenerationState; 053 import org.jetbrains.kotlin.config.CompilerConfiguration; 054 import org.jetbrains.kotlin.context.MutableModuleContext; 055 import org.jetbrains.kotlin.descriptors.ScriptDescriptor; 056 import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider; 057 import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl; 058 import org.jetbrains.kotlin.idea.KotlinLanguage; 059 import org.jetbrains.kotlin.name.FqName; 060 import org.jetbrains.kotlin.parsing.KotlinParserDefinition; 061 import org.jetbrains.kotlin.psi.KtFile; 062 import org.jetbrains.kotlin.psi.KtScript; 063 import org.jetbrains.kotlin.resolve.*; 064 import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo; 065 import org.jetbrains.kotlin.resolve.jvm.JvmClassName; 066 import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM; 067 import org.jetbrains.kotlin.resolve.lazy.ResolveSession; 068 import org.jetbrains.kotlin.resolve.lazy.data.JetClassLikeInfo; 069 import org.jetbrains.kotlin.resolve.lazy.declarations.*; 070 import org.jetbrains.kotlin.resolve.scopes.LexicalScope; 071 import org.jetbrains.kotlin.types.KotlinType; 072 import org.jetbrains.kotlin.utils.ExceptionUtilsKt; 073 import org.jetbrains.org.objectweb.asm.Type; 074 075 import java.io.File; 076 import java.io.PrintWriter; 077 import java.lang.reflect.Constructor; 078 import java.lang.reflect.Field; 079 import java.net.MalformedURLException; 080 import java.net.URL; 081 import java.net.URLClassLoader; 082 import java.util.ArrayList; 083 import java.util.Arrays; 084 import java.util.Collections; 085 import java.util.List; 086 087 import static org.jetbrains.kotlin.codegen.AsmUtil.asmTypeByFqNameWithoutInnerClasses; 088 import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.registerClassNameForScript; 089 import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration; 090 091 public class ReplInterpreter { 092 private int lineNumber = 0; 093 094 @Nullable 095 private LexicalScope lastLineScope; 096 private final List<EarlierLine> earlierLines = Lists.newArrayList(); 097 private final List<String> previousIncompleteLines = Lists.newArrayList(); 098 private final ReplClassLoader classLoader; 099 100 private final PsiFileFactoryImpl psiFileFactory; 101 private final BindingTraceContext trace; 102 private final ModuleDescriptorImpl module; 103 104 private final TopDownAnalysisContext topDownAnalysisContext; 105 private final LazyTopDownAnalyzerForTopLevel topDownAnalyzer; 106 private final ResolveSession resolveSession; 107 private final ScriptMutableDeclarationProviderFactory scriptDeclarationFactory; 108 109 private final boolean ideMode; 110 private final ReplSystemInWrapper replReader; 111 112 public ReplInterpreter( 113 @NotNull Disposable disposable, 114 @NotNull CompilerConfiguration configuration, 115 boolean ideMode, 116 @Nullable ReplSystemInWrapper replReader 117 ) { 118 KotlinCoreEnvironment environment = 119 KotlinCoreEnvironment.createForProduction(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES); 120 Project project = environment.getProject(); 121 this.psiFileFactory = (PsiFileFactoryImpl) PsiFileFactory.getInstance(project); 122 this.trace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(); 123 MutableModuleContext moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(project, ModuleNameKt 124 .getModuleName(environment)); 125 this.module = moduleContext.getModule(); 126 127 scriptDeclarationFactory = new ScriptMutableDeclarationProviderFactory(); 128 129 ContainerForReplWithJava container = InjectionKt.createContainerForReplWithJava( 130 moduleContext, 131 trace, 132 scriptDeclarationFactory, 133 ProjectScope.getAllScope(project), 134 new ReplLastLineScopeProvider() { 135 @Nullable 136 @Override 137 public LexicalScope getLastLineScope() { 138 return lastLineScope; 139 } 140 }, 141 new JvmPackagePartProvider(environment) 142 ); 143 144 this.topDownAnalysisContext = new TopDownAnalysisContext(TopDownAnalysisMode.LocalDeclarations, DataFlowInfo.EMPTY, 145 container.getResolveSession().getDeclarationScopeProvider()); 146 this.topDownAnalyzer = container.getLazyTopDownAnalyzerForTopLevel(); 147 this.resolveSession = container.getResolveSession(); 148 149 moduleContext.initializeModuleContents(new CompositePackageFragmentProvider( 150 Arrays.asList( 151 container.getResolveSession().getPackageFragmentProvider(), 152 container.getJavaDescriptorResolver().getPackageFragmentProvider() 153 ) 154 )); 155 156 List<URL> classpath = Lists.newArrayList(); 157 for (File file : JvmContentRootsKt.getJvmClasspathRoots(configuration)) { 158 try { 159 classpath.add(file.toURI().toURL()); 160 } 161 catch (MalformedURLException e) { 162 throw ExceptionUtilsKt.rethrow(e); 163 } 164 } 165 166 this.classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[classpath.size()]), null)); 167 168 this.ideMode = ideMode; 169 this.replReader = replReader; 170 } 171 172 private static void prepareForTheNextReplLine(@NotNull TopDownAnalysisContext c) { 173 c.getScripts().clear(); 174 } 175 176 public enum LineResultType { 177 SUCCESS, 178 COMPILE_ERROR, 179 RUNTIME_ERROR, 180 INCOMPLETE, 181 } 182 183 public static class LineResult { 184 private final Object value; 185 private final boolean unit; 186 private final String errorText; 187 private final LineResultType type; 188 189 private LineResult(Object value, boolean unit, String errorText, @NotNull LineResultType type) { 190 this.value = value; 191 this.unit = unit; 192 this.errorText = errorText; 193 this.type = type; 194 } 195 196 @NotNull 197 public LineResultType getType() { 198 return type; 199 } 200 201 private void checkSuccessful() { 202 if (getType() != LineResultType.SUCCESS) { 203 throw new IllegalStateException("it is error"); 204 } 205 } 206 207 public Object getValue() { 208 checkSuccessful(); 209 return value; 210 } 211 212 public boolean isUnit() { 213 checkSuccessful(); 214 return unit; 215 } 216 217 @NotNull 218 public String getErrorText() { 219 return errorText; 220 } 221 222 @NotNull 223 private static LineResult error(@NotNull String errorText, @NotNull LineResultType errorType) { 224 if (errorText.isEmpty()) { 225 errorText = "<unknown error>"; 226 } 227 else if (!errorText.endsWith("\n")) { 228 errorText += "\n"; 229 } 230 231 return new LineResult(null, false, errorText, errorType); 232 } 233 234 @NotNull 235 public static LineResult successful(Object value, boolean unit) { 236 return new LineResult(value, unit, null, LineResultType.SUCCESS); 237 } 238 239 @NotNull 240 public static LineResult compileError(@NotNull String errorText) { 241 return error(errorText, LineResultType.COMPILE_ERROR); 242 } 243 244 @NotNull 245 public static LineResult runtimeError(@NotNull String errorText) { 246 return error(errorText, LineResultType.RUNTIME_ERROR); 247 } 248 249 public static LineResult incomplete() { 250 return new LineResult(null, false, null, LineResultType.INCOMPLETE); 251 } 252 } 253 254 @NotNull 255 private DiagnosticMessageHolder createDiagnosticHolder() { 256 return ideMode ? new ReplIdeDiagnosticMessageHolder() 257 : new ReplTerminalDiagnosticMessageHolder(); 258 } 259 260 @NotNull 261 public LineResult eval(@NotNull String line) { 262 ++lineNumber; 263 264 FqName scriptFqName = new FqName("Line" + lineNumber); 265 Type scriptClassType = asmTypeByFqNameWithoutInnerClasses(scriptFqName); 266 267 StringBuilder fullText = new StringBuilder(); 268 for (String prevLine : previousIncompleteLines) { 269 fullText.append(prevLine).append("\n"); 270 } 271 fullText.append(line); 272 273 LightVirtualFile virtualFile = new LightVirtualFile("line" + lineNumber + KotlinParserDefinition.STD_SCRIPT_EXT, KotlinLanguage.INSTANCE, fullText.toString()); 274 virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET); 275 KtFile psiFile = (KtFile) psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false); 276 assert psiFile != null : "Script file not analyzed at line " + lineNumber + ": " + fullText; 277 278 DiagnosticMessageHolder errorHolder = createDiagnosticHolder(); 279 280 AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorHolder); 281 282 if (syntaxErrorReport.isHasErrors() && syntaxErrorReport.isAllErrorsAtEof()) { 283 if (ideMode) { 284 return LineResult.compileError(errorHolder.getRenderedDiagnostics()); 285 } 286 else { 287 previousIncompleteLines.add(line); 288 return LineResult.incomplete(); 289 } 290 } 291 292 previousIncompleteLines.clear(); 293 294 if (syntaxErrorReport.isHasErrors()) { 295 return LineResult.compileError(errorHolder.getRenderedDiagnostics()); 296 } 297 298 prepareForTheNextReplLine(topDownAnalysisContext); 299 trace.clearDiagnostics(); 300 301 //noinspection ConstantConditions 302 psiFile.getScript().putUserData(ScriptPriorities.PRIORITY_KEY, lineNumber); 303 304 ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorHolder); 305 if (scriptDescriptor == null) { 306 return LineResult.compileError(errorHolder.getRenderedDiagnostics()); 307 } 308 309 List<Pair<ScriptDescriptor, Type>> earlierScripts = Lists.newArrayList(); 310 311 for (EarlierLine earlierLine : earlierLines) { 312 earlierScripts.add(Pair.create(earlierLine.getScriptDescriptor(), earlierLine.getClassType())); 313 } 314 315 GenerationState state = new GenerationState(psiFile.getProject(), ClassBuilderFactories.BINARIES, 316 module, trace.getBindingContext(), Collections.singletonList(psiFile)); 317 318 compileScript(psiFile.getScript(), scriptClassType, earlierScripts, state, CompilationErrorHandler.THROW_EXCEPTION); 319 320 for (OutputFile outputFile : state.getFactory().asList()) { 321 if(outputFile.getRelativePath().endsWith(".class")) { 322 classLoader.addClass(JvmClassName.byInternalName(outputFile.getRelativePath().replaceFirst("\\.class$", "")), 323 outputFile.asByteArray()); 324 } 325 } 326 327 try { 328 Class<?> scriptClass = classLoader.loadClass(scriptFqName.asString()); 329 330 Class<?>[] constructorParams = new Class<?>[earlierLines.size()]; 331 Object[] constructorArgs = new Object[earlierLines.size()]; 332 333 for (int i = 0; i < earlierLines.size(); ++i) { 334 constructorParams[i] = earlierLines.get(i).getScriptClass(); 335 constructorArgs[i] = earlierLines.get(i).getScriptInstance(); 336 } 337 338 Constructor<?> scriptInstanceConstructor = scriptClass.getConstructor(constructorParams); 339 Object scriptInstance; 340 try { 341 setReplScriptExecuting(true); 342 scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs); 343 } 344 catch (Throwable e) { 345 return LineResult.runtimeError(renderStackTrace(e.getCause())); 346 } finally { 347 setReplScriptExecuting(false); 348 } 349 350 Field rvField = scriptClass.getDeclaredField("rv"); 351 rvField.setAccessible(true); 352 Object rv = rvField.get(scriptInstance); 353 354 earlierLines.add(new EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance, scriptClassType)); 355 356 KotlinType returnType = scriptDescriptor.getScriptCodeDescriptor().getReturnType(); 357 return LineResult.successful(rv, returnType != null && KotlinBuiltIns.isUnit(returnType)); 358 } 359 catch (Throwable e) { 360 @SuppressWarnings("UseOfSystemOutOrSystemErr") 361 PrintWriter writer = new PrintWriter(System.err); 362 classLoader.dumpClasses(writer); 363 writer.flush(); 364 throw ExceptionUtilsKt.rethrow(e); 365 } 366 } 367 368 private void setReplScriptExecuting(boolean isExecuting) { 369 if (replReader != null) { 370 replReader.setReplScriptExecuting(isExecuting); 371 } 372 } 373 374 @NotNull 375 private static String renderStackTrace(@NotNull Throwable cause) { 376 StackTraceElement[] oldTrace = cause.getStackTrace(); 377 List<StackTraceElement> newTrace = new ArrayList<StackTraceElement>(); 378 boolean skip = true; 379 for (int i = oldTrace.length - 1; i >= 0; i--) { 380 StackTraceElement element = oldTrace[i]; 381 // All our code happens in the script constructor, and no reflection/native code happens in constructors. 382 // So we ignore everything in the stack trace until the first constructor 383 if (element.getMethodName().equals("<init>")) { 384 skip = false; 385 } 386 if (!skip) { 387 newTrace.add(element); 388 } 389 } 390 Collections.reverse(newTrace); 391 392 // throw away last element which contains Line1.kts<init>(Unknown source) 393 List<StackTraceElement> resultingTrace = newTrace.subList(0, newTrace.size() - 1); 394 395 cause.setStackTrace(resultingTrace.toArray(new StackTraceElement[resultingTrace.size()])); 396 return Throwables.getStackTraceAsString(cause); 397 } 398 399 @Nullable 400 private ScriptDescriptor doAnalyze(@NotNull KtFile psiFile, @NotNull DiagnosticMessageReporter errorReporter) { 401 scriptDeclarationFactory.setDelegateFactory( 402 new FileBasedDeclarationProviderFactory(resolveSession.getStorageManager(), Collections.singletonList(psiFile))); 403 404 TopDownAnalysisContext context = topDownAnalyzer.analyzeDeclarations( 405 topDownAnalysisContext.getTopDownAnalysisMode(), 406 Collections.singletonList(psiFile) 407 ); 408 409 if (trace.get(BindingContext.FILE_TO_PACKAGE_FRAGMENT, psiFile) == null) { 410 trace.record(BindingContext.FILE_TO_PACKAGE_FRAGMENT, psiFile, resolveSession.getPackageFragment(FqName.ROOT)); 411 } 412 413 boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics(trace.getBindingContext().getDiagnostics(), errorReporter, false); 414 if (hasErrors) { 415 return null; 416 } 417 418 ScriptDescriptor scriptDescriptor = context.getScripts().get(psiFile.getScript()); 419 lastLineScope = trace.get(BindingContext.SCRIPT_SCOPE, scriptDescriptor); 420 if (lastLineScope == null) { 421 throw new IllegalStateException("last line scope is not initialized"); 422 } 423 424 return scriptDescriptor; 425 } 426 427 public void dumpClasses(@NotNull PrintWriter out) { 428 classLoader.dumpClasses(out); 429 } 430 431 private static void registerEarlierScripts( 432 @NotNull GenerationState state, 433 @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts 434 ) { 435 List<ScriptDescriptor> earlierScriptDescriptors = new ArrayList<ScriptDescriptor>(earlierScripts.size()); 436 for (Pair<ScriptDescriptor, Type> pair : earlierScripts) { 437 ScriptDescriptor earlierDescriptor = pair.first; 438 Type earlierClassType = pair.second; 439 440 PsiElement jetScript = descriptorToDeclaration(earlierDescriptor); 441 if (jetScript != null) { 442 registerClassNameForScript(state.getBindingTrace(), (KtScript) jetScript, earlierClassType, state.getFileClassesProvider()); 443 earlierScriptDescriptors.add(earlierDescriptor); 444 } 445 } 446 state.setEarlierScriptsForReplInterpreter(earlierScriptDescriptors); 447 } 448 449 public static void compileScript( 450 @NotNull KtScript script, 451 @NotNull Type classType, 452 @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts, 453 @NotNull GenerationState state, 454 @NotNull CompilationErrorHandler errorHandler 455 ) { 456 registerEarlierScripts(state, earlierScripts); 457 registerClassNameForScript(state.getBindingTrace(), script, classType, state.getFileClassesProvider()); 458 459 state.beforeCompile(); 460 KotlinCodegenFacade.generatePackage( 461 state, 462 script.getContainingKtFile().getPackageFqName(), 463 Collections.singleton(script.getContainingKtFile()), 464 errorHandler 465 ); 466 } 467 468 private static class ScriptMutableDeclarationProviderFactory implements DeclarationProviderFactory { 469 private DeclarationProviderFactory delegateFactory; 470 private AdaptablePackageMemberDeclarationProvider rootPackageProvider; 471 472 public void setDelegateFactory(DeclarationProviderFactory delegateFactory) { 473 this.delegateFactory = delegateFactory; 474 475 PackageMemberDeclarationProvider provider = delegateFactory.getPackageMemberDeclarationProvider(FqName.ROOT); 476 if (rootPackageProvider == null) { 477 assert provider != null; 478 rootPackageProvider = new AdaptablePackageMemberDeclarationProvider(provider); 479 } 480 else { 481 rootPackageProvider.addDelegateProvider(provider); 482 } 483 } 484 485 @NotNull 486 @Override 487 public ClassMemberDeclarationProvider getClassMemberDeclarationProvider(@NotNull JetClassLikeInfo classLikeInfo) { 488 return delegateFactory.getClassMemberDeclarationProvider(classLikeInfo); 489 } 490 491 @Nullable 492 @Override 493 public PackageMemberDeclarationProvider getPackageMemberDeclarationProvider(@NotNull FqName packageFqName) { 494 if (packageFqName.isRoot()) { 495 return rootPackageProvider; 496 } 497 498 return this.delegateFactory.getPackageMemberDeclarationProvider(packageFqName); 499 } 500 501 public static class AdaptablePackageMemberDeclarationProvider extends DelegatePackageMemberDeclarationProvider { 502 @NotNull 503 private PackageMemberDeclarationProvider delegateProvider; 504 505 public AdaptablePackageMemberDeclarationProvider(@NotNull PackageMemberDeclarationProvider delegateProvider) { 506 super(delegateProvider); 507 this.delegateProvider = delegateProvider; 508 } 509 510 public void addDelegateProvider(PackageMemberDeclarationProvider provider) { 511 delegateProvider = new CombinedPackageMemberDeclarationProvider(Lists.newArrayList(provider, delegateProvider)); 512 513 setDelegate(delegateProvider); 514 } 515 } 516 } 517 }