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