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