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