From 128fd40945751fb3688167b669728d5d0f6ed6db Mon Sep 17 00:00:00 2001 From: "Egor.Ushakov" Date: Mon, 8 Sep 2014 20:30:23 +0400 Subject: [PATCH] compiling evaluator fixes: - use extract method - enable only when needed --- .../engine/JavaDebuggerEvaluator.java | 31 +++- .../expression/AnonymousClassException.java | 34 ++++ .../expression/EvaluatorBuilderImpl.java | 2 +- .../ui/impl/watch/CompilingEvaluator.java | 158 +++++++++++++++--- .../ui/impl/watch/EvaluationDescriptor.java | 25 ++- 5 files changed, 219 insertions(+), 31 deletions(-) create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/AnonymousClassException.java diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebuggerEvaluator.java b/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebuggerEvaluator.java index 2eab90666967..8ae28345e64f 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebuggerEvaluator.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebuggerEvaluator.java @@ -20,15 +20,21 @@ import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; import com.intellij.debugger.engine.evaluation.TextWithImportsImpl; import com.intellij.debugger.engine.events.DebuggerContextCommandImpl; import com.intellij.debugger.impl.EditorTextProvider; +import com.intellij.debugger.ui.impl.watch.CompilingEvaluator; import com.intellij.debugger.ui.impl.watch.WatchItemDescriptor; +import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.psi.PsiCodeFragment; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.refactoring.extractMethod.PrepareFailedException; +import com.intellij.refactoring.extractMethodObject.ExtractLightMethodObjectHandler; import com.intellij.xdebugger.XExpression; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.evaluation.XDebuggerEvaluator; @@ -59,6 +65,29 @@ public class JavaDebuggerEvaluator extends XDebuggerEvaluator { public void evaluate(@NotNull final XExpression expression, @NotNull final XEvaluationCallback callback, @Nullable XSourcePosition expressionPosition) { + final Project project = myDebugProcess.getProject(); + final WatchItemDescriptor descriptor = new WatchItemDescriptor(project, TextWithImportsImpl.fromXExpression(expression)); + if (Registry.is("debugger.compiling.evaluator")) { + try { + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + @Override + public void run() { + PsiElement element = myDebugProcess.getDebuggerContext().getContextElement(); + PsiFile psiFile = element.getContainingFile(); + PsiCodeFragment fragment = descriptor.createCodeFragment(element); + PsiDocumentManager.getInstance(project).commitAllDocuments(); + ExtractLightMethodObjectHandler.ExtractedData data = null; + try { + data = ExtractLightMethodObjectHandler.extractLightMethodObject(project, psiFile, fragment, "test"); + } + catch (PrepareFailedException ignore) { + } + descriptor.putUserData(CompilingEvaluator.COMPILING_EVALUATOR_DATA, data); + } + }, "asdjka", null); + } + catch (Exception ignore) {} + } myDebugProcess.getManagerThread().schedule(new DebuggerContextCommandImpl(myDebugProcess.getDebuggerContext()) { @Override public Priority getPriority() { @@ -67,8 +96,6 @@ public class JavaDebuggerEvaluator extends XDebuggerEvaluator { @Override public void threadAction() { - WatchItemDescriptor descriptor = new WatchItemDescriptor(myDebugProcess.getProject(), TextWithImportsImpl.fromXExpression( - expression)); EvaluationContextImpl evalContext = myStackFrame.getFrameDebuggerContext().createEvaluationContext(); if (evalContext == null) { callback.errorOccurred("Context is not available"); diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/AnonymousClassException.java b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/AnonymousClassException.java new file mode 100644 index 000000000000..c0e6adee1997 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/AnonymousClassException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.debugger.engine.evaluation.expression; + +import com.intellij.debugger.DebuggerBundle; +import com.intellij.debugger.engine.evaluation.EvaluateException; + +/** + * @author egor + */ +public class AnonymousClassException extends EvaluateException { + private static final AnonymousClassException INSTANCE = new AnonymousClassException(); + + public AnonymousClassException() { + super(DebuggerBundle.message("evaluation.error.anonymous.class.evaluation.not.supported")); + } + + public static AnonymousClassException getInstance() { + return INSTANCE; + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/EvaluatorBuilderImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/EvaluatorBuilderImpl.java index 16ff0aec4b37..f7176bd7e750 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/EvaluatorBuilderImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/EvaluatorBuilderImpl.java @@ -1146,7 +1146,7 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder { else if (expressionPsiType instanceof PsiClassType){ // must be a class ref PsiClass aClass = ((PsiClassType)expressionPsiType).resolve(); if(aClass instanceof PsiAnonymousClass) { - throwEvaluateException(DebuggerBundle.message("evaluation.error.anonymous.class.evaluation.not.supported")); + throw new EvaluateRuntimeException(AnonymousClassException.getInstance()); } PsiExpressionList argumentList = expression.getArgumentList(); if (argumentList == null) { diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/CompilingEvaluator.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/CompilingEvaluator.java index 042216c40f05..698412203319 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/CompilingEvaluator.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/CompilingEvaluator.java @@ -15,28 +15,55 @@ */ package com.intellij.debugger.ui.impl.watch; +import com.intellij.codeInsight.CodeInsightUtil; +import com.intellij.debugger.DebuggerInvocationUtil; +import com.intellij.debugger.EvaluatingComputable; +import com.intellij.debugger.engine.ContextUtil; import com.intellij.debugger.engine.DebugProcess; -import com.intellij.debugger.engine.evaluation.EvaluateException; -import com.intellij.debugger.engine.evaluation.EvaluationContext; -import com.intellij.debugger.engine.evaluation.TextWithImports; +import com.intellij.debugger.engine.evaluation.*; import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator; import com.intellij.debugger.engine.evaluation.expression.Modifier; +import com.intellij.debugger.impl.DebuggerUtilsEx; import com.intellij.debugger.jdi.VirtualMachineProxyImpl; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.refactoring.extractMethodObject.ExtractLightMethodObjectHandler; import com.sun.jdi.*; import javax.tools.*; import java.io.ByteArrayOutputStream; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Locale; /** * @author egor */ -class CompilingEvaluator implements ExpressionEvaluator { +public class CompilingEvaluator implements ExpressionEvaluator { private final TextWithImports myText; + private final PsiCodeFragment myCodeFragment; + private final PsiElement myPsiContext; + private final ExtractLightMethodObjectHandler.ExtractedData myData; - public CompilingEvaluator(TextWithImports text) { + public static Key COMPILING_EVALUATOR_DATA = new Key("COMPILING_EVALUATOR_DATA"); + private final EvaluationDescriptor myDescriptor; + + public CompilingEvaluator(TextWithImports text, + PsiCodeFragment codeFragment, + PsiElement context, + ExtractLightMethodObjectHandler.ExtractedData data, + EvaluationDescriptor descriptor) { myText = text; + myCodeFragment = codeFragment; + myPsiContext = context; + myData = data; + myDescriptor = descriptor; } @Override @@ -49,20 +76,44 @@ class CompilingEvaluator implements ExpressionEvaluator { return null; } - @Override - public Value evaluate(EvaluationContext context) throws EvaluateException { - try { - DebugProcess process = context.getDebugProcess(); - ThreadReference threadReference = context.getSuspendContext().getThread().getThreadReference(); + private TextWithImports getCallCode() { + return new TextWithImportsImpl(CodeFragmentKind.CODE_BLOCK, myData.getGeneratedCallText()); + } - ClassLoaderReference classLoader = getClassLoader(context); + @Override + public Value evaluate(final EvaluationContext evaluationContext) throws EvaluateException { + try { + DebugProcess process = evaluationContext.getDebugProcess(); + ThreadReference threadReference = evaluationContext.getSuspendContext().getThread().getThreadReference(); + + ClassLoaderReference classLoader = getClassLoader(evaluationContext); Collection classes = compile(); - ClassType mainClass = defineClasses(classes, context, process, threadReference, classLoader); + ClassType mainClass = defineClasses(classes, evaluationContext, process, threadReference, classLoader); - Method foo = mainClass.methodsByName(GEN_METHOD_NAME).get(0); - return mainClass.invokeMethod(threadReference, foo, Collections.emptyList() ,ClassType.INVOKE_SINGLE_THREADED); + //Method foo = mainClass.methodsByName(GEN_METHOD_NAME).get(0); + //return mainClass.invokeMethod(threadReference, foo, Collections.emptyList() ,ClassType.INVOKE_SINGLE_THREADED); + + // invoke base evaluator on call code + final Project project = myPsiContext.getProject(); + ExpressionEvaluator evaluator = + DebuggerInvocationUtil.commitAndRunReadAction(project, new EvaluatingComputable() { + @Override + public ExpressionEvaluator compute() throws EvaluateException { + final TextWithImports callCode = getCallCode(); + PsiFile file = myData.getGeneratedInnerClass().getContainingFile(); + final TextRange range = myPsiContext.getTextRange(); + final PsiElement copyContext = + CodeInsightUtil.findElementInRange(file, range.getStartOffset(), range.getEndOffset(), myPsiContext.getClass()); + final CodeFragmentFactory factory = DebuggerUtilsEx.findAppropriateCodeFragmentFactory(callCode, copyContext); + return factory.getEvaluatorBuilder(). + build(factory.createCodeFragment(callCode, copyContext, project), + ContextUtil.getSourcePosition(evaluationContext)); + } + }); + ((EvaluationContextImpl)evaluationContext).setClassLoader(classLoader); + return evaluator.evaluate(evaluationContext); } catch (Exception e) { throw new EvaluateException(e.getMessage()); @@ -74,13 +125,13 @@ class CompilingEvaluator implements ExpressionEvaluator { // TODO: cache DebugProcess process = context.getDebugProcess(); ClassType loaderClass = (ClassType)process.findClass(context, "java.net.URLClassLoader", context.getClassLoader()); - Method ctorMethod = loaderClass.concreteMethodByName("", "([Ljava/net/URL;)V"); + Method ctorMethod = loaderClass.concreteMethodByName("", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V"); ThreadReference threadReference = context.getSuspendContext().getThread().getThreadReference(); return (ClassLoaderReference)loaderClass.newInstance(threadReference, ctorMethod, - Arrays.asList(createURLArray(context)), ClassType.INVOKE_SINGLE_THREADED); + Arrays.asList(createURLArray(context), context.getClassLoader()), ClassType.INVOKE_SINGLE_THREADED); } - private static ClassType defineClasses(Collection classes, + private ClassType defineClasses(Collection classes, EvaluationContext context, DebugProcess process, ThreadReference threadReference, @@ -98,7 +149,7 @@ class CompilingEvaluator implements ExpressionEvaluator { args.add(proxy.mirrorOf(bytes.length)); classLoader.invokeMethod(threadReference, defineMethod, args, ClassType.INVOKE_SINGLE_THREADED); } - return (ClassType)process.findClass(context, GEN_CLASS_FULL_NAME, classLoader); + return (ClassType)process.findClass(context, getGenPackageName() + '.' + getGenClassName(), classLoader); } private static ArrayReference mirrorOf(byte[] bytes, EvaluationContext context, DebugProcess process) @@ -112,10 +163,73 @@ class CompilingEvaluator implements ExpressionEvaluator { return reference; } - private static final String GEN_CLASS_NAME = "Evaluator"; + private static final String GEN_CLASS_NAME = "Test"; private static final String GEN_CLASS_PACKAGE = "dummy"; private static final String GEN_CLASS_FULL_NAME = GEN_CLASS_PACKAGE + '.' + GEN_CLASS_NAME; - private static final String GEN_METHOD_NAME = "eval"; + private static final String GEN_METHOD_NAME = "invoke"; + + private String getClassCode() { + if (myData != null) { + return ApplicationManager.getApplication().runReadAction(new Computable() { + @Override + public String compute() { + //String text = myData.getGeneratedInnerClass().getText(); + ////TODO: remove + //String prefix = "public static"; + //if (text.startsWith(prefix)) { + // text = "public" + text.substring(prefix.length()); + //} + //PsiElement[] children = ((PsiJavaFile)myPsiContext.getContainingFile()).getImportList().getChildren(); + //StringBuilder imports = new StringBuilder(); + //for (PsiElement child : children) { + // if (child instanceof PsiImportStatement) { + // String name = ((PsiImportStatement)child).getImportReference().getQualifiedName(); + // imports.append("import ").append(name).append(";"); + // } + //} + //text = text.replace("class " + GEN_CLASS_NAME, "class " + getGenClassName()); + //text = text.replace(GEN_CLASS_NAME + "(", getGenClassName() + "("); + //text = text.replace(((PsiClass)myData.getGeneratedInnerClass().getParent()).getName() + "." + GEN_CLASS_NAME, getGenClassName()); + //return "package " + getGenPackageName() + "; " + imports.toString() + text; + return myData.getGeneratedInnerClass().getContainingFile().getText(); + } + }); + } + return null; + } + + private String getGenPackageName() { + return ApplicationManager.getApplication().runReadAction(new Computable() { + @Override + public String compute() { + return ((PsiJavaFile)myData.getGeneratedInnerClass().getContainingFile()).getPackageName(); + } + }); + } + + private String getMainClassName() { + return ((PsiClass)myData.getGeneratedInnerClass().getParent()).getName(); + } + + private String getGenClassName() { + return getMainClassName() + '$' + GEN_CLASS_NAME; + } + + //private String createClassCode() { + // return ApplicationManager.getApplication().runReadAction(new Computable() { + // @Override + // public String compute() { + // try { + // myExtractedData = + // ExtractLightMethodObjectHandler.extractLightMethodObject(myCodeFragment.getProject(), myFile , myCodeFragment, "test"); + // } + // catch (PrepareFailedException e) { + // e.printStackTrace(); + // } + // return null; + // } + // }); + //} private static String createClassCode(TextWithImports body) { StringBuilder text = new StringBuilder(); @@ -155,7 +269,7 @@ class CompilingEvaluator implements ExpressionEvaluator { MemoryFileManager manager = new MemoryFileManager(compiler); DiagnosticCollector diagnostic = new DiagnosticCollector(); if (!compiler.getTask(null, manager, diagnostic, null, null, Arrays - .asList(new SourceFileObject(GEN_CLASS_NAME, JavaFileObject.Kind.SOURCE, createClassCode(myText)))).call()) { + .asList(new SourceFileObject(getMainClassName(), JavaFileObject.Kind.SOURCE, getClassCode()))).call()) { // TODO: show only errors throw new EvaluateException(diagnostic.getDiagnostics().get(0).getMessage(Locale.getDefault())); } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/EvaluationDescriptor.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/EvaluationDescriptor.java index 56c4c1653881..1fb6467429fe 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/EvaluationDescriptor.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/EvaluationDescriptor.java @@ -25,6 +25,7 @@ import com.intellij.debugger.engine.evaluation.EvaluateException; import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil; import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; import com.intellij.debugger.engine.evaluation.TextWithImports; +import com.intellij.debugger.engine.evaluation.expression.AnonymousClassException; import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator; import com.intellij.debugger.engine.evaluation.expression.Modifier; import com.intellij.debugger.impl.DebuggerUtilsEx; @@ -38,6 +39,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiExpressionCodeFragment; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.refactoring.extractMethodObject.ExtractLightMethodObjectHandler; import com.sun.jdi.ObjectCollectedException; import com.sun.jdi.ObjectReference; import com.sun.jdi.Value; @@ -64,7 +66,7 @@ public abstract class EvaluationDescriptor extends ValueDescriptorImpl{ protected abstract PsiCodeFragment getEvaluationCode(StackFrameContext context) throws EvaluateException; - protected PsiCodeFragment createCodeFragment(PsiElement context) { + public PsiCodeFragment createCodeFragment(PsiElement context) { TextWithImports text = getEvaluationText(); final PsiCodeFragment fragment = DebuggerUtilsEx.findAppropriateCodeFragmentFactory(text, context).createCodeFragment(text, context, myProject); @@ -76,11 +78,9 @@ public abstract class EvaluationDescriptor extends ValueDescriptorImpl{ try { final EvaluationContextImpl thisEvaluationContext = getEvaluationContext(evaluationContext); - final ExpressionEvaluator evaluator; - if (Registry.is("debugger.compiling.evaluator")) { - evaluator = new CompilingEvaluator(getEvaluationText()); - } - else { + ExpressionEvaluator evaluator; + final ExtractLightMethodObjectHandler.ExtractedData data = getUserData(CompilingEvaluator.COMPILING_EVALUATOR_DATA); + try { evaluator = DebuggerInvocationUtil.commitAndRunReadAction(myProject, new EvaluatingComputable() { public ExpressionEvaluator compute() throws EvaluateException { final PsiElement psiContext = PositionUtil.getContextElement(evaluationContext); @@ -89,6 +89,19 @@ public abstract class EvaluationDescriptor extends ValueDescriptorImpl{ } }); } + catch (AnonymousClassException ex) { + if (Registry.is("debugger.compiling.evaluator") && data != null) { + evaluator = DebuggerInvocationUtil.commitAndRunReadAction(myProject, new EvaluatingComputable() { + public ExpressionEvaluator compute() throws EvaluateException { + final PsiElement psiContext = PositionUtil.getContextElement(evaluationContext); + return new CompilingEvaluator(getEvaluationText(), getEvaluationCode(thisEvaluationContext), psiContext, data, EvaluationDescriptor.this); + } + }); + } + else { + throw ex; + } + } if (!thisEvaluationContext.getDebugProcess().isAttached()) { throw EvaluateExceptionUtil.PROCESS_EXITED;