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