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;
+	}
+}