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 }