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    }