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.testFramework.LightVirtualFile; 031 import org.jetbrains.annotations.NotNull; 032 import org.jetbrains.annotations.Nullable; 033 import org.jetbrains.jet.OutputFile; 034 import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport; 035 import org.jetbrains.jet.cli.common.messages.MessageCollector; 036 import org.jetbrains.jet.cli.common.messages.MessageCollectorToString; 037 import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys; 038 import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment; 039 import org.jetbrains.jet.codegen.ClassBuilderFactories; 040 import org.jetbrains.jet.codegen.CompilationErrorHandler; 041 import org.jetbrains.jet.codegen.KotlinCodegenFacade; 042 import org.jetbrains.jet.codegen.state.GenerationState; 043 import org.jetbrains.jet.config.CompilerConfiguration; 044 import org.jetbrains.jet.di.InjectorForTopDownAnalyzerForJvm; 045 import org.jetbrains.jet.lang.descriptors.ScriptDescriptor; 046 import org.jetbrains.jet.lang.descriptors.impl.CompositePackageFragmentProvider; 047 import org.jetbrains.jet.lang.descriptors.impl.ModuleDescriptorImpl; 048 import org.jetbrains.jet.lang.descriptors.impl.PackageLikeBuilderDummy; 049 import org.jetbrains.jet.lang.parsing.JetParserDefinition; 050 import org.jetbrains.jet.lang.psi.JetFile; 051 import org.jetbrains.jet.lang.psi.JetScript; 052 import org.jetbrains.jet.lang.resolve.*; 053 import org.jetbrains.jet.lang.resolve.java.JvmClassName; 054 import org.jetbrains.jet.lang.resolve.java.TopDownAnalyzerFacadeForJVM; 055 import org.jetbrains.jet.lang.resolve.name.FqName; 056 import org.jetbrains.jet.lang.resolve.scopes.JetScope; 057 import org.jetbrains.jet.lang.resolve.scopes.WritableScope; 058 import org.jetbrains.jet.lang.resolve.scopes.WritableScopeImpl; 059 import org.jetbrains.jet.lang.types.JetType; 060 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; 061 import org.jetbrains.jet.plugin.JetLanguage; 062 import org.jetbrains.jet.storage.ExceptionTracker; 063 import org.jetbrains.jet.storage.LockBasedStorageManager; 064 import org.jetbrains.jet.utils.UtilsPackage; 065 import org.jetbrains.org.objectweb.asm.Type; 066 067 import java.io.File; 068 import java.io.PrintWriter; 069 import java.lang.reflect.Constructor; 070 import java.lang.reflect.Field; 071 import java.net.MalformedURLException; 072 import java.net.URL; 073 import java.net.URLClassLoader; 074 import java.util.ArrayList; 075 import java.util.Arrays; 076 import java.util.Collections; 077 import java.util.List; 078 079 import static org.jetbrains.jet.codegen.AsmUtil.asmTypeByFqNameWithoutInnerClasses; 080 import static org.jetbrains.jet.codegen.binding.CodegenBinding.registerClassNameForScript; 081 import static org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils.descriptorToDeclaration; 082 083 public class ReplInterpreter { 084 private int lineNumber = 0; 085 086 @Nullable 087 private JetScope lastLineScope; 088 private final List<EarlierLine> earlierLines = Lists.newArrayList(); 089 private final List<String> previousIncompleteLines = Lists.newArrayList(); 090 private final ReplClassLoader classLoader; 091 092 private final PsiFileFactoryImpl psiFileFactory; 093 private final BindingTraceContext trace; 094 private final ModuleDescriptorImpl module; 095 private final TopDownAnalysisContext topDownAnalysisContext; 096 private final TopDownAnalyzer topDownAnalyzer; 097 098 public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) { 099 JetCoreEnvironment environment = JetCoreEnvironment.createForProduction(disposable, configuration); 100 Project project = environment.getProject(); 101 this.psiFileFactory = (PsiFileFactoryImpl) PsiFileFactory.getInstance(project); 102 this.trace = new BindingTraceContext(); 103 this.module = TopDownAnalyzerFacadeForJVM.createJavaModule("<repl>"); 104 TopDownAnalysisParameters topDownAnalysisParameters = TopDownAnalysisParameters.createForLocalDeclarations( 105 new LockBasedStorageManager(), 106 new ExceptionTracker(), // dummy 107 Predicates.<PsiFile>alwaysTrue() 108 ); 109 InjectorForTopDownAnalyzerForJvm injector = new InjectorForTopDownAnalyzerForJvm(project, topDownAnalysisParameters, trace, module); 110 this.topDownAnalysisContext = new TopDownAnalysisContext(topDownAnalysisParameters); 111 this.topDownAnalyzer = injector.getTopDownAnalyzer(); 112 113 module.initialize(new CompositePackageFragmentProvider( 114 Arrays.asList( 115 topDownAnalyzer.getPackageFragmentProvider(), 116 injector.getJavaDescriptorResolver().getPackageFragmentProvider() 117 ) 118 )); 119 module.addDependencyOnModule(module); 120 module.addDependencyOnModule(KotlinBuiltIns.getInstance().getBuiltInsModule()); 121 module.seal(); 122 123 List<URL> classpath = Lists.newArrayList(); 124 for (File file : configuration.getList(JVMConfigurationKeys.CLASSPATH_KEY)) { 125 try { 126 classpath.add(file.toURI().toURL()); 127 } 128 catch (MalformedURLException e) { 129 throw UtilsPackage.rethrow(e); 130 } 131 } 132 133 this.classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[classpath.size()]))); 134 } 135 136 private static void prepareForTheNextReplLine(@NotNull TopDownAnalysisContext c) { 137 c.getScripts().clear(); 138 } 139 140 public enum LineResultType { 141 SUCCESS, 142 ERROR, 143 INCOMPLETE, 144 } 145 146 public static class LineResult { 147 private final Object value; 148 private final boolean unit; 149 private final String errorText; 150 private final LineResultType type; 151 152 private LineResult(Object value, boolean unit, String errorText, @NotNull LineResultType type) { 153 this.value = value; 154 this.unit = unit; 155 this.errorText = errorText; 156 this.type = type; 157 } 158 159 @NotNull 160 public LineResultType getType() { 161 return type; 162 } 163 164 private void checkSuccessful() { 165 if (getType() != LineResultType.SUCCESS) { 166 throw new IllegalStateException("it is error"); 167 } 168 } 169 170 public Object getValue() { 171 checkSuccessful(); 172 return value; 173 } 174 175 public boolean isUnit() { 176 checkSuccessful(); 177 return unit; 178 } 179 180 @NotNull 181 public String getErrorText() { 182 return errorText; 183 } 184 185 public static LineResult successful(Object value, boolean unit) { 186 return new LineResult(value, unit, null, LineResultType.SUCCESS); 187 } 188 189 public static LineResult error(@NotNull String errorText) { 190 if (errorText.isEmpty()) { 191 errorText = "<unknown error>"; 192 } 193 else if (!errorText.endsWith("\n")) { 194 errorText += "\n"; 195 } 196 return new LineResult(null, false, errorText, LineResultType.ERROR); 197 } 198 199 public static LineResult incomplete() { 200 return new LineResult(null, false, null, LineResultType.INCOMPLETE); 201 } 202 } 203 204 @NotNull 205 public LineResult eval(@NotNull String line) { 206 ++lineNumber; 207 208 FqName scriptFqName = new FqName("Line" + lineNumber); 209 Type scriptClassType = asmTypeByFqNameWithoutInnerClasses(scriptFqName); 210 211 StringBuilder fullText = new StringBuilder(); 212 for (String prevLine : previousIncompleteLines) { 213 fullText.append(prevLine).append("\n"); 214 } 215 fullText.append(line); 216 217 LightVirtualFile virtualFile = new LightVirtualFile("line" + lineNumber + JetParserDefinition.STD_SCRIPT_EXT, JetLanguage.INSTANCE, fullText.toString()); 218 virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET); 219 JetFile psiFile = (JetFile) psiFileFactory.trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false); 220 assert psiFile != null : "Script file not analyzed at line " + lineNumber + ": " + fullText; 221 222 MessageCollectorToString errorCollector = new MessageCollectorToString(); 223 224 AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport = 225 AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector); 226 227 if (syntaxErrorReport.isHasErrors() && syntaxErrorReport.isAllErrorsAtEof()) { 228 previousIncompleteLines.add(line); 229 return LineResult.incomplete(); 230 } 231 232 previousIncompleteLines.clear(); 233 234 if (syntaxErrorReport.isHasErrors()) { 235 return LineResult.error(errorCollector.getString()); 236 } 237 238 prepareForTheNextReplLine(topDownAnalysisContext); 239 trace.clearDiagnostics(); 240 241 //noinspection ConstantConditions 242 psiFile.getScript().putUserData(ScriptHeaderResolver.PRIORITY_KEY, lineNumber); 243 244 ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorCollector); 245 if (scriptDescriptor == null) { 246 return LineResult.error(errorCollector.getString()); 247 } 248 249 List<Pair<ScriptDescriptor, Type>> earlierScripts = Lists.newArrayList(); 250 251 for (EarlierLine earlierLine : earlierLines) { 252 earlierScripts.add(Pair.create(earlierLine.getScriptDescriptor(), earlierLine.getClassType())); 253 } 254 255 GenerationState state = new GenerationState(psiFile.getProject(), ClassBuilderFactories.BINARIES, 256 module, trace.getBindingContext(), Collections.singletonList(psiFile)); 257 258 compileScript(psiFile.getScript(), scriptClassType, earlierScripts, state, CompilationErrorHandler.THROW_EXCEPTION); 259 260 for (OutputFile outputFile : state.getFactory().asList()) { 261 classLoader.addClass(JvmClassName.byInternalName(outputFile.getRelativePath().replaceFirst("\\.class$", "")), outputFile.asByteArray()); 262 } 263 264 try { 265 Class<?> scriptClass = classLoader.loadClass(scriptFqName.asString()); 266 267 Class<?>[] constructorParams = new Class<?>[earlierLines.size()]; 268 Object[] constructorArgs = new Object[earlierLines.size()]; 269 270 for (int i = 0; i < earlierLines.size(); ++i) { 271 constructorParams[i] = earlierLines.get(i).getScriptClass(); 272 constructorArgs[i] = earlierLines.get(i).getScriptInstance(); 273 } 274 275 Constructor<?> scriptInstanceConstructor = scriptClass.getConstructor(constructorParams); 276 Object scriptInstance; 277 try { 278 scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs); 279 } 280 catch (Throwable e) { 281 return LineResult.error(renderStackTrace(e.getCause())); 282 } 283 Field rvField = scriptClass.getDeclaredField("rv"); 284 rvField.setAccessible(true); 285 Object rv = rvField.get(scriptInstance); 286 287 earlierLines.add(new EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance, scriptClassType)); 288 289 JetType returnType = scriptDescriptor.getScriptCodeDescriptor().getReturnType(); 290 return LineResult.successful(rv, returnType != null && KotlinBuiltIns.getInstance().isUnit(returnType)); 291 } 292 catch (Throwable e) { 293 PrintWriter writer = new PrintWriter(System.err); 294 classLoader.dumpClasses(writer); 295 writer.flush(); 296 throw UtilsPackage.rethrow(e); 297 } 298 } 299 300 @NotNull 301 private static String renderStackTrace(@NotNull Throwable cause) { 302 StackTraceElement[] oldTrace = cause.getStackTrace(); 303 List<StackTraceElement> newTrace = new ArrayList<StackTraceElement>(); 304 boolean skip = true; 305 for (int i = oldTrace.length - 1; i >= 0; i--) { 306 StackTraceElement element = oldTrace[i]; 307 // All our code happens in the script constructor, and no reflection/native code happens in constructors. 308 // So we ignore everything in the stack trace until the first constructor 309 if (element.getMethodName().equals("<init>")) { 310 skip = false; 311 } 312 if (!skip) { 313 newTrace.add(element); 314 } 315 } 316 Collections.reverse(newTrace); 317 cause.setStackTrace(newTrace.toArray(new StackTraceElement[newTrace.size()])); 318 return Throwables.getStackTraceAsString(cause); 319 } 320 321 @Nullable 322 private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull MessageCollector messageCollector) { 323 WritableScope scope = new WritableScopeImpl( 324 JetScope.EMPTY, module, 325 new TraceBasedRedeclarationHandler(trace), "Root scope in analyzePackage" 326 ); 327 328 scope.changeLockLevel(WritableScope.LockLevel.BOTH); 329 330 // Import a scope that contains all top-level packages that come from dependencies 331 // This makes the packages visible at all, does not import themselves 332 scope.importScope(module.getPackage(FqName.ROOT).getMemberScope()); 333 334 if (lastLineScope != null) { 335 scope.importScope(lastLineScope); 336 } 337 338 scope.changeLockLevel(WritableScope.LockLevel.READING); 339 340 // dummy builder is used because "root" is module descriptor, 341 // packages added to module explicitly in 342 topDownAnalyzer.doProcess(topDownAnalysisContext, scope, new PackageLikeBuilderDummy(), Collections.singletonList(psiFile)); 343 344 boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics(trace.getBindingContext().getDiagnostics(), messageCollector); 345 if (hasErrors) { 346 return null; 347 } 348 349 ScriptDescriptor scriptDescriptor = topDownAnalysisContext.getScripts().get(psiFile.getScript()); 350 lastLineScope = trace.get(BindingContext.SCRIPT_SCOPE, scriptDescriptor); 351 if (lastLineScope == null) { 352 throw new IllegalStateException("last line scope is not initialized"); 353 } 354 355 return scriptDescriptor; 356 } 357 358 public void dumpClasses(@NotNull PrintWriter out) { 359 classLoader.dumpClasses(out); 360 } 361 362 private static void registerEarlierScripts( 363 @NotNull GenerationState state, 364 @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts 365 ) { 366 List<ScriptDescriptor> earlierScriptDescriptors = new ArrayList<ScriptDescriptor>(earlierScripts.size()); 367 for (Pair<ScriptDescriptor, Type> pair : earlierScripts) { 368 ScriptDescriptor earlierDescriptor = pair.first; 369 Type earlierClassType = pair.second; 370 371 PsiElement jetScript = descriptorToDeclaration(earlierDescriptor); 372 if (jetScript != null) { 373 registerClassNameForScript(state.getBindingTrace(), (JetScript) jetScript, earlierClassType); 374 earlierScriptDescriptors.add(earlierDescriptor); 375 } 376 } 377 state.setEarlierScriptsForReplInterpreter(earlierScriptDescriptors); 378 } 379 380 public static void compileScript( 381 @NotNull JetScript script, 382 @NotNull Type classType, 383 @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts, 384 @NotNull GenerationState state, 385 @NotNull CompilationErrorHandler errorHandler 386 ) { 387 registerEarlierScripts(state, earlierScripts); 388 registerClassNameForScript(state.getBindingTrace(), script, classType); 389 390 state.beforeCompile(); 391 KotlinCodegenFacade.generatePackage( 392 state, 393 script.getContainingJetFile().getPackageFqName(), 394 Collections.singleton(script.getContainingJetFile()), 395 errorHandler 396 ); 397 } 398 399 }