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