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