Bug 503435: [otdre][impl] use ASM's CheckClassAdapter to harden the
transformer implementation
- fix error regarding AccTeam
- fix error re accing inexistent local "boundMethodId"
diff --git a/plugins/org.eclipse.objectteams.otdt/src/org/eclipse/objectteams/otdt/core/ext/OTREContainer.java b/plugins/org.eclipse.objectteams.otdt/src/org/eclipse/objectteams/otdt/core/ext/OTREContainer.java
index 5a0f6d9..3c9eae7 100644
--- a/plugins/org.eclipse.objectteams.otdt/src/org/eclipse/objectteams/otdt/core/ext/OTREContainer.java
+++ b/plugins/org.eclipse.objectteams.otdt/src/org/eclipse/objectteams/otdt/core/ext/OTREContainer.java
@@ -255,7 +255,8 @@
}
ASM : {
int asm = WeavingScheme.OTDRE.ordinal();
- BYTECODE_WEAVER_PATHS[asm] = new IPath[ASM_BUNDLE_NAMES.length+1];
+ int numBundles = ASM_BUNDLE_NAMES.length+1;
+ BYTECODE_WEAVER_PATHS[asm] = new IPath[numBundles];
int i = 0;
BYTECODE_WEAVER_PATHS[asm][i++] = new Path(OTVariableInitializer.getInstallatedPath(OTDTPlugin.getDefault(), OTDRE_PLUGIN_NAME, "bin")); //$NON-NLS-1$
for (String bundleName : ASM_BUNDLE_NAMES) {
@@ -267,7 +268,7 @@
break;
}
}
- if (i == 4)
+ if (i == numBundles)
break ASM;
throw new RuntimeException("bytecode libarary for OTDRE not found"); //$NON-NLS-1$
}
diff --git a/plugins/org.eclipse.objectteams.otredyn/.classpath b/plugins/org.eclipse.objectteams.otredyn/.classpath
index ad32c83..098194c 100644
--- a/plugins/org.eclipse.objectteams.otredyn/.classpath
+++ b/plugins/org.eclipse.objectteams.otredyn/.classpath
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
diff --git a/plugins/org.eclipse.objectteams.otredyn/.settings/org.eclipse.jdt.core.prefs b/plugins/org.eclipse.objectteams.otredyn/.settings/org.eclipse.jdt.core.prefs
index fd5749d..aa1e992 100644
--- a/plugins/org.eclipse.objectteams.otredyn/.settings/org.eclipse.jdt.core.prefs
+++ b/plugins/org.eclipse.objectteams.otredyn/.settings/org.eclipse.jdt.core.prefs
@@ -6,9 +6,9 @@
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@@ -97,5 +97,5 @@
org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
-org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.compiler.source=1.7
org.eclipse.objectteams.otdt.compiler.option.pure_java=enabled
diff --git a/plugins/org.eclipse.objectteams.otredyn/META-INF/MANIFEST.MF b/plugins/org.eclipse.objectteams.otredyn/META-INF/MANIFEST.MF
index 218129a..fc3bc22 100644
--- a/plugins/org.eclipse.objectteams.otredyn/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.objectteams.otredyn/META-INF/MANIFEST.MF
@@ -11,7 +11,7 @@
org.eclipse.objectteams.otredyn.transformer.jplis,
org.eclipse.objectteams.otredyn.transformer.names,
org.eclipse.objectteams.otredyn.util
-Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Bundle-ClassPath: .
Require-Bundle: org.eclipse.objectteams.runtime;bundle-version="[2.5.0,3.0.0)",
org.objectweb.asm;bundle-version="5.0.1",
diff --git a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/AbstractTransformableClassNode.java b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/AbstractTransformableClassNode.java
index 1673139..e235886 100644
--- a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/AbstractTransformableClassNode.java
+++ b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/AbstractTransformableClassNode.java
@@ -32,6 +32,7 @@
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
+import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
@@ -389,6 +390,45 @@
}
/**
+ * Add a local variable to be visible throughout the all of the instruction list.
+ * Side effect: may add labels at beginning and end, unless labels are already present at these locations.
+ */
+ protected void addLocal(MethodNode method, String selector, String desc, int slot) {
+ InsnList instructions = method.instructions;
+ LabelNode start, end;
+ if (instructions.getFirst() instanceof LabelNode) {
+ start = (LabelNode) instructions.getFirst();
+ } else {
+ start = new LabelNode();
+ instructions.insert(start);
+ }
+ if (instructions.getLast() instanceof LabelNode) {
+ end = (LabelNode) instructions.getLast();
+ } else {
+ end = new LabelNode();
+ instructions.add(end);
+ }
+ addLocal(method, selector, desc, start, end, slot);
+ }
+
+ /**
+ * Add a local variable to be visible from 'start' to 'end'.
+ * Checks whether a local variable of that name already exists, in which case we don't change anything.
+ * TODO: should check if ranges of both variables overlap!
+ */
+ protected void addLocal(MethodNode method, String selector, String desc, LabelNode start, LabelNode end, int slot) {
+ for (Object lv : method.localVariables) {
+ if (((LocalVariableNode)lv).name.equals(selector))
+ return;
+ }
+ method.localVariables.add(new LocalVariableNode(selector, desc, null, start, end, slot));
+ }
+
+ protected void addThisVariable(MethodNode method) {
+ addLocal(method, "this", "L"+this.name+";", 0);
+ }
+
+ /**
* In this method, concrete Implementations of this class
* can manipulate the bytecode
* @return whether transformation actually happened
diff --git a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/AsmWritableBoundClass.java b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/AsmWritableBoundClass.java
index 7b32cc1..2a110a7 100644
--- a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/AsmWritableBoundClass.java
+++ b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/AsmWritableBoundClass.java
@@ -16,11 +16,9 @@
**********************************************************************/
package org.eclipse.objectteams.otredyn.bytecode.asm;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -30,6 +28,7 @@
import org.eclipse.objectteams.otredyn.bytecode.IBytecodeProvider;
import org.eclipse.objectteams.otredyn.bytecode.Method;
import org.eclipse.objectteams.otredyn.bytecode.RedefineStrategyFactory;
+import org.eclipse.objectteams.otredyn.bytecode.asm.verify.OTCheckClassAdapter;
import org.eclipse.objectteams.otredyn.runtime.TeamManager;
import org.eclipse.objectteams.otredyn.transformer.names.ClassNames;
import org.eclipse.objectteams.otredyn.transformer.names.ConstantMembers;
@@ -37,7 +36,6 @@
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
-import org.objectweb.asm.util.CheckClassAdapter;
/**
* This class implements the bytecode manipulating part of {@link AbstractBoundClass}.
@@ -172,23 +170,7 @@
byte[] bytes = writer.toByteArray();
setBytecode(bytes);
if (verifying) {
- final String [] prints = new String[1];
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- CheckClassAdapter.verify(new ClassReader(bytes), this.loader, false, new PrintWriter(out) {
- public void println(String x) {
- super.println(x);
- prints[0] = x;
- }
- });
- if (prints[0] != null) {
- StringBuilder message = new StringBuilder();
- message.append(node.getClass().getSimpleName());
- message.append(" caused a verify error on ");
- message.append(this.getName()).append('.').append(prints[0]);
- message.append('\n');
- message.append(out.toString());
- throw new VerifyError(message.toString());
- }
+ OTCheckClassAdapter.verify(node, bytes, this.loader);
}
}
}
diff --git a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/MoveCodeToCallOrigAdapter.java b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/MoveCodeToCallOrigAdapter.java
index ad16013..5161b05 100644
--- a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/MoveCodeToCallOrigAdapter.java
+++ b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/MoveCodeToCallOrigAdapter.java
@@ -1,7 +1,7 @@
/**********************************************************************
* This file is part of "Object Teams Dynamic Runtime Environment"
*
- * Copyright 2009, 2015 Oliver Frank and others.
+ * Copyright 2009, 2016 Oliver Frank and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -31,6 +31,7 @@
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
+import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
@@ -43,6 +44,9 @@
* @author Oliver Frank
*/
public class MoveCodeToCallOrigAdapter extends AbstractTransformableClassNode {
+
+ private static final String BOUND_METHOD_ID = "boundMethodId";
+
private Method method;
private int boundMethodId;
private int firstArgIndex; // slot index of the first argument (0 (static) or 1 (non-static))
@@ -84,12 +88,15 @@
//Unboxing arguments
Type[] args = Type.getArgumentTypes(orgMethod.desc);
+ LabelNode start = new LabelNode(), end = new LabelNode(); // range for new local variables
+ newInstructions.add(start);
int boundMethodIdSlot = firstArgIndex;
if (args.length > 0) {
// move boundMethodId to a higher slot, to make lower slots available for original locals
newInstructions.add(new IntInsnNode(Opcodes.ILOAD, boundMethodIdSlot));
boundMethodIdSlot = orgMethod.maxLocals+1;
+ addLocal(callOrig, BOUND_METHOD_ID, "I", start, end, boundMethodIdSlot);
newInstructions.add(new IntInsnNode(Opcodes.ISTORE, boundMethodIdSlot));
newInstructions.add(new IntInsnNode(Opcodes.ALOAD, firstArgIndex + argOffset + 1));
@@ -132,6 +139,7 @@
if (orgMethod.localVariables != null) {
orgMethod.localVariables.clear();
}
+ newInstructions.add(end);
addNewLabelToSwitch(callOrig.instructions, newInstructions, boundMethodId);
diff --git a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/verify/OTCheckClassAdapter.java b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/verify/OTCheckClassAdapter.java
new file mode 100644
index 0000000..1396f63
--- /dev/null
+++ b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/verify/OTCheckClassAdapter.java
@@ -0,0 +1,267 @@
+/**********************************************************************
+ * This file is part of "Object Teams Dynamic Runtime Environment"
+ *
+ * Copyright 2016 GK Software AG
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Please visit http://www.eclipse.org/objectteams for updates and contact.
+ *
+ * Contributors:
+ * Stephan Herrmann - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.objectteams.otredyn.bytecode.asm.verify;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.analysis.Analyzer;
+import org.objectweb.asm.tree.analysis.BasicValue;
+import org.objectweb.asm.tree.analysis.Frame;
+import org.objectweb.asm.tree.analysis.SimpleVerifier;
+import org.objectweb.asm.util.CheckClassAdapter;
+import org.objectweb.asm.util.Textifier;
+import org.objectweb.asm.util.TraceMethodVisitor;
+
+public class OTCheckClassAdapter extends org.objectweb.asm.util.CheckClassAdapter {
+
+ static final int AccTeam = 0x8000;
+
+ /**
+ * A print writer that captures the last argument to {@link PrintWriter#println(String)}.
+ */
+ static class CapturingPrintWriter extends PrintWriter {
+ String errorText;
+
+ private CapturingPrintWriter(OutputStream out) {
+ super(out);
+ }
+
+ public void println(String x) {
+ super.println(x);
+ this.errorText = x;
+ }
+ }
+
+
+ /**
+ * A class loader that can return already loaded classes,
+ * but instead of loading new classes it throws {@link LoadAttempted}.
+ */
+ static class ShyLoader extends ClassLoader {
+
+ @SuppressWarnings("serial")
+ static class LoadAttempted extends RuntimeException { }
+
+ Method findLoadedClass;
+
+ public ShyLoader(ClassLoader parent) {
+ super(parent);
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ try {
+ Class<?> loaded = findLoadedFromParent(name); // don't attempt real class loading!
+ if (loaded != null)
+ return loaded;
+ } catch (Exception e) {
+ }
+ throw new LoadAttempted();
+ }
+
+ Class<?> findLoadedFromParent(String name) throws Exception {
+ if (findLoadedClass == null) {
+ findLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
+ findLoadedClass.setAccessible(true);
+ }
+ ClassLoader parent = getParent();
+ while (parent != null) {
+ Class<?> c = (Class<?>) findLoadedClass.invoke(parent, name);
+ if (c != null)
+ return c;
+ parent = parent.getParent();
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Use the correct class loader for verification (wrapped in a ShyLoader to only find loaded classes).
+ * Short-cut verification that would need real class loading.
+ */
+ static class OTVerifier extends SimpleVerifier {
+
+ ClassLoader loader;
+
+ public OTVerifier(Type objectType, Type syperType, List<Type> interfaces, boolean isInterface, ClassLoader loader) {
+ super(objectType, syperType, interfaces, isInterface);
+ this.loader = new ShyLoader(loader);
+ }
+
+ @Override
+ protected boolean isAssignableFrom(Type t, Type u) {
+ try {
+ return super.isAssignableFrom(t, u);
+ } catch (ShyLoader.LoadAttempted e) {
+ return true; // if we cannot verify without loading classes, just answer 'true' for now
+ }
+ }
+
+ /** Same as super, but use our local ClassLoader. */
+ @Override
+ protected Class<?> getClass(final Type t) {
+ try {
+ if (t.getSort() == Type.ARRAY) {
+ return Class.forName(t.getDescriptor().replace('/', '.'),
+ false, loader);
+ }
+ return Class.forName(t.getClassName(), false, loader);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+ }
+
+ public OTCheckClassAdapter(ClassVisitor cv, boolean checkDataFlow) {
+ super(Opcodes.ASM5, cv, checkDataFlow);
+ }
+
+ /**
+ * Invoke {@link CheckClassAdapter#verify(ClassReader, ClassLoader, boolean, PrintWriter)}
+ * set up to use an instance of {@link OTCheckClassAdapter}.
+ */
+ public static void verify(ClassNode node, byte[] bytes, ClassLoader loader) throws VerifyError {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try (CapturingPrintWriter printWriter = new CapturingPrintWriter(out)) {
+ verify(new ClassReader(bytes), loader, false, printWriter);
+ if (printWriter.errorText != null) {
+ StringBuilder message = new StringBuilder();
+ message.append(node.getClass().getSimpleName());
+ message.append(" caused a verify error on ");
+ message.append(node.name).append('.').append(printWriter.errorText);
+ message.append('\n');
+ message.append(out.toString());
+ throw new VerifyError(message.toString());
+ }
+ }
+ }
+
+ public static void verify(final ClassReader cr, final ClassLoader loader,
+ final boolean dump, final PrintWriter pw) {
+ ClassNode cn = new ClassNode();
+//{ObjectTeams: instantiate OTCheckClassAdapter:
+ cr.accept(new OTCheckClassAdapter(cn, false), ClassReader.SKIP_DEBUG);
+// SH}
+
+ Type syperType = cn.superName == null ? null : Type
+ .getObjectType(cn.superName);
+ List<MethodNode> methods = cn.methods;
+
+ List<Type> interfaces = new ArrayList<Type>();
+ for (Iterator<String> i = cn.interfaces.iterator(); i.hasNext();) {
+ interfaces.add(Type.getObjectType(i.next()));
+ }
+
+ for (int i = 0; i < methods.size(); ++i) {
+ MethodNode method = methods.get(i);
+//{ObjectTeams: instantiate OTVerifier and pass loader:
+ SimpleVerifier verifier = new OTVerifier(
+ Type.getObjectType(cn.name), syperType, interfaces,
+ (cn.access & Opcodes.ACC_INTERFACE) != 0,
+ loader);
+// SH}
+ Analyzer<BasicValue> a = new Analyzer<BasicValue>(verifier);
+ if (loader != null) {
+ verifier.setClassLoader(loader);
+ }
+ try {
+ a.analyze(cn.name, method);
+ if (!dump) {
+ continue;
+ }
+ } catch (Exception e) {
+ e.printStackTrace(pw);
+ }
+ printAnalyzerResult(method, a, pw);
+ }
+ pw.flush();
+ }
+
+
+ static void printAnalyzerResult(MethodNode method, Analyzer<BasicValue> a, final PrintWriter pw) {
+ Frame<BasicValue>[] frames = a.getFrames();
+ Textifier t = new Textifier();
+ TraceMethodVisitor mv = new TraceMethodVisitor(t);
+
+ pw.println(method.name + method.desc);
+ for (int j = 0; j < method.instructions.size(); ++j) {
+ method.instructions.get(j).accept(mv);
+
+ StringBuilder sb = new StringBuilder();
+ Frame<BasicValue> f = frames[j];
+ if (f == null) {
+ sb.append('?');
+ } else {
+ for (int k = 0; k < f.getLocals(); ++k) {
+ sb.append(getShortName(f.getLocal(k).toString()))
+ .append(' ');
+ }
+ sb.append(" : ");
+ for (int k = 0; k < f.getStackSize(); ++k) {
+ sb.append(getShortName(f.getStack(k).toString()))
+ .append(' ');
+ }
+ }
+ while (sb.length() < method.maxStack + method.maxLocals + 1) {
+ sb.append(' ');
+ }
+ pw.print(Integer.toString(j + 100000).substring(1));
+ pw.print(" " + sb + " : " + t.text.get(t.text.size() - 1));
+ }
+ for (int j = 0; j < method.tryCatchBlocks.size(); ++j) {
+ method.tryCatchBlocks.get(j).accept(mv);
+ pw.print(" " + t.text.get(t.text.size() - 1));
+ }
+ pw.println();
+ }
+
+ private static String getShortName(final String name) {
+ int n = name.lastIndexOf('/');
+ int k = name.length();
+ if (name.charAt(k - 1) == ';') {
+ k--;
+ }
+ return n == -1 ? name : name.substring(n + 1, k);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ // tolerate AccTeam in a class's access flags:
+ super.visit(version, adjustClassFlags(access), name, signature, superName, interfaces);
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ // tolerate AccTeam in a class's access flags:
+ super.visitInnerClass(name, outerName, innerName, adjustClassFlags(access));
+ }
+
+ protected int adjustClassFlags(int access) {
+ return access & ~AccTeam;
+ }
+}