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