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