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