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