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