001    /*
002     * Copyright 2010-2015 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.kotlin.cli.jvm.repl;
018    
019    import com.google.common.base.Throwables;
020    import com.google.common.collect.Lists;
021    import com.intellij.openapi.Disposable;
022    import com.intellij.openapi.project.Project;
023    import com.intellij.openapi.util.Pair;
024    import com.intellij.openapi.vfs.CharsetToolkit;
025    import com.intellij.psi.PsiElement;
026    import com.intellij.psi.PsiFileFactory;
027    import com.intellij.psi.impl.PsiFileFactoryImpl;
028    import com.intellij.psi.search.ProjectScope;
029    import com.intellij.testFramework.LightVirtualFile;
030    import org.jetbrains.annotations.NotNull;
031    import org.jetbrains.annotations.Nullable;
032    import org.jetbrains.kotlin.backend.common.output.OutputFile;
033    import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
034    import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport;
035    import org.jetbrains.kotlin.cli.common.messages.DiagnosticMessageReporter;
036    import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport;
037    import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
038    import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider;
039    import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
040    import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt;
041    import org.jetbrains.kotlin.cli.jvm.config.ModuleNameKt;
042    import org.jetbrains.kotlin.cli.jvm.repl.di.ContainerForReplWithJava;
043    import org.jetbrains.kotlin.cli.jvm.repl.di.InjectionKt;
044    import org.jetbrains.kotlin.cli.jvm.repl.di.ReplLastLineScopeProvider;
045    import org.jetbrains.kotlin.cli.jvm.repl.messages.DiagnosticMessageHolder;
046    import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplIdeDiagnosticMessageHolder;
047    import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplSystemInWrapper;
048    import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplTerminalDiagnosticMessageHolder;
049    import org.jetbrains.kotlin.codegen.ClassBuilderFactories;
050    import org.jetbrains.kotlin.codegen.CompilationErrorHandler;
051    import org.jetbrains.kotlin.codegen.KotlinCodegenFacade;
052    import org.jetbrains.kotlin.codegen.state.GenerationState;
053    import org.jetbrains.kotlin.config.CompilerConfiguration;
054    import org.jetbrains.kotlin.context.MutableModuleContext;
055    import org.jetbrains.kotlin.descriptors.ScriptDescriptor;
056    import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider;
057    import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl;
058    import org.jetbrains.kotlin.idea.KotlinLanguage;
059    import org.jetbrains.kotlin.name.FqName;
060    import org.jetbrains.kotlin.parsing.KotlinParserDefinition;
061    import org.jetbrains.kotlin.psi.KtFile;
062    import org.jetbrains.kotlin.psi.KtScript;
063    import org.jetbrains.kotlin.resolve.*;
064    import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
065    import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
066    import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM;
067    import org.jetbrains.kotlin.resolve.lazy.ResolveSession;
068    import org.jetbrains.kotlin.resolve.lazy.data.JetClassLikeInfo;
069    import org.jetbrains.kotlin.resolve.lazy.declarations.*;
070    import org.jetbrains.kotlin.resolve.scopes.LexicalScope;
071    import org.jetbrains.kotlin.types.KotlinType;
072    import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
073    import org.jetbrains.org.objectweb.asm.Type;
074    
075    import java.io.File;
076    import java.io.PrintWriter;
077    import java.lang.reflect.Constructor;
078    import java.lang.reflect.Field;
079    import java.net.MalformedURLException;
080    import java.net.URL;
081    import java.net.URLClassLoader;
082    import java.util.ArrayList;
083    import java.util.Arrays;
084    import java.util.Collections;
085    import java.util.List;
086    
087    import static org.jetbrains.kotlin.codegen.AsmUtil.asmTypeByFqNameWithoutInnerClasses;
088    import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.registerClassNameForScript;
089    import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration;
090    
091    public class ReplInterpreter {
092        private int lineNumber = 0;
093    
094        @Nullable
095        private LexicalScope lastLineScope;
096        private final List<EarlierLine> earlierLines = Lists.newArrayList();
097        private final List<String> previousIncompleteLines = Lists.newArrayList();
098        private final ReplClassLoader classLoader;
099    
100        private final PsiFileFactoryImpl psiFileFactory;
101        private final BindingTraceContext trace;
102        private final ModuleDescriptorImpl module;
103    
104        private final TopDownAnalysisContext topDownAnalysisContext;
105        private final LazyTopDownAnalyzerForTopLevel topDownAnalyzer;
106        private final ResolveSession resolveSession;
107        private final ScriptMutableDeclarationProviderFactory scriptDeclarationFactory;
108    
109        private final boolean ideMode;
110        private final ReplSystemInWrapper replReader;
111    
112        public ReplInterpreter(
113                @NotNull Disposable disposable,
114                @NotNull CompilerConfiguration configuration,
115                boolean ideMode,
116                @Nullable ReplSystemInWrapper replReader
117        ) {
118            KotlinCoreEnvironment environment =
119                    KotlinCoreEnvironment.createForProduction(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
120            Project project = environment.getProject();
121            this.psiFileFactory = (PsiFileFactoryImpl) PsiFileFactory.getInstance(project);
122            this.trace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace();
123            MutableModuleContext moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(project, ModuleNameKt
124                    .getModuleName(environment));
125            this.module = moduleContext.getModule();
126    
127            scriptDeclarationFactory = new ScriptMutableDeclarationProviderFactory();
128    
129            ContainerForReplWithJava container = InjectionKt.createContainerForReplWithJava(
130                    moduleContext,
131                    trace,
132                    scriptDeclarationFactory,
133                    ProjectScope.getAllScope(project),
134                    new ReplLastLineScopeProvider() {
135                        @Nullable
136                        @Override
137                        public LexicalScope getLastLineScope() {
138                            return lastLineScope;
139                        }
140                    },
141                    new JvmPackagePartProvider(environment)
142            );
143    
144            this.topDownAnalysisContext = new TopDownAnalysisContext(TopDownAnalysisMode.LocalDeclarations, DataFlowInfo.EMPTY,
145                                                                     container.getResolveSession().getDeclarationScopeProvider());
146            this.topDownAnalyzer = container.getLazyTopDownAnalyzerForTopLevel();
147            this.resolveSession = container.getResolveSession();
148    
149            moduleContext.initializeModuleContents(new CompositePackageFragmentProvider(
150                    Arrays.asList(
151                            container.getResolveSession().getPackageFragmentProvider(),
152                            container.getJavaDescriptorResolver().getPackageFragmentProvider()
153                    )
154            ));
155    
156            List<URL> classpath = Lists.newArrayList();
157            for (File file : JvmContentRootsKt.getJvmClasspathRoots(configuration)) {
158                try {
159                    classpath.add(file.toURI().toURL());
160                }
161                catch (MalformedURLException e) {
162                    throw ExceptionUtilsKt.rethrow(e);
163                }
164            }
165    
166            this.classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[classpath.size()]), null));
167    
168            this.ideMode = ideMode;
169            this.replReader = replReader;
170        }
171    
172        private static void prepareForTheNextReplLine(@NotNull TopDownAnalysisContext c) {
173            c.getScripts().clear();
174        }
175    
176        public enum LineResultType {
177            SUCCESS,
178            COMPILE_ERROR,
179            RUNTIME_ERROR,
180            INCOMPLETE,
181        }
182    
183        public static class LineResult {
184            private final Object value;
185            private final boolean unit;
186            private final String errorText;
187            private final LineResultType type;
188    
189            private LineResult(Object value, boolean unit, String errorText, @NotNull LineResultType type) {
190                this.value = value;
191                this.unit = unit;
192                this.errorText = errorText;
193                this.type = type;
194            }
195    
196            @NotNull
197            public LineResultType getType() {
198                return type;
199            }
200    
201            private void checkSuccessful() {
202                if (getType() != LineResultType.SUCCESS) {
203                    throw new IllegalStateException("it is error");
204                }
205            }
206    
207            public Object getValue() {
208                checkSuccessful();
209                return value;
210            }
211    
212            public boolean isUnit() {
213                checkSuccessful();
214                return unit;
215            }
216    
217            @NotNull
218            public String getErrorText() {
219                return errorText;
220            }
221    
222            @NotNull
223            private static LineResult error(@NotNull String errorText, @NotNull LineResultType errorType) {
224                if (errorText.isEmpty()) {
225                    errorText = "<unknown error>";
226                }
227                else if (!errorText.endsWith("\n")) {
228                    errorText += "\n";
229                }
230    
231                return new LineResult(null, false, errorText, errorType);
232            }
233    
234            @NotNull
235            public static LineResult successful(Object value, boolean unit) {
236                return new LineResult(value, unit, null, LineResultType.SUCCESS);
237            }
238    
239            @NotNull
240            public static LineResult compileError(@NotNull String errorText) {
241                return error(errorText, LineResultType.COMPILE_ERROR);
242            }
243    
244            @NotNull
245            public static LineResult runtimeError(@NotNull String errorText) {
246                return error(errorText, LineResultType.RUNTIME_ERROR);
247            }
248    
249            public static LineResult incomplete() {
250                return new LineResult(null, false, null, LineResultType.INCOMPLETE);
251            }
252        }
253    
254        @NotNull
255        private DiagnosticMessageHolder createDiagnosticHolder() {
256            return ideMode ? new ReplIdeDiagnosticMessageHolder()
257                           : new ReplTerminalDiagnosticMessageHolder();
258        }
259    
260        @NotNull
261        public LineResult eval(@NotNull String line) {
262            ++lineNumber;
263    
264            FqName scriptFqName = new FqName("Line" + lineNumber);
265            Type scriptClassType = asmTypeByFqNameWithoutInnerClasses(scriptFqName);
266    
267            StringBuilder fullText = new StringBuilder();
268            for (String prevLine : previousIncompleteLines) {
269                fullText.append(prevLine).append("\n");
270            }
271            fullText.append(line);
272    
273            LightVirtualFile virtualFile = new LightVirtualFile("line" + lineNumber + KotlinParserDefinition.STD_SCRIPT_EXT, KotlinLanguage.INSTANCE, fullText.toString());
274            virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET);
275            KtFile psiFile = (KtFile) psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false);
276            assert psiFile != null : "Script file not analyzed at line " + lineNumber + ": " + fullText;
277    
278            DiagnosticMessageHolder errorHolder = createDiagnosticHolder();
279    
280            AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorHolder);
281    
282            if (syntaxErrorReport.isHasErrors() && syntaxErrorReport.isAllErrorsAtEof()) {
283                if (ideMode) {
284                    return LineResult.compileError(errorHolder.getRenderedDiagnostics());
285                }
286                else {
287                    previousIncompleteLines.add(line);
288                    return LineResult.incomplete();
289                }
290            }
291    
292            previousIncompleteLines.clear();
293    
294            if (syntaxErrorReport.isHasErrors()) {
295                return LineResult.compileError(errorHolder.getRenderedDiagnostics());
296            }
297    
298            prepareForTheNextReplLine(topDownAnalysisContext);
299            trace.clearDiagnostics();
300    
301            //noinspection ConstantConditions
302            psiFile.getScript().putUserData(ScriptPriorities.PRIORITY_KEY, lineNumber);
303    
304            ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorHolder);
305            if (scriptDescriptor == null) {
306                return LineResult.compileError(errorHolder.getRenderedDiagnostics());
307            }
308    
309            List<Pair<ScriptDescriptor, Type>> earlierScripts = Lists.newArrayList();
310    
311            for (EarlierLine earlierLine : earlierLines) {
312                earlierScripts.add(Pair.create(earlierLine.getScriptDescriptor(), earlierLine.getClassType()));
313            }
314    
315            GenerationState state = new GenerationState(psiFile.getProject(), ClassBuilderFactories.BINARIES,
316                                                        module, trace.getBindingContext(), Collections.singletonList(psiFile));
317    
318            compileScript(psiFile.getScript(), scriptClassType, earlierScripts, state, CompilationErrorHandler.THROW_EXCEPTION);
319    
320            for (OutputFile outputFile : state.getFactory().asList()) {
321                if(outputFile.getRelativePath().endsWith(".class")) {
322                    classLoader.addClass(JvmClassName.byInternalName(outputFile.getRelativePath().replaceFirst("\\.class$", "")),
323                                         outputFile.asByteArray());
324                }
325            }
326    
327            try {
328                Class<?> scriptClass = classLoader.loadClass(scriptFqName.asString());
329    
330                Class<?>[] constructorParams = new Class<?>[earlierLines.size()];
331                Object[] constructorArgs = new Object[earlierLines.size()];
332    
333                for (int i = 0; i < earlierLines.size(); ++i) {
334                    constructorParams[i] = earlierLines.get(i).getScriptClass();
335                    constructorArgs[i] = earlierLines.get(i).getScriptInstance();
336                }
337    
338                Constructor<?> scriptInstanceConstructor = scriptClass.getConstructor(constructorParams);
339                Object scriptInstance;
340                try {
341                    setReplScriptExecuting(true);
342                    scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs);
343                }
344                catch (Throwable e) {
345                    return LineResult.runtimeError(renderStackTrace(e.getCause()));
346                } finally {
347                    setReplScriptExecuting(false);
348                }
349    
350                Field rvField = scriptClass.getDeclaredField("rv");
351                rvField.setAccessible(true);
352                Object rv = rvField.get(scriptInstance);
353    
354                earlierLines.add(new EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance, scriptClassType));
355    
356                KotlinType returnType = scriptDescriptor.getScriptCodeDescriptor().getReturnType();
357                return LineResult.successful(rv, returnType != null && KotlinBuiltIns.isUnit(returnType));
358            }
359            catch (Throwable e) {
360                @SuppressWarnings("UseOfSystemOutOrSystemErr")
361                PrintWriter writer = new PrintWriter(System.err);
362                classLoader.dumpClasses(writer);
363                writer.flush();
364                throw ExceptionUtilsKt.rethrow(e);
365            }
366        }
367    
368        private void setReplScriptExecuting(boolean isExecuting) {
369            if (replReader != null) {
370                replReader.setReplScriptExecuting(isExecuting);
371            }
372        }
373    
374        @NotNull
375        private static String renderStackTrace(@NotNull Throwable cause) {
376            StackTraceElement[] oldTrace = cause.getStackTrace();
377            List<StackTraceElement> newTrace = new ArrayList<StackTraceElement>();
378            boolean skip = true;
379            for (int i = oldTrace.length - 1; i >= 0; i--) {
380                StackTraceElement element = oldTrace[i];
381                // All our code happens in the script constructor, and no reflection/native code happens in constructors.
382                // So we ignore everything in the stack trace until the first constructor
383                if (element.getMethodName().equals("<init>")) {
384                    skip = false;
385                }
386                if (!skip) {
387                    newTrace.add(element);
388                }
389            }
390            Collections.reverse(newTrace);
391    
392            // throw away last element which contains Line1.kts<init>(Unknown source)
393            List<StackTraceElement> resultingTrace = newTrace.subList(0, newTrace.size() - 1);
394    
395            cause.setStackTrace(resultingTrace.toArray(new StackTraceElement[resultingTrace.size()]));
396            return Throwables.getStackTraceAsString(cause);
397        }
398    
399        @Nullable
400        private ScriptDescriptor doAnalyze(@NotNull KtFile psiFile, @NotNull DiagnosticMessageReporter errorReporter) {
401            scriptDeclarationFactory.setDelegateFactory(
402                    new FileBasedDeclarationProviderFactory(resolveSession.getStorageManager(), Collections.singletonList(psiFile)));
403    
404            TopDownAnalysisContext context = topDownAnalyzer.analyzeDeclarations(
405                    topDownAnalysisContext.getTopDownAnalysisMode(),
406                    Collections.singletonList(psiFile)
407            );
408    
409            if (trace.get(BindingContext.FILE_TO_PACKAGE_FRAGMENT, psiFile) == null) {
410                trace.record(BindingContext.FILE_TO_PACKAGE_FRAGMENT, psiFile, resolveSession.getPackageFragment(FqName.ROOT));
411            }
412    
413            boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics(trace.getBindingContext().getDiagnostics(), errorReporter, false);
414            if (hasErrors) {
415                return null;
416            }
417    
418            ScriptDescriptor scriptDescriptor = context.getScripts().get(psiFile.getScript());
419            lastLineScope = trace.get(BindingContext.SCRIPT_SCOPE, scriptDescriptor);
420            if (lastLineScope == null) {
421                throw new IllegalStateException("last line scope is not initialized");
422            }
423    
424            return scriptDescriptor;
425        }
426    
427        public void dumpClasses(@NotNull PrintWriter out) {
428            classLoader.dumpClasses(out);
429        }
430    
431        private static void registerEarlierScripts(
432                @NotNull GenerationState state,
433                @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts
434        ) {
435            List<ScriptDescriptor> earlierScriptDescriptors = new ArrayList<ScriptDescriptor>(earlierScripts.size());
436            for (Pair<ScriptDescriptor, Type> pair : earlierScripts) {
437                ScriptDescriptor earlierDescriptor = pair.first;
438                Type earlierClassType = pair.second;
439    
440                PsiElement jetScript = descriptorToDeclaration(earlierDescriptor);
441                if (jetScript != null) {
442                    registerClassNameForScript(state.getBindingTrace(), (KtScript) jetScript, earlierClassType, state.getFileClassesProvider());
443                    earlierScriptDescriptors.add(earlierDescriptor);
444                }
445            }
446            state.setEarlierScriptsForReplInterpreter(earlierScriptDescriptors);
447        }
448    
449        public static void compileScript(
450                @NotNull KtScript script,
451                @NotNull Type classType,
452                @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts,
453                @NotNull GenerationState state,
454                @NotNull CompilationErrorHandler errorHandler
455        ) {
456            registerEarlierScripts(state, earlierScripts);
457            registerClassNameForScript(state.getBindingTrace(), script, classType, state.getFileClassesProvider());
458    
459            state.beforeCompile();
460            KotlinCodegenFacade.generatePackage(
461                    state,
462                    script.getContainingKtFile().getPackageFqName(),
463                    Collections.singleton(script.getContainingKtFile()),
464                    errorHandler
465            );
466        }
467    
468        private static class ScriptMutableDeclarationProviderFactory implements DeclarationProviderFactory {
469            private DeclarationProviderFactory delegateFactory;
470            private AdaptablePackageMemberDeclarationProvider rootPackageProvider;
471    
472            public void setDelegateFactory(DeclarationProviderFactory delegateFactory) {
473                this.delegateFactory = delegateFactory;
474    
475                PackageMemberDeclarationProvider provider = delegateFactory.getPackageMemberDeclarationProvider(FqName.ROOT);
476                if (rootPackageProvider == null) {
477                    assert provider != null;
478                    rootPackageProvider = new AdaptablePackageMemberDeclarationProvider(provider);
479                }
480                else {
481                    rootPackageProvider.addDelegateProvider(provider);
482                }
483            }
484    
485            @NotNull
486            @Override
487            public ClassMemberDeclarationProvider getClassMemberDeclarationProvider(@NotNull JetClassLikeInfo classLikeInfo) {
488                return delegateFactory.getClassMemberDeclarationProvider(classLikeInfo);
489            }
490    
491            @Nullable
492            @Override
493            public PackageMemberDeclarationProvider getPackageMemberDeclarationProvider(@NotNull FqName packageFqName) {
494                if (packageFqName.isRoot()) {
495                    return rootPackageProvider;
496                }
497    
498                return this.delegateFactory.getPackageMemberDeclarationProvider(packageFqName);
499            }
500    
501            public static class AdaptablePackageMemberDeclarationProvider extends DelegatePackageMemberDeclarationProvider {
502                @NotNull
503                private PackageMemberDeclarationProvider delegateProvider;
504    
505                public AdaptablePackageMemberDeclarationProvider(@NotNull PackageMemberDeclarationProvider delegateProvider) {
506                    super(delegateProvider);
507                    this.delegateProvider = delegateProvider;
508                }
509    
510                public void addDelegateProvider(PackageMemberDeclarationProvider provider) {
511                    delegateProvider = new CombinedPackageMemberDeclarationProvider(Lists.newArrayList(provider, delegateProvider));
512    
513                    setDelegate(delegateProvider);
514                }
515            }
516        }
517    }