Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
...
  • 4 commits
  • 3 files changed
  • 0 commit comments
  • 2 contributors
Commits on Jul 23, 2012
@istudens istudens add injectOnExit, make installScript method public dc5b34b
Commits on Jul 24, 2012
@adinn fixed BYTEMAN-211
Modified non-loading class writer so it really does compute the lowest
common superclass of two classes on demand rather than simply
returning java/lang/Object. it does this without actually loading any
classes which means in rare cases it may not be able to find a super.
69958cb
Commits on Jul 25, 2012
@adinn Merge pull request #15 from istudens/dtest_patch
BYTEMAN-213: add injectOnExit, make installScript method public
92a4704
@adinn Merge branch 'master' of github.com:bytemanproject/byteman f6ada62
View
176 agent/src/main/java/org/jboss/byteman/agent/TransformContext.java
@@ -28,6 +28,7 @@
import org.jboss.byteman.agent.adapter.BMLocalScopeAdapter;
import org.jboss.byteman.agent.adapter.RuleCheckAdapter;
import org.jboss.byteman.agent.adapter.RuleTriggerAdapter;
+import org.jboss.byteman.agent.check.ClassChecker;
import org.jboss.byteman.rule.exception.CompileException;
import org.jboss.byteman.rule.exception.ParseException;
import org.jboss.byteman.rule.exception.TypeException;
@@ -39,8 +40,10 @@
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
+import java.util.ArrayList;
import java.util.HashMap;
-import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
/**
* Class used to localise the context information employed when creating a rule from a rule script and
@@ -48,7 +51,7 @@
*/
public class TransformContext
{
- public TransformContext(RuleScript ruleScript, String triggerClassName, ClassLoader loader, HelperManager helperManager)
+ public TransformContext(Transformer transformer, RuleScript ruleScript, String triggerClassName, ClassLoader loader, HelperManager helperManager)
{
// the target method spec may just be a bare method name or it may optionally include a
// parameter type list and a return type. With Java syntax the return type appears before
@@ -57,6 +60,7 @@ public TransformContext(RuleScript ruleScript, String triggerClassName, ClassLoa
// parseMethodDescriptor call below will eat specs in this latter format.
final String targetMethodSpec = ruleScript.getTargetMethod();
String mungedMethodSpec = mungeMethodSpecReturnType(targetMethodSpec);
+ this.transformer = transformer;
this.ruleScript = ruleScript;
this.triggerClassName = triggerClassName;
this.targetMethodName = TypeHelper.parseMethodName(mungedMethodSpec);
@@ -399,24 +403,184 @@ private String mungeMethodSpecReturnType(String targetMethodSpec)
}
/**
- * get a class writer which will not attempt to load classes.The default classwriter tries this when a
+ * get a class writer which will not attempt to load classes. The default classwriter tries this when a
* reference type local var frame slot aligns with a slot of reference type in a successor block's
* frame. This is merely so it can optimize a slot out of the frame change set in the special case where
- * f1[slot].type < f2[slot].type or vice versa by using whichever is the maximal class. We avoid classloading
- * by returning class Object.
+ * f1[slot].type < f2[slot].type or vice versa by using the least common supereclass. We have to use
+ * the Transformer to reuse existing loaded classes and, where a class has not been loaded, to
+ * attempt to load the bytecode as a resource and identify supers via the bytecode.
+ *
* @param flags
* @return
*/
private ClassWriter getNonLoadingClassWriter(int flags)
{
+ final TransformContext finalContext = this;
return new ClassWriter(flags) {
+ TransformContext context = finalContext;
protected String getCommonSuperClass(final String type1, final String type2) {
// if we always return Object we cannot go wrong
- return "java/lang/Object";
+ return context.findLeastCommonSuper(type1, type2);
}
};
}
+ public final static String TOFU = "java/lang/Object"; // TOFU = top of universe
+
+ public String findLeastCommonSuper(final String t1, final String t2)
+ {
+ // check the simple cases first
+
+ if (TOFU.equals(t1) || TOFU.equals(t2)) {
+ return TOFU;
+ }
+ if (t1.equals(t2)) {
+ return t2;
+ }
+ // switch to canonical names containing "." instead of "/" when
+ // checking against names found in bytecode but ensure the returned
+ // name contains "/"
+
+ String type1 = t1.replaceAll("/", ".");
+ String type2 = t2.replaceAll("/", ".");
+ ClassChecker checker1 = transformer.getClassChecker(type1, loader);
+ ClassChecker checker2 = transformer.getClassChecker(type2, loader);
+
+ if (checker1.isInterface()) {
+ if (checker2.isInterface()) {
+ // both are interfaces so find the first common parent interface
+ // (including the original interfaces) or return Object
+ LinkedList<String> interfaces2 = listInterfaces(checker2);
+ if (interfaces2.contains(type1)) {
+ return t1;
+ } else {
+ LinkedList<String> interfaces1 = listInterfaces(checker1);
+ while (!interfaces1.isEmpty()) {
+ String next = interfaces1.pop();
+ if (next.equals(type2)) {
+ return t2;
+ }
+ if (interfaces2.contains(next)) {
+ return next.replaceAll("\\.", "/");
+ }
+ }
+ return TOFU;
+ }
+ } else {
+ // type1 is an interface but type2 is a class so return the
+ // first parent interface of type2 which implements either type1 or
+ // one of type1's parent interfaces or return Object
+ LinkedList<String> interfaces2 = listInterfaces(checker2);
+ if (interfaces2.contains(type1)) {
+ // type1 is an interface of type2
+ return t1;
+ } else {
+ LinkedList<String> interfaces1 = listInterfaces(checker1);
+ while (!interfaces1.isEmpty()) {
+ String next = interfaces1.pop();
+ if (interfaces2.contains(next)) {
+ return next.replaceAll("\\.", "/");
+ }
+ }
+ return TOFU;
+ }
+ }
+ } else {
+ if (checker2.isInterface()) {
+ // type2 is an interface but type1 is a class so return the
+ // first parent interface of type1 which implements either type1 or
+ // one of type1's parent interfaces or return Object
+ LinkedList<String> interfaces1 = listInterfaces(checker1);
+ if (interfaces1.contains(type2)) {
+ // type2 is an interface of type1
+ return t1;
+ } else {
+ LinkedList<String> interfaces2 = listInterfaces(checker2);
+ while (!interfaces2.isEmpty()) {
+ String next = interfaces2.pop();
+ if (interfaces1.contains(next)) {
+ return next.replaceAll("\\.", "/");
+ }
+ }
+ return TOFU;
+ }
+ } else {
+ // see if the classes have a common super class before Object
+ LinkedList<String> supers2 = listSupers(checker2);
+ if (supers2.contains(type1)) {
+ // type2 implements interface type1
+ return t1;
+ } else {
+ LinkedList<String> supers1 = listSupers(checker1);
+ while (!supers1.isEmpty()) {
+ String next = supers1.pop();
+ if (next.equals(type2)) {
+ return t2;
+ }
+ if (supers2.contains(next)) {
+ return next.replaceAll("\\.", "/");
+ }
+ }
+ return TOFU;
+ }
+ }
+ }
+ }
+
+ private LinkedList<String> listInterfaces(ClassChecker checker)
+ {
+ LinkedList<String> interfaces = new LinkedList<String>();
+ ClassChecker current = checker;
+ while (current != null) {
+ LinkedList<String> toCheck = new LinkedList<String>();
+ int count = current.getInterfaceCount();
+ for (int i = 0; i < count; i++) {
+ toCheck.add(current.getInterface(i));
+ }
+ // now recursively work through unchecked interfaces adding them
+ // if not already processed and including the interfaces they extend
+ // in the toCheck list.
+ while (!toCheck.isEmpty()) {
+ String next = toCheck.pop();
+ if (!interfaces.contains(next)) {
+ interfaces.add(next);
+ ClassChecker newChecker = transformer.getClassChecker(next, loader);
+ count = newChecker.getInterfaceCount();
+ for (int i = 0; i < count; i++) {
+ toCheck.add(newChecker.getInterface(i));
+ }
+ }
+ }
+ // repeat for the next super in line up to java/lang/Object
+ String superType = current.getSuper();
+ if (superType == null || TOFU.equals(superType)) {
+ current = null;
+ } else {
+ current = transformer.getClassChecker(superType, loader);
+ }
+ }
+
+ return interfaces;
+ }
+
+ private LinkedList<String> listSupers(ClassChecker checker)
+ {
+ LinkedList<String> supers = new LinkedList<String>();
+ ClassChecker current = checker;
+ while (current != null) {
+ String superType = current.getSuper();
+ if (superType != null) {
+ supers.add(superType);
+ current = transformer.getClassChecker(superType, loader);
+ } else {
+ current = null;
+ }
+ }
+
+ return supers;
+ }
+
+ private Transformer transformer;
private RuleScript ruleScript;
private String triggerClassName;
private String targetMethodName;
View
4 agent/src/main/java/org/jboss/byteman/agent/Transformer.java
@@ -669,7 +669,7 @@ public static void maybeDumpClass(String fullName, byte[] bytes)
*/
public byte[] transform(RuleScript ruleScript, ClassLoader loader, String className, byte[] targetClassBytes)
{
- TransformContext transformContext = new TransformContext(ruleScript, className, loader, helperManager);
+ TransformContext transformContext = new TransformContext(this, ruleScript, className, loader, helperManager);
return transformContext.transform(targetClassBytes);
}
@@ -827,7 +827,7 @@ private boolean isTransformed(Class clazz, String name, boolean isInterface)
* @param baseLoader the class loader of the subclass's bytecode
* @return the requisite checker or null if the class does not need to be checked or cannot be loaded
*/
- private org.jboss.byteman.agent.check.ClassChecker getClassChecker(String name, ClassLoader baseLoader)
+ public org.jboss.byteman.agent.check.ClassChecker getClassChecker(String name, ClassLoader baseLoader)
{
// we would like to just do this
// Class superClazz = baseLoader.loadClass(name)
View
43 contrib/dtest/src/org/jboss/byteman/contrib/dtest/Instrumentor.java
@@ -164,16 +164,43 @@ public void setRedirectedSubmissionsFile(File redirectedSubmissionsFile)
*/
public void injectOnCall(Class clazz, String methodName, String action) throws Exception
{
+ injectOnMethod(clazz, methodName, "true", action, "ENTRY");
+ }
+
+ /**
+ * Inject an action to take place upon exit of the specified class.method
+ *
+ * @param clazz The Class in which the injection point resides.
+ * @param methodName The method which should be intercepted.
+ * @param action The action that should take place upon invocation of the method.
+ * @throws Exception in case of failure.
+ */
+ public void injectOnExit(Class clazz, String methodName, String action) throws Exception
+ {
+ injectOnMethod(clazz, methodName, "true", action, "EXIT");
+ }
+
+ /**
+ * Inject an action to take place at a given point within the specified class.method
+ *
+ * @param clazz The Class in which the injection point resides.
+ * @param methodName The method which should be intercepted.
+ * @param action The action that should take place upon invocation of the method.
+ * @param where the injection point e.g. "ENTRY".
+ * @throws Exception in case of failure.
+ */
+ public void injectOnMethod(Class clazz, String methodName, String condition, String action, String where) throws Exception
+ {
String className = clazz.getCanonicalName();
- String ruleName = this.getClass().getCanonicalName()+"_"+className+"_"+methodName+"_callinjection";
+ String ruleName = this.getClass().getCanonicalName()+"_"+className+"_"+methodName+"_injectionat"+where;
RuleBuilder ruleBuilder = new RuleBuilder(ruleName);
- ruleBuilder.onClass(className).inMethod(methodName).atEntry();
+ ruleBuilder.onClass(className).inMethod(methodName).at(where);
ruleBuilder.usingHelper(BytemanTestHelper.class);
- ruleBuilder.whenTrue().doAction(action);
+ ruleBuilder.when(condition).doAction(action);
String ruleText = ruleBuilder.toString();
- installScript("onCall"+className+"."+methodName, ruleText);
+ installScript("onCall"+className+"."+methodName+"."+where, ruleText);
}
/**
@@ -282,7 +309,7 @@ public void crashAtMethodEntry(String className, String methodName) throws Excep
*/
public void crashAtMethod(String className, String methodName, String where) throws Exception
{
- String ruleName = this.getClass().getCanonicalName()+"_"+className+"_"+methodName+"_crashatexit";
+ String ruleName = this.getClass().getCanonicalName()+"_"+className+"_"+methodName+"_crashat"+where;
String action = "debug(\"killing JVM\"), killJVM()";
@@ -291,7 +318,7 @@ public void crashAtMethod(String className, String methodName, String where) thr
ruleBuilder.usingHelper(BytemanTestHelper.class);
ruleBuilder.whenTrue().doAction(action);
- installScript("crash"+className+"."+methodName, ruleBuilder.toString());
+ installScript("crash"+className+"."+methodName+"."+where, ruleBuilder.toString());
}
/**
@@ -303,7 +330,7 @@ public void crashAtMethod(String className, String methodName, String where) thr
* @param scriptString The text of the script i.e. one or more Rules.
* @throws Exception in case of failure.
*/
- private void installScript(String scriptName, String scriptString)
+ public void installScript(String scriptName, String scriptString)
throws Exception
{
System.out.println("installing: "+scriptString);
@@ -407,4 +434,4 @@ public void removeAllInstrumentation() throws Exception
submit.deleteScripts(installedScripts);
removeLocalState();
}
-}
+}

No commit comments for this range

Something went wrong with that request. Please try again.