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