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.MessageCollector; 037 import org.jetbrains.kotlin.cli.common.messages.MessageCollectorToString; 038 import org.jetbrains.kotlin.cli.jvm.JVMConfigurationKeys; 039 import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport; 040 import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; 041 import org.jetbrains.kotlin.cli.jvm.compiler.JetCoreEnvironment; 042 import org.jetbrains.kotlin.codegen.ClassBuilderFactories; 043 import org.jetbrains.kotlin.codegen.CompilationErrorHandler; 044 import org.jetbrains.kotlin.codegen.KotlinCodegenFacade; 045 import org.jetbrains.kotlin.codegen.state.GenerationState; 046 import org.jetbrains.kotlin.config.CompilerConfiguration; 047 import org.jetbrains.kotlin.context.ContextPackage; 048 import org.jetbrains.kotlin.context.GlobalContextImpl; 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.di.InjectorForReplWithJava; 053 import org.jetbrains.kotlin.idea.JetLanguage; 054 import org.jetbrains.kotlin.name.FqName; 055 import org.jetbrains.kotlin.parsing.JetParserDefinition; 056 import org.jetbrains.kotlin.psi.JetFile; 057 import org.jetbrains.kotlin.psi.JetScript; 058 import org.jetbrains.kotlin.resolve.*; 059 import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo; 060 import org.jetbrains.kotlin.resolve.jvm.JvmClassName; 061 import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM; 062 import org.jetbrains.kotlin.resolve.lazy.ResolveSession; 063 import org.jetbrains.kotlin.resolve.lazy.ScopeProvider; 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.ArrayList; 079 import java.util.Arrays; 080 import java.util.Collections; 081 import java.util.List; 082 083 import static org.jetbrains.kotlin.codegen.AsmUtil.asmTypeByFqNameWithoutInnerClasses; 084 import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.registerClassNameForScript; 085 import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration; 086 087 public class ReplInterpreter { 088 private int lineNumber = 0; 089 090 @Nullable 091 private JetScope lastLineScope; 092 private final List<EarlierLine> earlierLines = Lists.newArrayList(); 093 private final List<String> previousIncompleteLines = Lists.newArrayList(); 094 private final ReplClassLoader classLoader; 095 096 private final PsiFileFactoryImpl psiFileFactory; 097 private final BindingTraceContext trace; 098 private final ModuleDescriptorImpl module; 099 100 private final TopDownAnalysisContext topDownAnalysisContext; 101 private final LazyTopDownAnalyzerForTopLevel topDownAnalyzer; 102 private final ResolveSession resolveSession; 103 private final ScriptMutableDeclarationProviderFactory scriptDeclarationFactory; 104 105 public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) { 106 JetCoreEnvironment environment = 107 JetCoreEnvironment.createForProduction(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES); 108 Project project = environment.getProject(); 109 this.psiFileFactory = (PsiFileFactoryImpl) PsiFileFactory.getInstance(project); 110 this.trace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(); 111 this.module = TopDownAnalyzerFacadeForJVM.createJavaModule("<repl>"); 112 113 GlobalContextImpl context = ContextPackage.GlobalContext(); 114 115 TopDownAnalysisParameters topDownAnalysisParameters = TopDownAnalysisParameters.create( 116 context.getStorageManager(), 117 context.getExceptionTracker(), 118 false, 119 true 120 ); 121 122 scriptDeclarationFactory = new ScriptMutableDeclarationProviderFactory(); 123 124 ScopeProvider.AdditionalFileScopeProvider scopeProvider = new ScopeProvider.AdditionalFileScopeProvider() { 125 @NotNull 126 @Override 127 public List<JetScope> scopes(@NotNull JetFile file) { 128 return lastLineScope != null ? new SmartList<JetScope>(lastLineScope) : Collections.<JetScope>emptyList(); 129 } 130 }; 131 132 InjectorForReplWithJava injector = new InjectorForReplWithJava( 133 project, 134 topDownAnalysisParameters, 135 trace, 136 module, 137 scriptDeclarationFactory, 138 ProjectScope.getAllScope(project), 139 scopeProvider 140 ); 141 142 this.topDownAnalysisContext = new TopDownAnalysisContext(topDownAnalysisParameters, DataFlowInfo.EMPTY); 143 this.topDownAnalyzer = injector.getLazyTopDownAnalyzerForTopLevel(); 144 this.resolveSession = injector.getResolveSession(); 145 146 module.initialize(new CompositePackageFragmentProvider( 147 Arrays.asList( 148 injector.getResolveSession().getPackageFragmentProvider(), 149 injector.getJavaDescriptorResolver().getPackageFragmentProvider() 150 ) 151 )); 152 module.addDependencyOnModule(module); 153 module.addDependencyOnModule(KotlinBuiltIns.getInstance().getBuiltInsModule()); 154 module.seal(); 155 156 List<URL> classpath = Lists.newArrayList(); 157 for (File file : configuration.getList(JVMConfigurationKeys.CLASSPATH_KEY)) { 158 try { 159 classpath.add(file.toURI().toURL()); 160 } 161 catch (MalformedURLException e) { 162 throw UtilsPackage.rethrow(e); 163 } 164 } 165 166 this.classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[classpath.size()]))); 167 } 168 169 private static void prepareForTheNextReplLine(@NotNull TopDownAnalysisContext c) { 170 c.getScripts().clear(); 171 } 172 173 public enum LineResultType { 174 SUCCESS, 175 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 public static LineResult successful(Object value, boolean unit) { 219 return new LineResult(value, unit, null, LineResultType.SUCCESS); 220 } 221 222 public static LineResult error(@NotNull String errorText) { 223 if (errorText.isEmpty()) { 224 errorText = "<unknown error>"; 225 } 226 else if (!errorText.endsWith("\n")) { 227 errorText += "\n"; 228 } 229 return new LineResult(null, false, errorText, LineResultType.ERROR); 230 } 231 232 public static LineResult incomplete() { 233 return new LineResult(null, false, null, LineResultType.INCOMPLETE); 234 } 235 } 236 237 @NotNull 238 public LineResult eval(@NotNull String line) { 239 ++lineNumber; 240 241 FqName scriptFqName = new FqName("Line" + lineNumber); 242 Type scriptClassType = asmTypeByFqNameWithoutInnerClasses(scriptFqName); 243 244 StringBuilder fullText = new StringBuilder(); 245 for (String prevLine : previousIncompleteLines) { 246 fullText.append(prevLine).append("\n"); 247 } 248 fullText.append(line); 249 250 LightVirtualFile virtualFile = new LightVirtualFile("line" + lineNumber + JetParserDefinition.STD_SCRIPT_EXT, JetLanguage.INSTANCE, fullText.toString()); 251 virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET); 252 JetFile psiFile = (JetFile) psiFileFactory.trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false); 253 assert psiFile != null : "Script file not analyzed at line " + lineNumber + ": " + fullText; 254 255 MessageCollectorToString errorCollector = new MessageCollectorToString(); 256 257 AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport = 258 AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector); 259 260 if (syntaxErrorReport.isHasErrors() && syntaxErrorReport.isAllErrorsAtEof()) { 261 previousIncompleteLines.add(line); 262 return LineResult.incomplete(); 263 } 264 265 previousIncompleteLines.clear(); 266 267 if (syntaxErrorReport.isHasErrors()) { 268 return LineResult.error(errorCollector.getString()); 269 } 270 271 prepareForTheNextReplLine(topDownAnalysisContext); 272 trace.clearDiagnostics(); 273 274 //noinspection ConstantConditions 275 psiFile.getScript().putUserData(ScriptPriorities.PRIORITY_KEY, lineNumber); 276 277 ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorCollector); 278 if (scriptDescriptor == null) { 279 return LineResult.error(errorCollector.getString()); 280 } 281 282 List<Pair<ScriptDescriptor, Type>> earlierScripts = Lists.newArrayList(); 283 284 for (EarlierLine earlierLine : earlierLines) { 285 earlierScripts.add(Pair.create(earlierLine.getScriptDescriptor(), earlierLine.getClassType())); 286 } 287 288 GenerationState state = new GenerationState(psiFile.getProject(), ClassBuilderFactories.BINARIES, 289 module, trace.getBindingContext(), Collections.singletonList(psiFile)); 290 291 compileScript(psiFile.getScript(), scriptClassType, earlierScripts, state, CompilationErrorHandler.THROW_EXCEPTION); 292 293 for (OutputFile outputFile : state.getFactory().asList()) { 294 classLoader.addClass(JvmClassName.byInternalName(outputFile.getRelativePath().replaceFirst("\\.class$", "")), outputFile.asByteArray()); 295 } 296 297 try { 298 Class<?> scriptClass = classLoader.loadClass(scriptFqName.asString()); 299 300 Class<?>[] constructorParams = new Class<?>[earlierLines.size()]; 301 Object[] constructorArgs = new Object[earlierLines.size()]; 302 303 for (int i = 0; i < earlierLines.size(); ++i) { 304 constructorParams[i] = earlierLines.get(i).getScriptClass(); 305 constructorArgs[i] = earlierLines.get(i).getScriptInstance(); 306 } 307 308 Constructor<?> scriptInstanceConstructor = scriptClass.getConstructor(constructorParams); 309 Object scriptInstance; 310 try { 311 scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs); 312 } 313 catch (Throwable e) { 314 return LineResult.error(renderStackTrace(e.getCause())); 315 } 316 Field rvField = scriptClass.getDeclaredField("rv"); 317 rvField.setAccessible(true); 318 Object rv = rvField.get(scriptInstance); 319 320 earlierLines.add(new EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance, scriptClassType)); 321 322 JetType returnType = scriptDescriptor.getScriptCodeDescriptor().getReturnType(); 323 return LineResult.successful(rv, returnType != null && KotlinBuiltIns.isUnit(returnType)); 324 } 325 catch (Throwable e) { 326 @SuppressWarnings("UseOfSystemOutOrSystemErr") 327 PrintWriter writer = new PrintWriter(System.err); 328 classLoader.dumpClasses(writer); 329 writer.flush(); 330 throw UtilsPackage.rethrow(e); 331 } 332 } 333 334 @NotNull 335 private static String renderStackTrace(@NotNull Throwable cause) { 336 StackTraceElement[] oldTrace = cause.getStackTrace(); 337 List<StackTraceElement> newTrace = new ArrayList<StackTraceElement>(); 338 boolean skip = true; 339 for (int i = oldTrace.length - 1; i >= 0; i--) { 340 StackTraceElement element = oldTrace[i]; 341 // All our code happens in the script constructor, and no reflection/native code happens in constructors. 342 // So we ignore everything in the stack trace until the first constructor 343 if (element.getMethodName().equals("<init>")) { 344 skip = false; 345 } 346 if (!skip) { 347 newTrace.add(element); 348 } 349 } 350 Collections.reverse(newTrace); 351 cause.setStackTrace(newTrace.toArray(new StackTraceElement[newTrace.size()])); 352 return Throwables.getStackTraceAsString(cause); 353 } 354 355 @Nullable 356 private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull MessageCollector messageCollector) { 357 scriptDeclarationFactory.setDelegateFactory( 358 new FileBasedDeclarationProviderFactory(topDownAnalysisContext.getStorageManager(), Collections.singletonList(psiFile))); 359 360 TopDownAnalysisContext context = topDownAnalyzer.analyzeDeclarations( 361 topDownAnalysisContext.getTopDownAnalysisParameters(), 362 Collections.singletonList(psiFile) 363 ); 364 365 if (trace.get(BindingContext.FILE_TO_PACKAGE_FRAGMENT, psiFile) == null) { 366 trace.record(BindingContext.FILE_TO_PACKAGE_FRAGMENT, psiFile, resolveSession.getPackageFragment(FqName.ROOT)); 367 } 368 369 boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics(trace.getBindingContext().getDiagnostics(), messageCollector); 370 if (hasErrors) { 371 return null; 372 } 373 374 ScriptDescriptor scriptDescriptor = context.getScripts().get(psiFile.getScript()); 375 lastLineScope = trace.get(BindingContext.SCRIPT_SCOPE, scriptDescriptor); 376 if (lastLineScope == null) { 377 throw new IllegalStateException("last line scope is not initialized"); 378 } 379 380 return scriptDescriptor; 381 } 382 383 public void dumpClasses(@NotNull PrintWriter out) { 384 classLoader.dumpClasses(out); 385 } 386 387 private static void registerEarlierScripts( 388 @NotNull GenerationState state, 389 @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts 390 ) { 391 List<ScriptDescriptor> earlierScriptDescriptors = new ArrayList<ScriptDescriptor>(earlierScripts.size()); 392 for (Pair<ScriptDescriptor, Type> pair : earlierScripts) { 393 ScriptDescriptor earlierDescriptor = pair.first; 394 Type earlierClassType = pair.second; 395 396 PsiElement jetScript = descriptorToDeclaration(earlierDescriptor); 397 if (jetScript != null) { 398 registerClassNameForScript(state.getBindingTrace(), (JetScript) jetScript, earlierClassType); 399 earlierScriptDescriptors.add(earlierDescriptor); 400 } 401 } 402 state.setEarlierScriptsForReplInterpreter(earlierScriptDescriptors); 403 } 404 405 public static void compileScript( 406 @NotNull JetScript script, 407 @NotNull Type classType, 408 @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts, 409 @NotNull GenerationState state, 410 @NotNull CompilationErrorHandler errorHandler 411 ) { 412 registerEarlierScripts(state, earlierScripts); 413 registerClassNameForScript(state.getBindingTrace(), script, classType); 414 415 state.beforeCompile(); 416 KotlinCodegenFacade.generatePackage( 417 state, 418 script.getContainingJetFile().getPackageFqName(), 419 Collections.singleton(script.getContainingJetFile()), 420 errorHandler 421 ); 422 } 423 424 private static class ScriptMutableDeclarationProviderFactory implements DeclarationProviderFactory { 425 private DeclarationProviderFactory delegateFactory; 426 private AdaptablePackageMemberDeclarationProvider rootPackageProvider; 427 428 public void setDelegateFactory(DeclarationProviderFactory delegateFactory) { 429 this.delegateFactory = delegateFactory; 430 431 PackageMemberDeclarationProvider provider = delegateFactory.getPackageMemberDeclarationProvider(FqName.ROOT); 432 if (rootPackageProvider == null) { 433 assert provider != null; 434 rootPackageProvider = new AdaptablePackageMemberDeclarationProvider(provider); 435 } 436 else { 437 rootPackageProvider.addDelegateProvider(provider); 438 } 439 } 440 441 @NotNull 442 @Override 443 public ClassMemberDeclarationProvider getClassMemberDeclarationProvider(@NotNull JetClassLikeInfo classLikeInfo) { 444 return delegateFactory.getClassMemberDeclarationProvider(classLikeInfo); 445 } 446 447 @Nullable 448 @Override 449 public PackageMemberDeclarationProvider getPackageMemberDeclarationProvider(@NotNull FqName packageFqName) { 450 if (packageFqName.isRoot()) { 451 return rootPackageProvider; 452 } 453 454 return this.delegateFactory.getPackageMemberDeclarationProvider(packageFqName); 455 } 456 457 public static class AdaptablePackageMemberDeclarationProvider extends DelegatePackageMemberDeclarationProvider { 458 @NotNull 459 private PackageMemberDeclarationProvider delegateProvider; 460 461 public AdaptablePackageMemberDeclarationProvider(@NotNull PackageMemberDeclarationProvider delegateProvider) { 462 super(delegateProvider); 463 this.delegateProvider = delegateProvider; 464 } 465 466 public void addDelegateProvider(PackageMemberDeclarationProvider provider) { 467 delegateProvider = new CombinedPackageMemberDeclarationProvider(Lists.newArrayList(provider, delegateProvider)); 468 469 setDelegate(delegateProvider); 470 } 471 } 472 } 473 }