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.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.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, DataFlowInfo.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.reportSyntaxErrors(psiFile, errorHolder);
299    
300            if (syntaxErrorReport.isHasErrors() && syntaxErrorReport.isAllErrorsAtEof()) {
301                if (ideMode) {
302                    return LineResult.compileError(errorHolder.getRenderedDiagnostics());
303                }
304                else {
305                    previousIncompleteLines.add(line);
306                    return LineResult.incomplete();
307                }
308            }
309    
310            previousIncompleteLines.clear();
311    
312            if (syntaxErrorReport.isHasErrors()) {
313                return LineResult.compileError(errorHolder.getRenderedDiagnostics());
314            }
315    
316            prepareForTheNextReplLine(topDownAnalysisContext);
317            trace.clearDiagnostics();
318    
319            //noinspection ConstantConditions
320            psiFile.getScript().putUserData(ScriptPriorities.PRIORITY_KEY, lineNumber);
321    
322            ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorHolder);
323            if (scriptDescriptor == null) {
324                return LineResult.compileError(errorHolder.getRenderedDiagnostics());
325            }
326    
327            List<ScriptDescriptor> earlierScripts = Lists.newArrayList();
328    
329            for (EarlierLine earlierLine : earlierLines) {
330                earlierScripts.add(earlierLine.getScriptDescriptor());
331            }
332    
333            GenerationState state = new GenerationState(psiFile.getProject(), ClassBuilderFactories.BINARIES,
334                                                        module, trace.getBindingContext(), Collections.singletonList(psiFile));
335    
336            compileScript(psiFile.getScript(), earlierScripts, state, CompilationErrorHandler.THROW_EXCEPTION);
337    
338            for (OutputFile outputFile : state.getFactory().asList()) {
339                if(outputFile.getRelativePath().endsWith(".class")) {
340                    classLoader.addClass(JvmClassName.byInternalName(outputFile.getRelativePath().replaceFirst("\\.class$", "")),
341                                         outputFile.asByteArray());
342                }
343            }
344    
345            try {
346                Class<?> scriptClass = classLoader.loadClass(scriptFqName.asString());
347    
348                Class<?>[] constructorParams = new Class<?>[earlierLines.size()];
349                Object[] constructorArgs = new Object[earlierLines.size()];
350    
351                for (int i = 0; i < earlierLines.size(); ++i) {
352                    constructorParams[i] = earlierLines.get(i).getScriptClass();
353                    constructorArgs[i] = earlierLines.get(i).getScriptInstance();
354                }
355    
356                Constructor<?> scriptInstanceConstructor = scriptClass.getConstructor(constructorParams);
357                Object scriptInstance;
358                try {
359                    setReplScriptExecuting(true);
360                    scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs);
361                }
362                catch (Throwable e) {
363                    return LineResult.runtimeError(renderStackTrace(e.getCause()));
364                } finally {
365                    setReplScriptExecuting(false);
366                }
367    
368                Field rvField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME);
369                rvField.setAccessible(true);
370                Object rv = rvField.get(scriptInstance);
371    
372                earlierLines.add(new EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance));
373    
374                return LineResult.successful(rv, !state.getReplSpecific().getHasResult());
375            }
376            catch (Throwable e) {
377                @SuppressWarnings("UseOfSystemOutOrSystemErr")
378                PrintWriter writer = new PrintWriter(System.err);
379                classLoader.dumpClasses(writer);
380                writer.flush();
381                throw ExceptionUtilsKt.rethrow(e);
382            }
383        }
384    
385        private void setReplScriptExecuting(boolean isExecuting) {
386            if (replReader != null) {
387                replReader.setReplScriptExecuting(isExecuting);
388            }
389        }
390    
391        @NotNull
392        private static String renderStackTrace(@NotNull Throwable cause) {
393            StackTraceElement[] oldTrace = cause.getStackTrace();
394            List<StackTraceElement> newTrace = new ArrayList<StackTraceElement>();
395            boolean skip = true;
396            for (int i = oldTrace.length - 1; i >= 0; i--) {
397                StackTraceElement element = oldTrace[i];
398                // All our code happens in the script constructor, and no reflection/native code happens in constructors.
399                // So we ignore everything in the stack trace until the first constructor
400                if (element.getMethodName().equals("<init>")) {
401                    skip = false;
402                }
403                if (!skip) {
404                    newTrace.add(element);
405                }
406            }
407            Collections.reverse(newTrace);
408    
409            // throw away last element which contains Line1.kts<init>(Unknown source)
410            List<StackTraceElement> resultingTrace = newTrace.subList(0, newTrace.size() - 1);
411    
412            cause.setStackTrace(resultingTrace.toArray(new StackTraceElement[resultingTrace.size()]));
413            return Throwables.getStackTraceAsString(cause);
414        }
415    
416        @Nullable
417        private ScriptDescriptor doAnalyze(@NotNull KtFile psiFile, @NotNull DiagnosticMessageReporter errorReporter) {
418            scriptDeclarationFactory.setDelegateFactory(
419                    new FileBasedDeclarationProviderFactory(resolveSession.getStorageManager(), Collections.singletonList(psiFile)));
420    
421            TopDownAnalysisContext context = topDownAnalyzer.analyzeDeclarations(
422                    topDownAnalysisContext.getTopDownAnalysisMode(),
423                    Collections.singletonList(psiFile)
424            );
425    
426            if (trace.get(BindingContext.FILE_TO_PACKAGE_FRAGMENT, psiFile) == null) {
427                trace.record(BindingContext.FILE_TO_PACKAGE_FRAGMENT, psiFile, resolveSession.getPackageFragment(FqName.ROOT));
428            }
429    
430            boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics(trace.getBindingContext().getDiagnostics(), errorReporter, false);
431            if (hasErrors) {
432                return null;
433            }
434    
435            LazyScriptDescriptor scriptDescriptor = context.getScripts().get(psiFile.getScript());
436            lastLineScope = scriptDescriptor.getScopeForInitializerResolution();
437            return scriptDescriptor;
438        }
439    
440        public void dumpClasses(@NotNull PrintWriter out) {
441            classLoader.dumpClasses(out);
442        }
443    
444        public static void compileScript(
445                @NotNull KtScript script,
446                @NotNull List<ScriptDescriptor> earlierScripts,
447                @NotNull GenerationState state,
448                @NotNull CompilationErrorHandler errorHandler
449        ) {
450            state.getReplSpecific().setScriptResultFieldName(SCRIPT_RESULT_FIELD_NAME);
451            state.getReplSpecific().setEarlierScriptsForReplInterpreter(new ArrayList<ScriptDescriptor>(earlierScripts));
452    
453            state.beforeCompile();
454            KotlinCodegenFacade.generatePackage(
455                    state,
456                    script.getContainingKtFile().getPackageFqName(),
457                    Collections.singleton(script.getContainingKtFile()),
458                    errorHandler
459            );
460        }
461    
462        private static class ScriptMutableDeclarationProviderFactory implements DeclarationProviderFactory {
463            private DeclarationProviderFactory delegateFactory;
464            private AdaptablePackageMemberDeclarationProvider rootPackageProvider;
465    
466            public void setDelegateFactory(DeclarationProviderFactory delegateFactory) {
467                this.delegateFactory = delegateFactory;
468    
469                PackageMemberDeclarationProvider provider = delegateFactory.getPackageMemberDeclarationProvider(FqName.ROOT);
470                if (rootPackageProvider == null) {
471                    assert provider != null;
472                    rootPackageProvider = new AdaptablePackageMemberDeclarationProvider(provider);
473                }
474                else {
475                    rootPackageProvider.addDelegateProvider(provider);
476                }
477            }
478    
479            @NotNull
480            @Override
481            public ClassMemberDeclarationProvider getClassMemberDeclarationProvider(@NotNull KtClassLikeInfo classLikeInfo) {
482                return delegateFactory.getClassMemberDeclarationProvider(classLikeInfo);
483            }
484    
485            @Nullable
486            @Override
487            public PackageMemberDeclarationProvider getPackageMemberDeclarationProvider(@NotNull FqName packageFqName) {
488                if (packageFqName.isRoot()) {
489                    return rootPackageProvider;
490                }
491    
492                return this.delegateFactory.getPackageMemberDeclarationProvider(packageFqName);
493            }
494    
495            @Override
496            public void diagnoseMissingPackageFragment(KtFile file) {
497                this.delegateFactory.diagnoseMissingPackageFragment(file);
498            }
499    
500            public static class AdaptablePackageMemberDeclarationProvider extends DelegatePackageMemberDeclarationProvider {
501                @NotNull
502                private PackageMemberDeclarationProvider delegateProvider;
503    
504                public AdaptablePackageMemberDeclarationProvider(@NotNull PackageMemberDeclarationProvider delegateProvider) {
505                    super(delegateProvider);
506                    this.delegateProvider = delegateProvider;
507                }
508    
509                public void addDelegateProvider(PackageMemberDeclarationProvider provider) {
510                    delegateProvider = new CombinedPackageMemberDeclarationProvider(Lists.newArrayList(provider, delegateProvider));
511    
512                    setDelegate(delegateProvider);
513                }
514            }
515        }
516    }