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    }