blob: 467cf985f04cca22b0c8ccdbafa243265c5619a6 [file] [log] [blame]
/*
* 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.ui.impl.watch;
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.*;
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.psi.PsiClass;
import com.intellij.psi.PsiCodeFragment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiJavaFile;
import com.intellij.refactoring.extractMethodObject.ExtractLightMethodObjectHandler;
import com.sun.jdi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.org.objectweb.asm.ClassReader;
import org.jetbrains.org.objectweb.asm.ClassVisitor;
import org.jetbrains.org.objectweb.asm.ClassWriter;
import org.jetbrains.org.objectweb.asm.Opcodes;
import javax.tools.*;
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
/**
* @author egor
*/
public class CompilingEvaluator implements ExpressionEvaluator {
private final TextWithImports myText;
private final PsiCodeFragment myCodeFragment;
private final PsiElement myPsiContext;
@NotNull private final ExtractLightMethodObjectHandler.ExtractedData myData;
private final EvaluationDescriptor myDescriptor;
public CompilingEvaluator(TextWithImports text,
PsiCodeFragment codeFragment,
PsiElement context,
@NotNull ExtractLightMethodObjectHandler.ExtractedData data,
EvaluationDescriptor descriptor) {
myText = text;
myCodeFragment = codeFragment;
myPsiContext = context;
myData = data;
myDescriptor = descriptor;
}
@Override
public Value getValue() {
return null;
}
@Override
public Modifier getModifier() {
return null;
}
private TextWithImports getCallCode() {
return new TextWithImportsImpl(CodeFragmentKind.CODE_BLOCK, myData.getGeneratedCallText());
}
@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<OutputFileObject> classes = compile();
ClassType mainClass = defineClasses(classes, evaluationContext, process, threadReference, classLoader);
//Method foo = mainClass.methodsByName(GEN_METHOD_NAME).get(0);
//return mainClass.invokeMethod(threadReference, foo, Collections.<Value>emptyList() ,ClassType.INVOKE_SINGLE_THREADED);
// invoke base evaluator on call code
final Project project = myPsiContext.getProject();
ExpressionEvaluator evaluator =
DebuggerInvocationUtil.commitAndRunReadAction(project, new EvaluatingComputable<ExpressionEvaluator>() {
@Override
public ExpressionEvaluator compute() throws EvaluateException {
final TextWithImports callCode = getCallCode();
PsiElement copyContext = myData.getAnchor();
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());
}
}
private static ClassLoaderReference getClassLoader(EvaluationContext context)
throws EvaluateException, InvocationException, InvalidTypeException, ClassNotLoadedException, IncompatibleThreadStateException {
// TODO: cache
DebugProcess process = context.getDebugProcess();
ClassType loaderClass = (ClassType)process.findClass(context, "java.net.URLClassLoader", context.getClassLoader());
Method ctorMethod = loaderClass.concreteMethodByName("<init>", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V");
ThreadReference threadReference = context.getSuspendContext().getThread().getThreadReference();
return (ClassLoaderReference)loaderClass.newInstance(threadReference, ctorMethod,
Arrays.asList(createURLArray(context), context.getClassLoader()), ClassType.INVOKE_SINGLE_THREADED);
}
private ClassType defineClasses(Collection<OutputFileObject> classes,
EvaluationContext context,
DebugProcess process,
ThreadReference threadReference,
ClassLoaderReference classLoader)
throws EvaluateException, InvalidTypeException, ClassNotLoadedException, IncompatibleThreadStateException, InvocationException {
VirtualMachineProxyImpl proxy = (VirtualMachineProxyImpl)process.getVirtualMachineProxy();
for (OutputFileObject cls : classes) {
if (cls.getName().contains(getGenClassName())) {
Method defineMethod =
((ClassType)classLoader.referenceType()).concreteMethodByName("defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;");
byte[] bytes = changeSuperToMagicAccessor(cls.toByteArray());
ArrayList<Value> args = new ArrayList<Value>();
args.add(proxy.mirrorOf(cls.myOrigName));
args.add(mirrorOf(bytes, context, process));
args.add(proxy.mirrorOf(0));
args.add(proxy.mirrorOf(bytes.length));
classLoader.invokeMethod(threadReference, defineMethod, args, ClassType.INVOKE_SINGLE_THREADED);
}
}
return (ClassType)process.findClass(context, getGenClassFullName(), classLoader);
}
private static byte[] changeSuperToMagicAccessor(byte[] bytes) {
ClassWriter classWriter = new ClassWriter(0);
ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM5, classWriter) {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
if ("java/lang/Object".equals(superName)) {
superName = "sun/reflect/MagicAccessorImpl";
}
super.visit(version, access, name, signature, superName, interfaces);
}
};
new ClassReader(bytes).accept(classVisitor, 0);
return classWriter.toByteArray();
}
private static ArrayReference mirrorOf(byte[] bytes, EvaluationContext context, DebugProcess process)
throws EvaluateException, InvalidTypeException, ClassNotLoadedException {
ArrayType arrayClass = (ArrayType)process.findClass(context, "byte[]", context.getClassLoader());
ArrayReference reference = process.newInstance(arrayClass, bytes.length);
reference.disableCollection();
for (int i = 0; i < bytes.length; i++) {
reference.setValue(i, ((VirtualMachineProxyImpl)process.getVirtualMachineProxy()).mirrorOf(bytes[i]));
}
return reference;
}
public static String getGeneratedClassName() {
return GEN_CLASS_NAME;
}
private static final String GEN_CLASS_NAME = "GeneratedEvaluationClass";
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 = "invoke";
private String getClassCode() {
if (myData != null) {
return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@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<String>() {
@Override
public String compute() {
return ((PsiJavaFile)myData.getGeneratedInnerClass().getContainingFile()).getPackageName();
}
});
}
private String getMainClassName() {
return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@Override
public String compute() {
return ((PsiClass)myData.getGeneratedInnerClass().getParent()).getName();
}
});
}
private String getGenClassName() {
return getMainClassName() + '$' + GEN_CLASS_NAME;
}
private String getGenClassFullName() {
String packageName = getGenPackageName();
if (packageName.isEmpty()) {
return getGenClassName();
}
return packageName + '.' + getGenClassName();
}
//private String createClassCode() {
// return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
// @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();
text.append("package " + GEN_CLASS_PACKAGE + ";");
String imports = body.getImports();
if (!imports.isEmpty()) {
for (String s : imports.split(",")) {
text.append("import " + s + ";");
}
}
String bodyText = body.getText();
if (!bodyText.endsWith(";")) {
bodyText += ';';
}
text.append("public class " + GEN_CLASS_NAME + " { public static Object " + GEN_METHOD_NAME + "() throws Exception {" + bodyText + "}}");
return text.toString();
}
private static ArrayReference createURLArray(EvaluationContext context)
throws EvaluateException, InvocationException, InvalidTypeException, ClassNotLoadedException, IncompatibleThreadStateException {
DebugProcess process = context.getDebugProcess();
ArrayType arrayType = (ArrayType)process.findClass(context, "java.net.URL[]", context.getClassLoader());
ArrayReference arrayRef = arrayType.newInstance(1);
ClassType classType = (ClassType)process.findClass(context, "java.net.URL", context.getClassLoader());
VirtualMachineProxyImpl proxy = (VirtualMachineProxyImpl)process.getVirtualMachineProxy();
ThreadReference threadReference = context.getSuspendContext().getThread().getThreadReference();
ObjectReference reference = classType.newInstance(threadReference, classType.concreteMethodByName("<init>", "(Ljava/lang/String;)V"),
Arrays.asList(proxy.mirrorOf("file:a")), ClassType.INVOKE_SINGLE_THREADED);
arrayRef.setValues(Arrays.asList(reference));
return arrayRef;
}
///////////////// Compiler stuff
private Collection<OutputFileObject> compile() throws EvaluateException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
MemoryFileManager manager = new MemoryFileManager(compiler);
DiagnosticCollector<JavaFileObject> diagnostic = new DiagnosticCollector<JavaFileObject>();
if (!compiler.getTask(null, manager, diagnostic, null, null, Arrays
.asList(new SourceFileObject(getMainClassName(), JavaFileObject.Kind.SOURCE, getClassCode()))).call()) {
// TODO: show only errors
throw new EvaluateException(diagnostic.getDiagnostics().get(0).toString());
}
return manager.classes;
}
private static URI getUri(String name, JavaFileObject.Kind kind) {
return URI.create("memo:///" + name.replace('.', '/') + kind.extension);
}
private static class SourceFileObject extends SimpleJavaFileObject {
private final String myContent;
SourceFileObject(String name, Kind kind, String content) {
super(getUri(name, kind), kind);
myContent = content;
}
@Override
public CharSequence getCharContent(boolean ignore) {
return myContent;
}
}
private static class OutputFileObject extends SimpleJavaFileObject {
private final ByteArrayOutputStream myStream = new ByteArrayOutputStream();
private final String myOrigName;
OutputFileObject(String name, Kind kind) {
super(getUri(name, kind), kind);
myOrigName = name;
}
byte[] toByteArray() {
return myStream.toByteArray();
}
@Override
public ByteArrayOutputStream openOutputStream() {
return myStream;
}
}
private static class MemoryFileManager extends ForwardingJavaFileManager {
private final Collection<OutputFileObject> classes = new ArrayList<OutputFileObject>();
MemoryFileManager(JavaCompiler compiler) {
super(compiler.getStandardFileManager(null, null, null));
}
@Override
public OutputFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject source) {
OutputFileObject mc = new OutputFileObject(name, kind);
classes.add(mc);
return mc;
}
}
}