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