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