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