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.INSTANCE$, 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 }