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