clazz) {
+ try {
+ return clazz.newInstance();
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+}
diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MatchUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MatchUtils.java
new file mode 100644
index 000000000..077040521
--- /dev/null
+++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MatchUtils.java
@@ -0,0 +1,169 @@
+package com.taobao.arthas.bytekit.utils;
+import java.util.ArrayList;
+import java.util.Stack;
+
+/**
+ * from org.apache.commons.io.FilenameUtils
+ *
+ * @author hengyunabc
+ *
+ */
+public class MatchUtils {
+
+ /**
+ * The wildcard matcher uses the characters '?' and '*' to represent a
+ * single or multiple wildcard characters.
+ *
+ * @param str
+ * @param wildcardMatcher
+ * @return
+ */
+ public static boolean wildcardMatch(String str, String wildcardMatcher) {
+ return wildcardMatch(str, wildcardMatcher, false);
+ }
+
+ /**
+ * The wildcard matcher uses the characters '?' and '*' to represent a
+ * single or multiple wildcard characters.
+ *
+ * @param str
+ * @param wildcardMatcher
+ * @param sensitive
+ * if sensitive is true, str and wildcardMatcher will
+ * toLowerCase.
+ * @return
+ */
+ public static boolean wildcardMatch(String str, String wildcardMatcher, boolean sensitive) {
+ if (str == null && wildcardMatcher == null) {
+ return true;
+ }
+ if (str == null || wildcardMatcher == null) {
+ return false;
+ }
+ str = convertCase(str, sensitive);
+ wildcardMatcher = convertCase(wildcardMatcher, sensitive);
+ String[] wcs = splitOnTokens(wildcardMatcher);
+ boolean anyChars = false;
+ int textIdx = 0;
+ int wcsIdx = 0;
+ Stack backtrack = new Stack();
+
+ // loop around a backtrack stack, to handle complex * matching
+ do {
+ if (backtrack.size() > 0) {
+ int[] array = (int[]) backtrack.pop();
+ wcsIdx = array[0];
+ textIdx = array[1];
+ anyChars = true;
+ }
+
+ // loop whilst tokens and text left to process
+ while (wcsIdx < wcs.length) {
+
+ if (wcs[wcsIdx].equals("?")) {
+ // ? so move to next text char
+ textIdx++;
+ anyChars = false;
+
+ } else if (wcs[wcsIdx].equals("*")) {
+ // set any chars status
+ anyChars = true;
+ if (wcsIdx == wcs.length - 1) {
+ textIdx = str.length();
+ }
+
+ } else {
+ // matching text token
+ if (anyChars) {
+ // any chars then try to locate text token
+ textIdx = str.indexOf(wcs[wcsIdx], textIdx);
+ if (textIdx == -1) {
+ // token not found
+ break;
+ }
+ int repeat = str.indexOf(wcs[wcsIdx], textIdx + 1);
+ if (repeat >= 0) {
+ backtrack.push(new int[] { wcsIdx, repeat });
+ }
+ } else {
+ // matching from current position
+ if (!str.startsWith(wcs[wcsIdx], textIdx)) {
+ // couldnt match token
+ break;
+ }
+ }
+
+ // matched text token, move text index to end of matched
+ // token
+ textIdx += wcs[wcsIdx].length();
+ anyChars = false;
+ }
+
+ wcsIdx++;
+ }
+
+ // full match
+ if (wcsIdx == wcs.length && textIdx == str.length()) {
+ return true;
+ }
+
+ } while (backtrack.size() > 0);
+
+ return false;
+ }
+
+ /**
+ * Splits a string into a number of tokens.
+ *
+ * @param text
+ * the text to split
+ * @return the tokens, never null
+ */
+ static String[] splitOnTokens(String text) {
+ // used by wildcardMatch
+ // package level so a unit test may run on this
+
+ if (text.indexOf("?") == -1 && text.indexOf("*") == -1) {
+ return new String[] { text };
+ }
+
+ char[] array = text.toCharArray();
+ ArrayList list = new ArrayList();
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == '?' || array[i] == '*') {
+ if (buffer.length() != 0) {
+ list.add(buffer.toString());
+ buffer.setLength(0);
+ }
+ if (array[i] == '?') {
+ list.add("?");
+ } else if (list.size() == 0 || (i > 0 && list.get(list.size() - 1).equals("*") == false)) {
+ list.add("*");
+ }
+ } else {
+ buffer.append(array[i]);
+ }
+ }
+ if (buffer.length() != 0) {
+ list.add(buffer.toString());
+ }
+
+ return (String[]) list.toArray(new String[list.size()]);
+ }
+
+ /**
+ * Converts the case of the input String to a standard format. Subsequent
+ * operations can then use standard String methods.
+ *
+ * @param str
+ * the string to convert, null returns null
+ * @return the lower-case version if case-insensitive
+ */
+ static String convertCase(String str, boolean sensitive) {
+ if (str == null) {
+ return null;
+ }
+ return sensitive ? str : str.toLowerCase();
+ }
+}
\ No newline at end of file
diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MethodUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MethodUtils.java
new file mode 100644
index 000000000..8023d3b24
--- /dev/null
+++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MethodUtils.java
@@ -0,0 +1,5 @@
+package com.taobao.arthas.bytekit.utils;
+
+public class MethodUtils {
+
+}
diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/ReflectionUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/ReflectionUtils.java
new file mode 100644
index 000000000..b96f30b3c
--- /dev/null
+++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/ReflectionUtils.java
@@ -0,0 +1,807 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * 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.taobao.arthas.bytekit.utils;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Simple utility class for working with the reflection API and handling
+ * reflection exceptions.
+ *
+ * Only intended for internal use.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Rod Johnson
+ * @author Costin Leau
+ * @author Sam Brannen
+ * @author Chris Beams
+ * @since 1.2.2
+ */
+public abstract class ReflectionUtils {
+
+ /**
+ * Naming prefix for CGLIB-renamed methods.
+ * @see #isCglibRenamedMethod
+ */
+ private static final String CGLIB_RENAMED_METHOD_PREFIX = "CGLIB$";
+
+ /**
+ * Attempt to find a {@link Field field} on the supplied {@link Class} with the
+ * supplied {@code name}. Searches all superclasses up to {@link Object}.
+ * @param clazz the class to introspect
+ * @param name the name of the field
+ * @return the corresponding Field object, or {@code null} if not found
+ */
+ public static Field findField(Class> clazz, String name) {
+ return findField(clazz, name, null);
+ }
+
+ /**
+ * Attempt to find a {@link Field field} on the supplied {@link Class} with the
+ * supplied {@code name} and/or {@link Class type}. Searches all superclasses
+ * up to {@link Object}.
+ * @param clazz the class to introspect
+ * @param name the name of the field (may be {@code null} if type is specified)
+ * @param type the type of the field (may be {@code null} if name is specified)
+ * @return the corresponding Field object, or {@code null} if not found
+ */
+ public static Field findField(Class> clazz, String name, Class> type) {
+ Class> searchType = clazz;
+ while (Object.class != searchType && searchType != null) {
+ Field[] fields = getDeclaredFields(searchType);
+ for (Field field : fields) {
+ if ((name == null || name.equals(field.getName())) &&
+ (type == null || type.equals(field.getType()))) {
+ return field;
+ }
+ }
+ searchType = searchType.getSuperclass();
+ }
+ return null;
+ }
+
+ /**
+ * Set the field represented by the supplied {@link Field field object} on the
+ * specified {@link Object target object} to the specified {@code value}.
+ * In accordance with {@link Field#set(Object, Object)} semantics, the new value
+ * is automatically unwrapped if the underlying field has a primitive type.
+ *
Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}.
+ * @param field the field to set
+ * @param target the target object on which to set the field
+ * @param value the value to set (may be {@code null})
+ */
+ public static void setField(Field field, Object target, Object value) {
+ try {
+ field.set(target, value);
+ }
+ catch (IllegalAccessException ex) {
+ handleReflectionException(ex);
+ throw new IllegalStateException(
+ "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Get the field represented by the supplied {@link Field field object} on the
+ * specified {@link Object target object}. In accordance with {@link Field#get(Object)}
+ * semantics, the returned value is automatically wrapped if the underlying field
+ * has a primitive type.
+ *
Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}.
+ * @param field the field to get
+ * @param target the target object from which to get the field
+ * @return the field's current value
+ */
+ public static Object getField(Field field, Object target) {
+ try {
+ return field.get(target);
+ }
+ catch (IllegalAccessException ex) {
+ handleReflectionException(ex);
+ throw new IllegalStateException(
+ "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Attempt to find a {@link Method} on the supplied class with the supplied name
+ * and no parameters. Searches all superclasses up to {@code Object}.
+ *
Returns {@code null} if no {@link Method} can be found.
+ * @param clazz the class to introspect
+ * @param name the name of the method
+ * @return the Method object, or {@code null} if none found
+ */
+ public static Method findMethod(Class> clazz, String name) {
+ return findMethod(clazz, name, new Class>[0]);
+ }
+
+ /**
+ * Attempt to find a {@link Method} on the supplied class with the supplied name
+ * and parameter types. Searches all superclasses up to {@code Object}.
+ *
Returns {@code null} if no {@link Method} can be found.
+ * @param clazz the class to introspect
+ * @param name the name of the method
+ * @param paramTypes the parameter types of the method
+ * (may be {@code null} to indicate any signature)
+ * @return the Method object, or {@code null} if none found
+ */
+ public static Method findMethod(Class> clazz, String name, Class>... paramTypes) {
+ Class> searchType = clazz;
+ while (searchType != null) {
+ Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType));
+ for (Method method : methods) {
+ if (name.equals(method.getName()) &&
+ (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
+ return method;
+ }
+ }
+ searchType = searchType.getSuperclass();
+ }
+ return null;
+ }
+
+ /**
+ * Invoke the specified {@link Method} against the supplied target object with no arguments.
+ * The target object can be {@code null} when invoking a static {@link Method}.
+ *
Thrown exceptions are handled via a call to {@link #handleReflectionException}.
+ * @param method the method to invoke
+ * @param target the target object to invoke the method on
+ * @return the invocation result, if any
+ * @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
+ */
+ public static Object invokeMethod(Method method, Object target) {
+ return invokeMethod(method, target, new Object[0]);
+ }
+
+ /**
+ * Invoke the specified {@link Method} against the supplied target object with the
+ * supplied arguments. The target object can be {@code null} when invoking a
+ * static {@link Method}.
+ *
Thrown exceptions are handled via a call to {@link #handleReflectionException}.
+ * @param method the method to invoke
+ * @param target the target object to invoke the method on
+ * @param args the invocation arguments (may be {@code null})
+ * @return the invocation result, if any
+ */
+ public static Object invokeMethod(Method method, Object target, Object... args) {
+ try {
+ return method.invoke(target, args);
+ }
+ catch (Exception ex) {
+ handleReflectionException(ex);
+ }
+ throw new IllegalStateException("Should never get here");
+ }
+
+ /**
+ * Invoke the specified JDBC API {@link Method} against the supplied target
+ * object with no arguments.
+ * @param method the method to invoke
+ * @param target the target object to invoke the method on
+ * @return the invocation result, if any
+ * @throws SQLException the JDBC API SQLException to rethrow (if any)
+ * @see #invokeJdbcMethod(java.lang.reflect.Method, Object, Object[])
+ */
+ public static Object invokeJdbcMethod(Method method, Object target) throws SQLException {
+ return invokeJdbcMethod(method, target, new Object[0]);
+ }
+
+ /**
+ * Invoke the specified JDBC API {@link Method} against the supplied target
+ * object with the supplied arguments.
+ * @param method the method to invoke
+ * @param target the target object to invoke the method on
+ * @param args the invocation arguments (may be {@code null})
+ * @return the invocation result, if any
+ * @throws SQLException the JDBC API SQLException to rethrow (if any)
+ * @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
+ */
+ public static Object invokeJdbcMethod(Method method, Object target, Object... args) throws SQLException {
+ try {
+ return method.invoke(target, args);
+ }
+ catch (IllegalAccessException ex) {
+ handleReflectionException(ex);
+ }
+ catch (InvocationTargetException ex) {
+ if (ex.getTargetException() instanceof SQLException) {
+ throw (SQLException) ex.getTargetException();
+ }
+ handleInvocationTargetException(ex);
+ }
+ throw new IllegalStateException("Should never get here");
+ }
+
+ /**
+ * Handle the given reflection exception. Should only be called if no
+ * checked exception is expected to be thrown by the target method.
+ *
Throws the underlying RuntimeException or Error in case of an
+ * InvocationTargetException with such a root cause. Throws an
+ * IllegalStateException with an appropriate message or
+ * UndeclaredThrowableException otherwise.
+ * @param ex the reflection exception to handle
+ */
+ public static void handleReflectionException(Exception ex) {
+ if (ex instanceof NoSuchMethodException) {
+ throw new IllegalStateException("Method not found: " + ex.getMessage());
+ }
+ if (ex instanceof IllegalAccessException) {
+ throw new IllegalStateException("Could not access method: " + ex.getMessage());
+ }
+ if (ex instanceof InvocationTargetException) {
+ handleInvocationTargetException((InvocationTargetException) ex);
+ }
+ if (ex instanceof RuntimeException) {
+ throw (RuntimeException) ex;
+ }
+ throw new UndeclaredThrowableException(ex);
+ }
+
+ /**
+ * Handle the given invocation target exception. Should only be called if no
+ * checked exception is expected to be thrown by the target method.
+ *
Throws the underlying RuntimeException or Error in case of such a root
+ * cause. Throws an UndeclaredThrowableException otherwise.
+ * @param ex the invocation target exception to handle
+ */
+ public static void handleInvocationTargetException(InvocationTargetException ex) {
+ rethrowRuntimeException(ex.getTargetException());
+ }
+
+ /**
+ * Rethrow the given {@link Throwable exception}, which is presumably the
+ * target exception of an {@link InvocationTargetException}.
+ * Should only be called if no checked exception is expected to be thrown
+ * by the target method.
+ *
Rethrows the underlying exception cast to a {@link RuntimeException} or
+ * {@link Error} if appropriate; otherwise, throws an
+ * {@link UndeclaredThrowableException}.
+ * @param ex the exception to rethrow
+ * @throws RuntimeException the rethrown exception
+ */
+ public static void rethrowRuntimeException(Throwable ex) {
+ if (ex instanceof RuntimeException) {
+ throw (RuntimeException) ex;
+ }
+ if (ex instanceof Error) {
+ throw (Error) ex;
+ }
+ throw new UndeclaredThrowableException(ex);
+ }
+
+ /**
+ * Rethrow the given {@link Throwable exception}, which is presumably the
+ * target exception of an {@link InvocationTargetException}.
+ * Should only be called if no checked exception is expected to be thrown
+ * by the target method.
+ *
Rethrows the underlying exception cast to an {@link Exception} or
+ * {@link Error} if appropriate; otherwise, throws an
+ * {@link UndeclaredThrowableException}.
+ * @param ex the exception to rethrow
+ * @throws Exception the rethrown exception (in case of a checked exception)
+ */
+ public static void rethrowException(Throwable ex) throws Exception {
+ if (ex instanceof Exception) {
+ throw (Exception) ex;
+ }
+ if (ex instanceof Error) {
+ throw (Error) ex;
+ }
+ throw new UndeclaredThrowableException(ex);
+ }
+
+ /**
+ * Determine whether the given method explicitly declares the given
+ * exception or one of its superclasses, which means that an exception
+ * of that type can be propagated as-is within a reflective invocation.
+ * @param method the declaring method
+ * @param exceptionType the exception to throw
+ * @return {@code true} if the exception can be thrown as-is;
+ * {@code false} if it needs to be wrapped
+ */
+ public static boolean declaresException(Method method, Class> exceptionType) {
+ Class>[] declaredExceptions = method.getExceptionTypes();
+ for (Class> declaredException : declaredExceptions) {
+ if (declaredException.isAssignableFrom(exceptionType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determine whether the given field is a "public static final" constant.
+ * @param field the field to check
+ */
+ public static boolean isPublicStaticFinal(Field field) {
+ int modifiers = field.getModifiers();
+ return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers));
+ }
+
+ /**
+ * Determine whether the given method is an "equals" method.
+ * @see java.lang.Object#equals(Object)
+ */
+ public static boolean isEqualsMethod(Method method) {
+ if (method == null || !method.getName().equals("equals")) {
+ return false;
+ }
+ Class>[] paramTypes = method.getParameterTypes();
+ return (paramTypes.length == 1 && paramTypes[0] == Object.class);
+ }
+
+ /**
+ * Determine whether the given method is a "hashCode" method.
+ * @see java.lang.Object#hashCode()
+ */
+ public static boolean isHashCodeMethod(Method method) {
+ return (method != null && method.getName().equals("hashCode") && method.getParameterTypes().length == 0);
+ }
+
+ /**
+ * Determine whether the given method is a "toString" method.
+ * @see java.lang.Object#toString()
+ */
+ public static boolean isToStringMethod(Method method) {
+ return (method != null && method.getName().equals("toString") && method.getParameterTypes().length == 0);
+ }
+
+ /**
+ * Determine whether the given method is originally declared by {@link java.lang.Object}.
+ */
+ public static boolean isObjectMethod(Method method) {
+ if (method == null) {
+ return false;
+ }
+ try {
+ Object.class.getDeclaredMethod(method.getName(), method.getParameterTypes());
+ return true;
+ }
+ catch (Exception ex) {
+ return false;
+ }
+ }
+
+ /**
+ * Determine whether the given method is a CGLIB 'renamed' method,
+ * following the pattern "CGLIB$methodName$0".
+ * @param renamedMethod the method to check
+ * @see org.springframework.cglib.proxy.Enhancer#rename
+ */
+ public static boolean isCglibRenamedMethod(Method renamedMethod) {
+ String name = renamedMethod.getName();
+ if (name.startsWith(CGLIB_RENAMED_METHOD_PREFIX)) {
+ int i = name.length() - 1;
+ while (i >= 0 && Character.isDigit(name.charAt(i))) {
+ i--;
+ }
+ return ((i > CGLIB_RENAMED_METHOD_PREFIX.length()) &&
+ (i < name.length() - 1) && name.charAt(i) == '$');
+ }
+ return false;
+ }
+
+ /**
+ * Make the given field accessible, explicitly setting it accessible if
+ * necessary. The {@code setAccessible(true)} method is only called
+ * when actually necessary, to avoid unnecessary conflicts with a JVM
+ * SecurityManager (if active).
+ * @param field the field to make accessible
+ * @see java.lang.reflect.Field#setAccessible
+ */
+ public static void makeAccessible(Field field) {
+ if ((!Modifier.isPublic(field.getModifiers()) ||
+ !Modifier.isPublic(field.getDeclaringClass().getModifiers()) ||
+ Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) {
+ field.setAccessible(true);
+ }
+ }
+
+ /**
+ * Make the given method accessible, explicitly setting it accessible if
+ * necessary. The {@code setAccessible(true)} method is only called
+ * when actually necessary, to avoid unnecessary conflicts with a JVM
+ * SecurityManager (if active).
+ * @param method the method to make accessible
+ * @see java.lang.reflect.Method#setAccessible
+ */
+ public static void makeAccessible(Method method) {
+ if ((!Modifier.isPublic(method.getModifiers()) ||
+ !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) {
+ method.setAccessible(true);
+ }
+ }
+
+ /**
+ * Make the given constructor accessible, explicitly setting it accessible
+ * if necessary. The {@code setAccessible(true)} method is only called
+ * when actually necessary, to avoid unnecessary conflicts with a JVM
+ * SecurityManager (if active).
+ * @param ctor the constructor to make accessible
+ * @see java.lang.reflect.Constructor#setAccessible
+ */
+ public static void makeAccessible(Constructor> ctor) {
+ if ((!Modifier.isPublic(ctor.getModifiers()) ||
+ !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) {
+ ctor.setAccessible(true);
+ }
+ }
+
+ /**
+ * Perform the given callback operation on all matching methods of the given
+ * class, as locally declared or equivalent thereof (such as default methods
+ * on Java 8 based interfaces that the given class implements).
+ * @param clazz the class to introspect
+ * @param mc the callback to invoke for each method
+ * @since 4.2
+ * @see #doWithMethods
+ */
+ public static void doWithLocalMethods(Class> clazz, MethodCallback mc) {
+ Method[] methods = getDeclaredMethods(clazz);
+ for (Method method : methods) {
+ try {
+ mc.doWith(method);
+ }
+ catch (IllegalAccessException ex) {
+ throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
+ }
+ }
+ }
+
+ /**
+ * Perform the given callback operation on all matching methods of the given
+ * class and superclasses.
+ *
The same named method occurring on subclass and superclass will appear
+ * twice, unless excluded by a {@link MethodFilter}.
+ * @param clazz the class to introspect
+ * @param mc the callback to invoke for each method
+ * @see #doWithMethods(Class, MethodCallback, MethodFilter)
+ */
+ public static void doWithMethods(Class> clazz, MethodCallback mc) {
+ doWithMethods(clazz, mc, null);
+ }
+
+ /**
+ * Perform the given callback operation on all matching methods of the given
+ * class and superclasses (or given interface and super-interfaces).
+ *
The same named method occurring on subclass and superclass will appear
+ * twice, unless excluded by the specified {@link MethodFilter}.
+ * @param clazz the class to introspect
+ * @param mc the callback to invoke for each method
+ * @param mf the filter that determines the methods to apply the callback to
+ */
+ public static void doWithMethods(Class> clazz, MethodCallback mc, MethodFilter mf) {
+ // Keep backing up the inheritance hierarchy.
+ Method[] methods = getDeclaredMethods(clazz);
+ for (Method method : methods) {
+ if (mf != null && !mf.matches(method)) {
+ continue;
+ }
+ try {
+ mc.doWith(method);
+ }
+ catch (IllegalAccessException ex) {
+ throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
+ }
+ }
+ if (clazz.getSuperclass() != null) {
+ doWithMethods(clazz.getSuperclass(), mc, mf);
+ }
+ else if (clazz.isInterface()) {
+ for (Class> superIfc : clazz.getInterfaces()) {
+ doWithMethods(superIfc, mc, mf);
+ }
+ }
+ }
+
+ /**
+ * Get all declared methods on the leaf class and all superclasses.
+ * Leaf class methods are included first.
+ * @param leafClass the class to introspect
+ */
+ public static Method[] getAllDeclaredMethods(Class> leafClass) {
+ final List methods = new ArrayList(32);
+ doWithMethods(leafClass, new MethodCallback() {
+ @Override
+ public void doWith(Method method) {
+ methods.add(method);
+ }
+ });
+ return methods.toArray(new Method[methods.size()]);
+ }
+
+ /**
+ * Get the unique set of declared methods on the leaf class and all superclasses.
+ * Leaf class methods are included first and while traversing the superclass hierarchy
+ * any methods found with signatures matching a method already included are filtered out.
+ * @param leafClass the class to introspect
+ */
+ public static Method[] getUniqueDeclaredMethods(Class> leafClass) {
+ final List methods = new ArrayList(32);
+ doWithMethods(leafClass, new MethodCallback() {
+ @Override
+ public void doWith(Method method) {
+ boolean knownSignature = false;
+ Method methodBeingOverriddenWithCovariantReturnType = null;
+ for (Method existingMethod : methods) {
+ if (method.getName().equals(existingMethod.getName()) &&
+ Arrays.equals(method.getParameterTypes(), existingMethod.getParameterTypes())) {
+ // Is this a covariant return type situation?
+ if (existingMethod.getReturnType() != method.getReturnType() &&
+ existingMethod.getReturnType().isAssignableFrom(method.getReturnType())) {
+ methodBeingOverriddenWithCovariantReturnType = existingMethod;
+ }
+ else {
+ knownSignature = true;
+ }
+ break;
+ }
+ }
+ if (methodBeingOverriddenWithCovariantReturnType != null) {
+ methods.remove(methodBeingOverriddenWithCovariantReturnType);
+ }
+ if (!knownSignature && !isCglibRenamedMethod(method)) {
+ methods.add(method);
+ }
+ }
+ });
+ return methods.toArray(new Method[methods.size()]);
+ }
+
+ /**
+ * This variant retrieves {@link Class#getDeclaredMethods()} from a local cache
+ * in order to avoid the JVM's SecurityManager check and defensive array copying.
+ * In addition, it also includes Java 8 default methods from locally implemented
+ * interfaces, since those are effectively to be treated just like declared methods.
+ * @param clazz the class to introspect
+ * @return the cached array of methods
+ * @see Class#getDeclaredMethods()
+ */
+ private static Method[] getDeclaredMethods(Class> clazz) {
+ Method[] result = null;
+ if (result == null) {
+ Method[] declaredMethods = clazz.getDeclaredMethods();
+ List defaultMethods = findConcreteMethodsOnInterfaces(clazz);
+ if (defaultMethods != null) {
+ result = new Method[declaredMethods.length + defaultMethods.size()];
+ System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length);
+ int index = declaredMethods.length;
+ for (Method defaultMethod : defaultMethods) {
+ result[index] = defaultMethod;
+ index++;
+ }
+ }
+ else {
+ result = declaredMethods;
+ }
+ }
+ return result;
+ }
+
+ private static List findConcreteMethodsOnInterfaces(Class> clazz) {
+ List result = null;
+ for (Class> ifc : clazz.getInterfaces()) {
+ for (Method ifcMethod : ifc.getMethods()) {
+ if (!Modifier.isAbstract(ifcMethod.getModifiers())) {
+ if (result == null) {
+ result = new LinkedList();
+ }
+ result.add(ifcMethod);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Invoke the given callback on all fields in the target class, going up the
+ * class hierarchy to get all declared fields.
+ * @param clazz the target class to analyze
+ * @param fc the callback to invoke for each field
+ * @since 4.2
+ * @see #doWithFields
+ */
+ public static void doWithLocalFields(Class> clazz, FieldCallback fc) {
+ for (Field field : getDeclaredFields(clazz)) {
+ try {
+ fc.doWith(field);
+ }
+ catch (IllegalAccessException ex) {
+ throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex);
+ }
+ }
+ }
+
+ /**
+ * Invoke the given callback on all fields in the target class, going up the
+ * class hierarchy to get all declared fields.
+ * @param clazz the target class to analyze
+ * @param fc the callback to invoke for each field
+ */
+ public static void doWithFields(Class> clazz, FieldCallback fc) {
+ doWithFields(clazz, fc, null);
+ }
+
+ /**
+ * Invoke the given callback on all fields in the target class, going up the
+ * class hierarchy to get all declared fields.
+ * @param clazz the target class to analyze
+ * @param fc the callback to invoke for each field
+ * @param ff the filter that determines the fields to apply the callback to
+ */
+ public static void doWithFields(Class> clazz, FieldCallback fc, FieldFilter ff) {
+ // Keep backing up the inheritance hierarchy.
+ Class> targetClass = clazz;
+ do {
+ Field[] fields = getDeclaredFields(targetClass);
+ for (Field field : fields) {
+ if (ff != null && !ff.matches(field)) {
+ continue;
+ }
+ try {
+ fc.doWith(field);
+ }
+ catch (IllegalAccessException ex) {
+ throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex);
+ }
+ }
+ targetClass = targetClass.getSuperclass();
+ }
+ while (targetClass != null && targetClass != Object.class);
+ }
+
+ /**
+ * This variant retrieves {@link Class#getDeclaredFields()} from a local cache
+ * in order to avoid the JVM's SecurityManager check and defensive array copying.
+ * @param clazz the class to introspect
+ * @return the cached array of fields
+ * @see Class#getDeclaredFields()
+ */
+ private static Field[] getDeclaredFields(Class> clazz) {
+ Field[] result = null;
+ if (result == null) {
+ result = clazz.getDeclaredFields();
+ }
+ return result;
+ }
+
+ /**
+ * Given the source object and the destination, which must be the same class
+ * or a subclass, copy all fields, including inherited fields. Designed to
+ * work on objects with public no-arg constructors.
+ */
+ public static void shallowCopyFieldState(final Object src, final Object dest) {
+ if (src == null) {
+ throw new IllegalArgumentException("Source for field copy cannot be null");
+ }
+ if (dest == null) {
+ throw new IllegalArgumentException("Destination for field copy cannot be null");
+ }
+ if (!src.getClass().isAssignableFrom(dest.getClass())) {
+ throw new IllegalArgumentException("Destination class [" + dest.getClass().getName() +
+ "] must be same or subclass as source class [" + src.getClass().getName() + "]");
+ }
+ doWithFields(src.getClass(), new FieldCallback() {
+ @Override
+ public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
+ makeAccessible(field);
+ Object srcValue = field.get(src);
+ field.set(dest, srcValue);
+ }
+ }, COPYABLE_FIELDS);
+ }
+
+
+ /**
+ * Action to take on each method.
+ */
+ public interface MethodCallback {
+
+ /**
+ * Perform an operation using the given method.
+ * @param method the method to operate on
+ */
+ void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
+ }
+
+
+ /**
+ * Callback optionally used to filter methods to be operated on by a method callback.
+ */
+ public interface MethodFilter {
+
+ /**
+ * Determine whether the given method matches.
+ * @param method the method to check
+ */
+ boolean matches(Method method);
+ }
+
+
+ /**
+ * Callback interface invoked on each field in the hierarchy.
+ */
+ public interface FieldCallback {
+
+ /**
+ * Perform an operation using the given field.
+ * @param field the field to operate on
+ */
+ void doWith(Field field) throws IllegalArgumentException, IllegalAccessException;
+ }
+
+
+ /**
+ * Callback optionally used to filter fields to be operated on by a field callback.
+ */
+ public interface FieldFilter {
+
+ /**
+ * Determine whether the given field matches.
+ * @param field the field to check
+ */
+ boolean matches(Field field);
+ }
+
+
+ /**
+ * Pre-built FieldFilter that matches all non-static, non-final fields.
+ */
+ public static final FieldFilter COPYABLE_FIELDS = new FieldFilter() {
+
+ @Override
+ public boolean matches(Field field) {
+ return !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers()));
+ }
+ };
+
+
+ /**
+ * Pre-built MethodFilter that matches all non-bridge methods.
+ */
+ public static final MethodFilter NON_BRIDGED_METHODS = new MethodFilter() {
+
+ @Override
+ public boolean matches(Method method) {
+ return !method.isBridge();
+ }
+ };
+
+
+ /**
+ * Pre-built MethodFilter that matches all non-bridge methods
+ * which are not declared on {@code java.lang.Object}.
+ */
+ public static final MethodFilter USER_DECLARED_METHODS = new MethodFilter() {
+
+ @Override
+ public boolean matches(Method method) {
+ return (!method.isBridge() && method.getDeclaringClass() != Object.class);
+ }
+ };
+
+}
diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/VerifyUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/VerifyUtils.java
new file mode 100644
index 000000000..f60bac4b1
--- /dev/null
+++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/VerifyUtils.java
@@ -0,0 +1,69 @@
+package com.taobao.arthas.bytekit.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import com.alibaba.arthas.deps.org.objectweb.asm.ClassReader;
+import com.alibaba.arthas.deps.org.objectweb.asm.ClassVisitor;
+import com.alibaba.arthas.deps.org.objectweb.asm.ClassWriter;
+import com.alibaba.arthas.deps.org.objectweb.asm.Type;
+import com.alibaba.arthas.deps.org.objectweb.asm.util.CheckClassAdapter;
+
+/**
+ *
+ * @author hengyunabc
+ *
+ */
+public class VerifyUtils {
+
+ public static void asmVerify(byte[] bytes) throws IOException {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+ ClassReader cr = new ClassReader(inputStream);
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+ ClassVisitor cv = new CheckClassAdapter(cw);
+
+ cr.accept(cv, 0);
+ }
+
+ public static Object instanceVerity(byte[] bytes) throws Exception {
+ String name = Type.getObjectType(AsmUtils.toClassNode(bytes).name).getClassName();
+
+ URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
+
+ @SuppressWarnings("resource")
+ ClassbyteClassLoader cl = new ClassbyteClassLoader(systemClassLoader.getURLs(),
+ ClassLoader.getSystemClassLoader().getParent());
+
+ cl.addClass(name, bytes);
+
+ Class> loadClass = cl.loadClass(name);
+ return loadClass.newInstance();
+ }
+
+ public static Object invoke(Object instance, String name, Object... args) throws Exception {
+ Method[] methods = instance.getClass().getMethods();
+ for (Method method : methods) {
+ if (name.contentEquals(method.getName())) {
+ return method.invoke(instance, args);
+ }
+ }
+ throw new NoSuchMethodError("name: " + name);
+ }
+
+ public static class ClassbyteClassLoader extends URLClassLoader {
+ public ClassbyteClassLoader(URL[] urls, ClassLoader cl) {
+ super(urls, cl);
+ }
+
+ public Class> addClass(String name, byte[] bytes) throws ClassFormatError {
+ Class> cl = defineClass(name, bytes, 0, bytes.length);
+ resolveClass(cl);
+
+ return cl;
+ }
+ }
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo.java
new file mode 100644
index 000000000..2daf425f5
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo.java
@@ -0,0 +1,18 @@
+package com.taobao.arthas.bytekit.asm.inst;
+
+public class InstDemo {
+
+ public int returnInt(int i) {
+ System.out.println(new Object[] { i });
+ return 9998;
+ }
+
+ public static void onEnter(Object[] args) {
+ System.out.println(args);
+ }
+
+ public static int returnIntStatic(int i) {
+ return 9998;
+ }
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemoTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemoTest.java
new file mode 100644
index 000000000..dc25eb0a6
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemoTest.java
@@ -0,0 +1,102 @@
+package com.taobao.arthas.bytekit.asm.inst;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Test;
+import com.alibaba.arthas.deps.org.objectweb.asm.Type;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.AnnotationNode;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.FieldNode;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
+
+import com.taobao.arthas.bytekit.asm.MethodProcessor;
+import com.taobao.arthas.bytekit.utils.AsmOpUtils;
+import com.taobao.arthas.bytekit.utils.AsmUtils;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+import com.taobao.arthas.bytekit.utils.VerifyUtils;
+
+public class InstDemoTest {
+
+ @Test
+ public void test() throws Exception {
+
+ ClassNode apmClassNode = AsmUtils.loadClass(InstDemo_APM.class);
+
+ ClassNode originClassNode = AsmUtils.loadClass(InstDemo.class);
+
+ ClassNode targetClassNode = AsmUtils.copy(originClassNode);
+
+
+ byte[] renameClass = AsmUtils.renameClass(AsmUtils.toBytes(apmClassNode), Type.getObjectType(originClassNode.name).getClassName());
+
+ apmClassNode = AsmUtils.toClassNode(renameClass);
+
+ for(FieldNode fieldNode : apmClassNode.fields) {
+ if( fieldNode.visibleAnnotations != null) {
+ for( AnnotationNode annotationNode : fieldNode.visibleAnnotations) {
+ System.err.println(annotationNode.desc);
+ System.err.println(annotationNode.values);
+
+ if(Type.getType(NewField.class).equals(Type.getType(annotationNode.desc))) {
+ AsmUtils.addField(targetClassNode, fieldNode);
+ }
+
+ }
+ }
+ }
+
+ for (MethodNode methodNode : apmClassNode.methods) {
+ methodNode = AsmUtils.removeLineNumbers(methodNode);
+ if (methodNode.name.startsWith("__origin_")) {
+ continue;
+ } else {
+ MethodNode findMethod = AsmUtils.findMethod(originClassNode.methods, methodNode);
+ if (findMethod != null) {
+ // 先要替换 invokeOrigin ,要判断
+ // 从 apm 里查找 __origin_ 开头的函数,忽略
+ // 查找 非 __origin_ 开头的函数,在原来的类里查找,如果有同样签名的函数
+ // 则从函数里查找 是否有 __origin_ 的函数调用。如果有的话,则从原有的类里查找到 method,再inline掉。
+
+ List originMethodInsnNodes = AsmUtils.findMethodInsnNodeWithPrefix(methodNode,
+ "__origin_");
+
+ for (MethodInsnNode methodInsnNode : originMethodInsnNodes) {
+ String toInlineMethodName = methodInsnNode.name.substring("__origin_".length());
+ MethodNode originMethodNode = AsmUtils.findMethod(originClassNode.methods, toInlineMethodName,
+ findMethod.desc);
+
+ MethodNode tmpMethodNode = AsmUtils.copy(originMethodNode);
+ tmpMethodNode.name = methodInsnNode.name;
+
+ MethodProcessor methodProcessor = new MethodProcessor(apmClassNode.name, methodNode);
+ methodProcessor.inline(originClassNode.name, tmpMethodNode);
+
+ AsmUtils.replaceMethod(targetClassNode, methodProcessor.getMethodNode());
+
+ }
+
+ } else {
+ // 没找到的函数,则加进去
+ AsmUtils.addMethod(targetClassNode, methodNode);
+ }
+ }
+
+
+ }
+
+ byte[] resutlBytes = AsmUtils.toBytes(targetClassNode);
+
+ System.err.println(Decompiler.decompile(resutlBytes));
+
+ System.err.println(AsmUtils.toASMCode(resutlBytes));
+
+ FileUtils.writeByteArrayToFile(new File("/tmp/ttt/InstDemo.class"), resutlBytes);
+
+ VerifyUtils.asmVerify(resutlBytes);
+ VerifyUtils.instanceVerity(resutlBytes);
+ }
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo_APM.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo_APM.java
new file mode 100644
index 000000000..75cfd3aed
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo_APM.java
@@ -0,0 +1,29 @@
+package com.taobao.arthas.bytekit.asm.inst;
+
+@Instrument
+public class InstDemo_APM {
+
+ @NewField
+ private String newField;
+
+ public int newMethod(String s) {
+ return s.length() + 998;
+ }
+
+ // 这种方式来写怎么样?有点丑,但是不需要写那些转换的代码。 在插件的编绎出结果后,可以检查下 名字,static,参数等是否匹配的。
+ // 这种处理有点丑,但inline应该没问题
+ public int __origin_returnInt(int i) {
+ return 0;
+ }
+
+ public int returnInt(int i) {
+
+ int re = __origin_returnInt(i);
+
+ return 9998 + re;
+ }
+
+ public static int returnIntStatic(int i) {
+ return 9998;
+ }
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo.java
new file mode 100644
index 000000000..041d26ea0
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo.java
@@ -0,0 +1,77 @@
+package com.taobao.arthas.bytekit.asm.inst;
+
+import java.util.Date;
+
+/**
+ * @author hengyunabc 2019-03-13
+ *
+ */
+public class InvokeOriginDemo {
+
+ public void returnVoid() {
+ }
+
+ public Void returnVoidObject() {
+ int i = 0;
+ try {
+ int parseInt = Integer.parseInt("1000");
+ i += parseInt;
+ } catch (Exception e) {
+ System.err.println(i + " " + e);
+ }
+
+ return null;
+ }
+
+ public int returnInt(int i) {
+ return 9998;
+ }
+
+ public int returnIntToObject(int i) {
+
+ return 9998;
+ }
+
+ public int returnIntToInteger(int i) {
+
+ return 9998;
+ }
+
+ public static int returnIntStatic(int i) {
+ return 9998;
+ }
+
+ public long returnLong() {
+ return 9998L;
+ }
+
+ public long returnLongToObject() {
+ return 9998L;
+ }
+
+ public String[] returnStrArray() {
+ String[] result = new String[] {"abc", "xyz" , "ufo"};
+ return result;
+ }
+
+ public String[] returnStrArrayWithArgs(int i, String s, long l) {
+ String[] result = new String[] {"abc" + i, "xyz" + s , "ufo" + l};
+ return result;
+ }
+
+ public String returnStr() {
+ return new Date().toString();
+ }
+
+ public Object returnObject() {
+ return InvokeOriginDemo.class;
+ }
+
+
+ public int recursive(int i) {
+ if (i == 1) {
+ return 1;
+ }
+ return i + recursive(i - 1);
+ }
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo_APM.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo_APM.java
new file mode 100644
index 000000000..a5d41a27d
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo_APM.java
@@ -0,0 +1,85 @@
+package com.taobao.arthas.bytekit.asm.inst;
+
+/**
+ *
+ * @author hengyunabc 2019-03-18
+ *
+ */
+public class InvokeOriginDemo_APM {
+
+ public void returnVoid() {
+ Object o = InstrumentApi.invokeOrigin();
+ System.out.println(o);
+ }
+
+ public Void returnVoidObject() {
+ Void v = InstrumentApi.invokeOrigin();
+ System.out.println(v);
+ return v;
+ }
+
+ public int returnInt(int i) {
+ System.out.println("before");
+ int value = InstrumentApi.invokeOrigin();
+ System.out.println("after");
+ return value + 123;
+ }
+
+ public int returnIntToObject(int i) {
+ Object value = InstrumentApi.invokeOrigin();
+ return 9998 + (Integer) value;
+ }
+
+ public int returnIntToInteger(int i) {
+
+ Integer ixx = InstrumentApi.invokeOrigin();
+
+ return ixx + 9998;
+ }
+
+ public static int returnIntStatic(int i) {
+ int result = InstrumentApi.invokeOrigin();
+ return 9998 + result;
+ }
+
+ public long returnLong() {
+ long result = InstrumentApi.invokeOrigin();
+ return 9998L + result;
+ }
+
+ public long returnLongToObject() {
+ Long lll = InstrumentApi.invokeOrigin();
+ return 9998L + lll;
+ }
+
+ public String[] returnStrArray() {
+ String[] result = InstrumentApi.invokeOrigin();
+ System.err.println(result);
+ return result;
+ }
+
+ public String[] returnStrArrayWithArgs(int i, String s, long l) {
+ System.out.println(i);
+ String[] result = InstrumentApi.invokeOrigin();
+ result[0] = "fff";
+ return result;
+ }
+
+ public String returnStr() {
+ System.err.println("ssss");
+ Object result = InstrumentApi.invokeOrigin();
+ return "hello" + result;
+ }
+
+ public Object returnObject() {
+ InstrumentApi.invokeOrigin();
+ return InvokeOriginDemo.class;
+ }
+
+ public int recursive(int i) {
+ int result = InstrumentApi.invokeOrigin();
+
+ System.err.println(result);
+ return result;
+ }
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginTest.java
new file mode 100644
index 000000000..4410b1a31
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginTest.java
@@ -0,0 +1,178 @@
+package com.taobao.arthas.bytekit.asm.inst;
+
+import java.io.IOException;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import com.alibaba.arthas.deps.org.objectweb.asm.Type;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
+
+import com.taobao.arthas.bytekit.asm.inst.impl.InstrumentImpl;
+import com.taobao.arthas.bytekit.utils.AsmUtils;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+import com.taobao.arthas.bytekit.utils.VerifyUtils;
+
+/**
+ *
+ * @author hengyunabc 2019-03-18
+ *
+ */
+public class InvokeOriginTest {
+
+ ClassNode apmClassNode;
+ ClassNode originClassNode;
+
+ ClassNode targetClassNode;
+
+ @Rule
+ public TestName testName = new TestName();
+
+ @BeforeClass
+ public static void beforeClass() throws IOException {
+
+ }
+
+ @Before
+ public void before() throws IOException {
+ apmClassNode = AsmUtils.loadClass(InvokeOriginDemo_APM.class);
+ originClassNode = AsmUtils.loadClass(InvokeOriginDemo.class);
+
+ byte[] renameClass = AsmUtils.renameClass(AsmUtils.toBytes(apmClassNode),
+ Type.getObjectType(originClassNode.name).getClassName());
+
+ apmClassNode = AsmUtils.toClassNode(renameClass);
+
+ targetClassNode = AsmUtils.copy(originClassNode);
+ }
+
+ private Object replace(String methodName) throws Exception {
+ System.err.println(methodName);
+ for (MethodNode methodNode : apmClassNode.methods) {
+ if (methodNode.name.equals(methodName)) {
+ methodNode = AsmUtils.removeLineNumbers(methodNode);
+ // 从原来的类里查找对应的函数
+ MethodNode findMethod = AsmUtils.findMethod(originClassNode.methods, methodNode);
+ if (findMethod != null) {
+ MethodNode methodNode2 = InstrumentImpl.replaceInvokeOrigin(originClassNode.name, findMethod,
+ methodNode);
+
+ System.err.println(Decompiler.toString(methodNode2));
+
+ AsmUtils.replaceMethod(targetClassNode, methodNode2);
+
+ } else {
+
+ }
+ }
+ }
+
+ byte[] resutlBytes = AsmUtils.toBytes(targetClassNode);
+
+ System.err.println("=================");
+
+ System.err.println(Decompiler.decompile(resutlBytes));
+
+ // System.err.println(AsmUtils.toASMCode(resutlBytes));
+
+ VerifyUtils.asmVerify(resutlBytes);
+ return VerifyUtils.instanceVerity(resutlBytes);
+ }
+
+ @Test
+ public void test_returnVoid() throws Exception {
+ String methodName = testName.getMethodName().substring("test_".length());
+ Object object = replace(methodName);
+
+ Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(null);
+ }
+
+ @Test
+ public void test_returnVoidObject() throws Exception {
+ String methodName = testName.getMethodName().substring("test_".length());
+ Object object = replace(methodName);
+ Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(null);
+ }
+
+ @Test
+ public void test_returnInt() throws Exception {
+ String methodName = testName.getMethodName().substring("test_".length());
+ Object object = replace(methodName);
+ Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 123);
+ }
+
+ @Test
+ public void test_returnIntToObject() throws Exception {
+ String methodName = testName.getMethodName().substring("test_".length());
+ Object object = replace(methodName);
+ Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 9998);
+ }
+
+ @Test
+ public void test_returnIntToInteger() throws Exception {
+ String methodName = testName.getMethodName().substring("test_".length());
+ Object object = replace(methodName);
+ Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 9998);
+ }
+
+ @Test
+ public void test_returnIntStatic() throws Exception {
+ String methodName = testName.getMethodName().substring("test_".length());
+ Object object = replace(methodName);
+ Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 9998);
+ }
+
+ @Test
+ public void test_returnLong() throws Exception {
+ String methodName = testName.getMethodName().substring("test_".length());
+ Object object = replace(methodName);
+ Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(9998L + 9998);
+ }
+
+ @Test
+ public void test_returnLongToObject() throws Exception {
+ String methodName = testName.getMethodName().substring("test_".length());
+ Object object = replace(methodName);
+ Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(9998L + 9998);
+ }
+
+ @Test
+ public void test_returnStrArray() throws Exception {
+ String methodName = testName.getMethodName().substring("test_".length());
+ Object object = replace(methodName);
+ Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(new String[] { "abc", "xyz", "ufo" });
+ }
+
+ @Test
+ public void test_returnStrArrayWithArgs() throws Exception {
+ String methodName = testName.getMethodName().substring("test_".length());
+ Object object = replace(methodName);
+ Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123, "sss", 777L))
+ .isEqualTo(new Object[] { "fff", "xyz" + "sss", "ufo" + 777 });
+ }
+
+ @Test
+ public void test_returnStr() throws Exception {
+ String methodName = testName.getMethodName().substring("test_".length());
+ Object object = replace(methodName);
+ Assertions.assertThat(VerifyUtils.invoke(object, methodName)).asString().startsWith("hello");
+ }
+
+ @Test
+ public void test_returnObject() throws Exception {
+ String methodName = testName.getMethodName().substring("test_".length());
+ Object object = replace(methodName);
+ Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(object.getClass());
+ }
+
+ @Test
+ public void test_recursive() throws Exception {
+ String methodName = testName.getMethodName().substring("test_".length());
+ Object object = replace(methodName);
+ Assertions.assertThat(VerifyUtils.invoke(object, methodName, 100)).isEqualTo((100 + 1) * 100 / 2);
+ }
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtEnterTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtEnterTest.java
new file mode 100644
index 000000000..5dead641f
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtEnterTest.java
@@ -0,0 +1,89 @@
+package com.taobao.arthas.bytekit.asm.interceptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.springframework.boot.test.rule.OutputCapture;
+
+import com.taobao.arthas.bytekit.asm.binding.Binding;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtEnter;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+
+public class AtEnterTest {
+
+ @Rule
+ public ExpectedException expectedEx = ExpectedException.none();
+
+ @Rule
+ public OutputCapture capture = new OutputCapture();
+
+ public static class Sample {
+
+ long longField;
+ String strField;
+ static int intField;
+
+ public int hello(String str, boolean exception) {
+ if (exception) {
+ throw new RuntimeException("test exception");
+ }
+ return str.length();
+ }
+
+ public long toBeInvoke(int i , long l, String s, long ll) {
+ return l + ll;
+ }
+
+ public void testInvokeArgs() {
+ toBeInvoke(1, 123L, "abc", 100L);
+ }
+
+ }
+
+ public static class TestPrintSuppressHandler {
+
+ @ExceptionHandler(inline = true)
+ public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) {
+ System.err.println("exception handler: " + clazz);
+ e.printStackTrace();
+ }
+ }
+
+ public static class EnterInterceptor {
+
+ @AtEnter(inline = true
+ , suppress = RuntimeException.class, suppressHandler = TestPrintSuppressHandler.class
+ )
+ public static long onEnter(
+ @Binding.This Object object, @Binding.Class Object clazz,
+ @Binding.Field(name = "longField") long longField,
+ @Binding.Field(name = "longField") Object longFieldObject,
+ @Binding.Field(name = "intField") int intField,
+ @Binding.Field(name = "strField") String strField,
+ @Binding.Field(name = "intField") Object intFielObject
+ ) {
+ System.err.println("onEnter, object:" + object);
+ return 123L;
+ }
+
+ }
+
+
+
+ @Test
+ public void testEnter() throws Exception {
+ TestHelper helper = TestHelper.builder().interceptorClass(EnterInterceptor.class).methodMatcher("hello")
+ .redefine(true);
+ byte[] bytes = helper.process(Sample.class);
+
+ new Sample().hello("abc", false);
+
+ System.err.println(Decompiler.decompile(bytes));
+
+ assertThat(capture.toString()).contains("onEnter, object:");
+ }
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExceptionExitTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExceptionExitTest.java
new file mode 100644
index 000000000..65adc4ad9
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExceptionExitTest.java
@@ -0,0 +1,84 @@
+package com.taobao.arthas.bytekit.asm.interceptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.springframework.boot.test.rule.OutputCapture;
+
+import com.taobao.arthas.bytekit.asm.binding.Binding;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExceptionExit;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+
+public class AtExceptionExitTest {
+
+ @Rule
+ public ExpectedException expectedEx = ExpectedException.none();
+
+ @Rule
+ public OutputCapture capture = new OutputCapture();
+
+ public static class Sample {
+
+ long longField;
+ String strField;
+ static int intField;
+
+ public int hello(String str, boolean exception) {
+ if (exception) {
+ throw new RuntimeException("test exception");
+ }
+ return str.length();
+ }
+
+ public long toBeInvoke(int i , long l, String s, long ll) {
+ return l + ll;
+ }
+
+ public void testInvokeArgs() {
+ toBeInvoke(1, 123L, "abc", 100L);
+ }
+
+ }
+
+ public static class TestPrintSuppressHandler {
+
+ @ExceptionHandler(inline = true)
+ public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) {
+ System.err.println("exception handler: " + clazz);
+ System.err.println(e.getMessage());
+ assertThat(e).hasMessage("exception for ExceptionHandler");
+ }
+ }
+
+ public static class ExceptionExitInterceptor {
+ @AtExceptionExit(inline = false, onException = RuntimeException.class ,suppress = Throwable.class, suppressHandler = TestPrintSuppressHandler.class)
+ public static void onExceptionExit(@Binding.Throwable RuntimeException ex, @Binding.This Object object,
+ @Binding.Class Object clazz) {
+ System.err.println("AtExceptionExit, ex:" + ex);
+ throw new RuntimeException("exception for ExceptionHandler");
+ }
+ }
+
+
+ @Test
+ public void testExecptionExitException() throws Exception {
+
+ TestHelper helper = TestHelper.builder().interceptorClass(ExceptionExitInterceptor.class).methodMatcher("hello")
+ .redefine(true);
+ byte[] bytes = helper.process(Sample.class);
+
+ System.err.println(Decompiler.decompile(bytes));
+ try {
+ new Sample().hello("abc", true);
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(RuntimeException.class).hasMessageContaining("test exception");
+ }
+
+ assertThat(capture.toString()).contains("AtExceptionExit, ex:");
+
+ }
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExitTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExitTest.java
new file mode 100644
index 000000000..74282e6f7
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExitTest.java
@@ -0,0 +1,93 @@
+package com.taobao.arthas.bytekit.asm.interceptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.boot.test.rule.OutputCapture;
+
+import com.taobao.arthas.bytekit.asm.binding.Binding;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExit;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+
+public class AtExitTest {
+ @Rule
+ public OutputCapture capture = new OutputCapture();
+
+ static class Sample {
+ long longField;
+ int intField;
+ String strField;
+
+ public void voidExit() {
+
+ }
+
+ public long longExit() {
+ return 100L;
+ }
+
+ public static long staticExit() {
+ return 999L;
+ }
+ }
+
+ public static class TestPrintSuppressHandler {
+
+ @ExceptionHandler(inline = false)
+ public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) {
+ System.err.println("exception handler: " + clazz);
+ e.printStackTrace();
+ }
+ }
+
+ public static class TestAccessInterceptor {
+ @AtExit(inline = false)
+ public static void atExit(@Binding.This Object object,
+ @Binding.Class Object clazz
+ ,
+ @Binding.Return Object re
+ ) {
+ System.err.println("AtFieldAccess: this" + object);
+ }
+ }
+
+ public static class ChangeReturnInterceptor {
+
+ @AtExit(inline = false, suppress = RuntimeException.class, suppressHandler = TestPrintSuppressHandler.class)
+ public static Object onExit(@Binding.This Object object, @Binding.Class Object clazz) {
+ System.err.println("onExit, object:" + object);
+ return 123L;
+ }
+ }
+
+ @Test
+ public void testExit() throws Exception {
+ TestHelper helper = TestHelper.builder().interceptorClass(TestAccessInterceptor.class).methodMatcher("voidExit")
+ .redefine(true);
+ byte[] bytes = helper.process(Sample.class);
+
+ new Sample().voidExit();
+
+ System.err.println(Decompiler.decompile(bytes));
+
+ assertThat(capture.toString()).contains("AtFieldAccess: this");
+ }
+
+
+ @Test
+ public void testExitAndChangeReturn() throws Exception {
+
+ TestHelper helper = TestHelper.builder().interceptorClass(ChangeReturnInterceptor.class).methodMatcher("longExit")
+ .redefine(true);
+ byte[] bytes = helper.process(Sample.class);
+
+ System.err.println(Decompiler.decompile(bytes));
+
+ long re = new Sample().longExit();
+
+ assertThat(re).isEqualTo(123);
+ assertThat(capture.toString()).contains("onExit, object:");
+ }
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtFieldAccessTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtFieldAccessTest.java
new file mode 100644
index 000000000..ca75af6cb
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtFieldAccessTest.java
@@ -0,0 +1,60 @@
+package com.taobao.arthas.bytekit.asm.interceptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.boot.test.rule.OutputCapture;
+
+import com.taobao.arthas.bytekit.asm.binding.Binding;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtFieldAccess;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+
+public class AtFieldAccessTest {
+ @Rule
+ public OutputCapture capture = new OutputCapture();
+
+ class Sample {
+ long longField;
+ int intField;
+ String strField;
+
+ public int testReadField(int ii) {
+ longField = 999;
+ return 123;
+ }
+ }
+
+ public static class TestPrintSuppressHandler {
+
+ @ExceptionHandler(inline = false)
+ public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) {
+ System.err.println("exception handler: " + clazz);
+ e.printStackTrace();
+ }
+ }
+
+ public static class FieldAccessInterceptor {
+ @AtFieldAccess(name = "longField" , inline =false)
+ public static void onFieldAccess(@Binding.This Object object,
+ @Binding.Class Object clazz) {
+ System.err.println("AtFieldAccess: this" + object);
+ }
+ }
+
+ @Test
+ public void testEnter() throws Exception {
+ TestHelper helper = TestHelper.builder().interceptorClass(FieldAccessInterceptor.class).methodMatcher("testReadField")
+ .redefine(true);
+ byte[] bytes = helper.process(Sample.class);
+
+ new Sample().testReadField(100);
+
+ System.err.println(Decompiler.decompile(bytes));
+
+ assertThat(capture.toString()).contains("AtFieldAccess: this");
+ }
+
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtInvokeTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtInvokeTest.java
new file mode 100644
index 000000000..940679013
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtInvokeTest.java
@@ -0,0 +1,101 @@
+package com.taobao.arthas.bytekit.asm.interceptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.boot.test.rule.OutputCapture;
+
+import com.taobao.arthas.bytekit.asm.binding.Binding;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvoke;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+
+public class AtInvokeTest {
+ @Rule
+ public OutputCapture capture = new OutputCapture();
+
+ static class Sample {
+ long longField;
+ int intField;
+ String strField;
+
+ public Sample(int i, long l, String s) {
+ staticToBeCall(i, l, s);
+ aaa("aaa");
+ }
+
+ public int testCall(int ii) {
+ toBeCall(ii, 123L, "");
+ System.err.println("abc");
+ aaa("abc");
+ return 123;
+ }
+
+
+ public void aaa(String aaa) {
+ return ;
+ }
+
+ public long toBeCall(int i , long l, String s) {
+ return l + i;
+ }
+
+ public static long staticToBeCall(int i , long l, String s) {
+ return l + i;
+ }
+ }
+
+ public static class TestPrintSuppressHandler {
+
+ @ExceptionHandler(inline = false)
+ public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) {
+ System.err.println("exception handler: " + clazz);
+ e.printStackTrace();
+ }
+ }
+
+ public static class TestAccessInterceptor {
+ @AtInvoke(name = "", inline = false, whenComplete=false, excludes = {"System."})
+ public static void onInvoke(
+ @Binding.This Object object,
+ @Binding.Class Object clazz
+ ,
+ @Binding.InvokeArgs Object[] args
+ ) {
+ System.err.println("onInvoke: this" + object);
+ }
+
+ @AtInvoke(name = "toBeCall", inline = false, whenComplete = true)
+ public static void onInvokeAfter(
+ @Binding.This Object object,
+ @Binding.Class Object clazz
+ ,
+ @Binding.InvokeReturn Object invokeReturn
+ ,
+ @Binding.InvokeMethodDeclaration String declaration
+ ) {
+ System.err.println("onInvokeAfter: this" + object);
+ System.err.println("declaration: " + declaration);
+ assertThat(declaration).isEqualTo("long toBeCall(int, long, java.lang.String)");
+
+ System.err.println("invokeReturn: " + invokeReturn);
+ assertThat(invokeReturn).isEqualTo(100 + 123L);
+ }
+ }
+
+ @Test
+ public void testInvokeBefore() throws Exception {
+ TestHelper helper = TestHelper.builder().interceptorClass(TestAccessInterceptor.class).methodMatcher("testCall")
+ .redefine(true);
+ byte[] bytes = helper.process(Sample.class);
+
+ new Sample(100, 100L, "").testCall(100);
+
+ System.err.println(Decompiler.decompile(bytes));
+
+ assertThat(capture.toString()).contains("onInvoke: this");
+ }
+
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtLineTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtLineTest.java
new file mode 100644
index 000000000..c02100781
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtLineTest.java
@@ -0,0 +1,85 @@
+package com.taobao.arthas.bytekit.asm.interceptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Arrays;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.boot.test.rule.OutputCapture;
+
+import com.taobao.arthas.bytekit.asm.binding.Binding;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtLine;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+
+public class AtLineTest {
+ @Rule
+ public OutputCapture capture = new OutputCapture();
+
+ static class Sample {
+
+ public int testLine(int i) {
+ String s = "" + i;
+ if(i > 0) {
+ String abc = s + i;
+ i++;
+ i = i * 100
+ + i
+ - 100 + Math.max(100, i);
+ i += s.length() + abc.length();
+ }else {
+ if(i == -1) {
+ try {
+ System.err.println("i is -1");
+ throw new RuntimeException();
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ }
+
+ }
+ }
+ return i * 2;
+ }
+
+ }
+
+ public static class TestAccessInterceptor {
+
+ @AtLine(lines = { -1}, inline = false)
+ public static void atLine(
+ @Binding.This Object object,
+ @Binding.Class Object clazz
+ ,
+ @Binding.Line int line,
+ @Binding.Args Object[] args
+ ,
+ @Binding.ArgNames String[] argNames
+ ,
+ @Binding.LocalVars Object[] vars,
+ @Binding.LocalVarNames String[] varNames
+ ) {
+ System.err.println("atLine: this" + object);
+ System.err.println("line: " + line);
+ System.err.println("args: " + Arrays.toString(args));
+ System.err.println("argNames: " + Arrays.toString(argNames));
+
+ System.err.println("vars: " + Arrays.toString(vars));
+ System.err.println("varNames: " + Arrays.toString(varNames));
+ }
+ }
+
+ @Test
+ public void testLine() throws Exception {
+ TestHelper helper = TestHelper.builder().interceptorClass(TestAccessInterceptor.class).methodMatcher("*")
+ .redefine(true);
+ byte[] bytes = helper.process(Sample.class);
+
+ new Sample().testLine(100);
+
+ System.err.println(Decompiler.decompile(bytes));
+
+ assertThat(capture.toString()).contains("atLine: this");
+ }
+
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncEnterTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncEnterTest.java
new file mode 100644
index 000000000..27d8ef7ca
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncEnterTest.java
@@ -0,0 +1,89 @@
+package com.taobao.arthas.bytekit.asm.interceptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Arrays;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.boot.test.rule.OutputCapture;
+
+import com.taobao.arthas.bytekit.asm.binding.Binding;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtSyncEnter;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+
+public class AtSyncEnterTest {
+ @Rule
+ public OutputCapture capture = new OutputCapture();
+
+ static class Sample {
+
+ public int testLine(int i) {
+ String s = "" + i;
+ synchronized (s) {
+ if(i > 0) {
+ String abc = s + i;
+ i++;
+ i = i * 100
+ + i
+ - 100 + Math.max(100, i);
+ i += s.length() + abc.length();
+ }else {
+ if(i == -1) {
+ try {
+ System.err.println("i is -1");
+ throw new RuntimeException();
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ }
+
+ }
+ }
+ }
+
+ return i * 2;
+ }
+
+ }
+
+ public static class TestInterceptor {
+
+ @AtSyncEnter(whenComplete=false, inline = false)
+ public static void atSyncEnter(
+ @Binding.This Object object,
+ @Binding.Class Object clazz
+ ,
+ @Binding.Args Object[] args
+ ,
+ @Binding.ArgNames String[] argNames
+ ,
+ @Binding.LocalVars Object[] vars,
+ @Binding.LocalVarNames String[] varNames
+ ,
+ @Binding.Monitor Object monitor
+ ) {
+ System.err.println("atSyncEnter: this" + object);
+ System.err.println("args: " + Arrays.toString(args));
+ System.err.println("argNames: " + Arrays.toString(argNames));
+
+ System.err.println("vars: " + Arrays.toString(vars));
+ System.err.println("varNames: " + Arrays.toString(varNames));
+
+ }
+ }
+
+ @Test
+ public void test() throws Exception {
+ TestHelper helper = TestHelper.builder().interceptorClass(TestInterceptor.class).methodMatcher("*")
+ .redefine(true);
+ byte[] bytes = helper.process(Sample.class);
+
+ new Sample().testLine(100);
+
+ System.err.println(Decompiler.decompile(bytes));
+
+ assertThat(capture.toString()).contains("atSyncEnter: this");
+ }
+
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncExitTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncExitTest.java
new file mode 100644
index 000000000..c9c23c6b2
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncExitTest.java
@@ -0,0 +1,89 @@
+package com.taobao.arthas.bytekit.asm.interceptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Arrays;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.boot.test.rule.OutputCapture;
+
+import com.taobao.arthas.bytekit.asm.binding.Binding;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtSyncExit;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+
+public class AtSyncExitTest {
+ @Rule
+ public OutputCapture capture = new OutputCapture();
+
+ static class Sample {
+
+ public int testLine(int i) {
+ String s = "" + i;
+ synchronized (s) {
+ if(i > 0) {
+ String abc = s + i;
+ i++;
+ i = i * 100
+ + i
+ - 100 + Math.max(100, i);
+ i += s.length() + abc.length();
+ }else {
+ if(i == -1) {
+ try {
+ System.err.println("i is -1");
+ throw new RuntimeException();
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ }
+
+ }
+ }
+ }
+
+ return i * 2;
+ }
+
+ }
+
+ public static class TestInterceptor {
+
+ @AtSyncExit(whenComplete=false, inline = false)
+ public static void atSyncExit(
+ @Binding.This Object object,
+ @Binding.Class Object clazz
+ ,
+ @Binding.Args Object[] args
+ ,
+ @Binding.ArgNames String[] argNames
+ ,
+ @Binding.LocalVars Object[] vars,
+ @Binding.LocalVarNames String[] varNames
+ ,
+ @Binding.Monitor Object monitor
+ ) {
+ System.err.println("atSyncExit: this" + object);
+ System.err.println("args: " + Arrays.toString(args));
+ System.err.println("argNames: " + Arrays.toString(argNames));
+
+ System.err.println("vars: " + Arrays.toString(vars));
+ System.err.println("varNames: " + Arrays.toString(varNames));
+
+ }
+ }
+
+ @Test
+ public void test() throws Exception {
+ TestHelper helper = TestHelper.builder().interceptorClass(TestInterceptor.class).methodMatcher("*")
+ .redefine(true);
+ byte[] bytes = helper.process(Sample.class);
+
+ new Sample().testLine(100);
+
+ System.err.println(Decompiler.decompile(bytes));
+
+ assertThat(capture.toString()).contains("atSyncExit: this");
+ }
+
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtThrowTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtThrowTest.java
new file mode 100644
index 000000000..c2d07da5b
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtThrowTest.java
@@ -0,0 +1,76 @@
+package com.taobao.arthas.bytekit.asm.interceptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Arrays;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.boot.test.rule.OutputCapture;
+
+import com.taobao.arthas.bytekit.asm.binding.Binding;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtThrow;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+
+public class AtThrowTest {
+ @Rule
+ public OutputCapture capture = new OutputCapture();
+
+ static class Sample {
+
+ public static long testThrow(int i , long l, String s) {
+ try {
+ if(i < 0) {
+ throw new RuntimeException("eeeee");
+ }
+ } catch (Exception e) {
+
+ System.err.println(e.getMessage());
+ }
+ return l + i;
+ }
+ }
+
+ public static class TestPrintSuppressHandler {
+
+ @ExceptionHandler(inline = false)
+ public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) {
+ System.err.println("exception handler: " + clazz);
+ e.printStackTrace();
+ }
+ }
+
+ public static class TestAccessInterceptor {
+
+ @AtThrow(inline = false)
+ public static void atThrow(
+ @Binding.This Object object,
+ @Binding.Class Object clazz
+ ,
+ @Binding.LocalVars Object[] vars,
+ @Binding.Throwable Throwable t
+ ) {
+ System.err.println("atThrow: this" + object);
+ System.err.println("vars: " + Arrays.toString(vars));
+ System.err.println("t: " + t);
+
+ assertThat(t).hasMessage("eeeee");
+ }
+ }
+
+ @Test
+ public void testThrow() throws Exception {
+ TestHelper helper = TestHelper.builder().interceptorClass(TestAccessInterceptor.class).methodMatcher("testThrow")
+ .redefine(true);
+ byte[] bytes = helper.process(Sample.class);
+
+ Sample.testThrow(-1, 0, null);
+
+ System.err.println(Decompiler.decompile(bytes));
+
+ assertThat(capture.toString()).contains("atThrow: this");
+ }
+
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/TestHelper.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/TestHelper.java
new file mode 100644
index 000000000..4fd32efa5
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/TestHelper.java
@@ -0,0 +1,78 @@
+package com.taobao.arthas.bytekit.asm.interceptor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
+
+import com.taobao.arthas.bytekit.asm.MethodProcessor;
+import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
+import com.taobao.arthas.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser;
+import com.taobao.arthas.bytekit.utils.AgentUtils;
+import com.taobao.arthas.bytekit.utils.AsmUtils;
+import com.taobao.arthas.bytekit.utils.MatchUtils;
+import com.taobao.arthas.bytekit.utils.VerifyUtils;
+
+public class TestHelper {
+
+ private Class> interceptorClass;
+
+ private boolean redefine;
+
+ private String methodMatcher = "*";
+
+ private boolean asmVerity = true;
+
+ public static TestHelper builder() {
+ return new TestHelper();
+ }
+
+ public TestHelper interceptorClass(Class> interceptorClass) {
+ this.interceptorClass = interceptorClass;
+ return this;
+ }
+
+ public TestHelper redefine(boolean redefine) {
+ this.redefine = redefine;
+ return this;
+ }
+
+ public TestHelper methodMatcher(String methodMatcher) {
+ this.methodMatcher = methodMatcher;
+ return this;
+ }
+
+ public byte[] process(Class> transform) throws Exception {
+ DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser();
+
+ List interceptorProcessors = defaultInterceptorClassParser.parse(interceptorClass);
+
+ ClassNode classNode = AsmUtils.loadClass(transform);
+
+ List matchedMethods = new ArrayList();
+ for (MethodNode methodNode : classNode.methods) {
+ if (MatchUtils.wildcardMatch(methodNode.name, methodMatcher)) {
+ matchedMethods.add(methodNode);
+ }
+ }
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ byte[] bytes = AsmUtils.toBytes(classNode);
+ if (asmVerity) {
+ VerifyUtils.asmVerify(bytes);
+ }
+
+ if (redefine) {
+ AgentUtils.redefine(transform, bytes);
+ }
+
+ return bytes;
+ }
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmUtilsTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmUtilsTest.java
new file mode 100644
index 000000000..eef1d9ab4
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmUtilsTest.java
@@ -0,0 +1,144 @@
+package com.taobao.arthas.bytekit.utils;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import com.alibaba.arthas.deps.org.objectweb.asm.ClassWriter;
+import com.alibaba.arthas.deps.org.objectweb.asm.MethodVisitor;
+import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
+import com.alibaba.arthas.deps.org.objectweb.asm.Type;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
+
+import com.taobao.arthas.bytekit.utils.AsmUtils;
+import com.taobao.arthas.bytekit.utils.VerifyUtils;
+
+public class AsmUtilsTest {
+
+ abstract static class TestClass {
+ public static synchronized List sss(int i, long l, List list) throws IOException, ArrayIndexOutOfBoundsException {
+ return null;
+ }
+ protected abstract String hello(String ss);
+ }
+
+ static class TestConstructorClass {
+ public TestConstructorClass(int i, String s) {
+
+ }
+ }
+
+ @Test
+ public void testMethodDeclaration() throws IOException {
+ ClassNode classNode = AsmUtils.loadClass(TestClass.class);
+ MethodNode sss = AsmUtils.findFirstMethod(classNode.methods, "sss");
+
+ MethodNode hello = AsmUtils.findFirstMethod(classNode.methods, "hello");
+
+ MethodNode constructor = AsmUtils.findFirstMethod(AsmUtils.loadClass(TestConstructorClass.class).methods, "");
+
+ String helloDeclaration = AsmUtils.methodDeclaration(Type.getType(TestClass.class), hello);
+ String sssDeclaration = AsmUtils.methodDeclaration(Type.getType(TestClass.class), sss);
+
+ String constructorDeclaration = AsmUtils.methodDeclaration(Type.getType(TestConstructorClass.class), constructor);
+
+ System.err.println(helloDeclaration);
+ System.err.println(sssDeclaration);
+ System.err.println(constructorDeclaration);
+
+ Assertions.assertThat(helloDeclaration).isEqualTo("protected abstract java.lang.String hello(java.lang.String)");
+ Assertions.assertThat(sssDeclaration).isEqualTo(
+ "public static synchronized java.util.List sss(int, long, java.util.List) throws java.io.IOException, java.lang.ArrayIndexOutOfBoundsException");
+ Assertions.assertThat(constructorDeclaration).isEqualTo("public com.taobao.arthas.bytekit.utils.AsmUtilsTest$TestConstructorClass(int, java.lang.String)");
+ }
+
+ public static byte[] emptyMethodBytes() throws Exception {
+ ClassWriter cw = new ClassWriter(0);
+ MethodVisitor mv;
+
+ cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, "LEmptyClass", null, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false);
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "emptyMethod", "()V", null, null);
+ // mv.visitCode();
+ mv.visitInsn(Opcodes.RETURN);
+ // mv.visitMaxs(0, 0);
+ // mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ @Test
+ public void emptyMethodTest() throws Exception {
+
+ byte[] emptyMethodBytes = emptyMethodBytes();
+
+ VerifyUtils.asmVerify(emptyMethodBytes);
+ VerifyUtils.instanceVerity(emptyMethodBytes);
+
+ ClassNode classNode = AsmUtils.toClassNode(emptyMethodBytes);
+ MethodNode methodNode = AsmUtils.findFirstMethod(classNode.methods, "emptyMethod");
+
+ AbstractInsnNode first = methodNode.instructions.getFirst();
+ AbstractInsnNode last = methodNode.instructions.getLast();
+ System.err.println(first);
+ System.err.println(last);
+
+ int size = methodNode.instructions.size();
+ for (int i = 0; i < size; ++i) {
+ System.err.println(methodNode.instructions.get(i));
+ }
+
+ // String asmCode = AsmUtils.toASMCode(classNode);
+ // System.err.println(asmCode);
+ }
+
+ private String aaa = "";
+ public void xxx () {
+ aaa = "bbb";
+ }
+
+ @Test
+ public void testFieldAccess() throws IOException {
+ ClassNode classNode = AsmUtils.loadClass(AsmUtilsTest.class);
+
+ MethodNode methodNode = AsmUtils.findFirstMethod(classNode.methods, "xxx");
+
+ int size = methodNode.instructions.size();
+ for (int i = 0; i < size; ++i) {
+ System.err.println(methodNode.instructions.get(i));
+ }
+
+
+ }
+
+
+ @Test
+ public void testRenameClass() throws Exception {
+ ClassNode classNode = AsmUtils.loadClass(AsmUtilsTest.class);
+
+ byte[] classBytes = AsmUtils.toBytes(classNode);
+
+ byte[] renameClass = AsmUtils.renameClass(classBytes, "com.test.Test.XXX");
+
+ VerifyUtils.asmVerify(renameClass);
+ Object object = VerifyUtils.instanceVerity(renameClass);
+
+ Assertions.assertThat(object.getClass().getName()).isEqualTo("com.test.Test.XXX");
+ }
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/EmptyClass.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/EmptyClass.java
new file mode 100644
index 000000000..4e5399ae4
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/EmptyClass.java
@@ -0,0 +1,9 @@
+package com.taobao.arthas.bytekit.utils;
+
+public class EmptyClass {
+
+ public static void emptyMethod() {
+
+ }
+
+}