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