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