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