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