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..98eed71aa
--- /dev/null
+++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/VerifyUtils.java
@@ -0,0 +1,74 @@
+package com.taobao.arthas.bytekit.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+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, boolean printResults) throws IOException {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+ ClassReader cr = new ClassReader(inputStream);
+ CheckClassAdapter.verify(cr, true, new PrintWriter(System.out));
+ }
+
+ 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();
+
+ @SuppressWarnings("resource")
+ ClassbyteClassLoader cl = new ClassbyteClassLoader(ClassLoaderUtils.getUrls(ClassLoader.getSystemClassLoader()),
+ 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/TryCatchBlockTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/TryCatchBlockTest.java
new file mode 100644
index 000000000..ec34f6353
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/TryCatchBlockTest.java
@@ -0,0 +1,127 @@
+package com.taobao.arthas.bytekit.asm;
+
+import java.lang.instrument.Instrumentation;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+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.InsnList;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.JumpInsnNode;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.LabelNode;
+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.utils.AsmOpUtils;
+import com.taobao.arthas.bytekit.utils.AsmUtils;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+import com.taobao.arthas.bytekit.utils.VerifyUtils;
+
+import net.bytebuddy.agent.ByteBuddyAgent;
+
+public class TryCatchBlockTest {
+
+ static public class Hello {
+
+ public long sss(String msg, int i, long l) {
+ return 124L;
+ }
+
+ public long toBeCall(int i , long l, String s) {
+ return l + i;
+ }
+
+// public String say(String msg, int i, long l) {
+// i = 0;
+// System.out.println("hello");
+// i = 0;
+// sss(msg, i, l);
+// return "";
+// }
+
+ public int say(int ii) {
+ toBeCall(ii, 123L, "");
+ return 123;
+ }
+ }
+
+ public static void ttt() throws Throwable {
+ try {
+ System.out.println();
+ } catch (Throwable e) {
+ e.printStackTrace();
+ throw e;
+ }
+ try {
+ System.out.println();
+ } catch (Throwable e) {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+
+ @Test
+ public void test() throws Exception {
+
+ Instrumentation instrumentation = ByteBuddyAgent.install();
+
+ ClassNode classNode = AsmUtils.loadClass(Hello.class);
+
+ List methods = AsmUtils.findMethods(classNode.methods, "say");
+
+ MethodNode methodNode = methods.get(0);
+
+ AbstractInsnNode insnNode = methodNode.instructions.getFirst();
+
+ List methodInsnNodes = new ArrayList();
+
+ while (insnNode != null) {
+ if (insnNode instanceof MethodInsnNode) {
+ MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
+ methodInsnNodes.add(methodInsnNode);
+ }
+ insnNode = insnNode.getNext();
+ }
+
+ for (MethodInsnNode methodInsnNode : methodInsnNodes) {
+ TryCatchBlock tryCatchBlock = new TryCatchBlock(methodNode);
+
+ InsnList toInsert = new InsnList();
+
+ LabelNode gotoDest = new LabelNode();
+
+ LabelNode startLabelNode = tryCatchBlock.getStartLabelNode();
+ LabelNode endLabelNode = tryCatchBlock.getEndLabelNode();
+
+ toInsert.add(new JumpInsnNode(Opcodes.GOTO, gotoDest));
+ toInsert.add(endLabelNode);
+
+ AsmOpUtils.dup(toInsert);
+ toInsert.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getType(Throwable.class).getInternalName(),
+ "printStackTrace", "()V", false));
+
+ AsmOpUtils.throwException(toInsert);
+
+ toInsert.add(gotoDest);
+
+ methodNode.instructions.insertBefore(methodInsnNode, startLabelNode);
+ methodNode.instructions.insert(methodInsnNode, toInsert);
+
+ tryCatchBlock.sort();
+ }
+
+ byte[] bytes = AsmUtils.toBytes(classNode);
+
+ String decompile = Decompiler.decompile(bytes);
+ System.err.println(decompile);
+
+ VerifyUtils.asmVerify(bytes, true);
+
+ VerifyUtils.instanceVerity(bytes);
+
+ }
+
+}
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..8dc26a9f1
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemoTest.java
@@ -0,0 +1,96 @@
+package com.taobao.arthas.bytekit.asm.inst;
+
+import java.util.List;
+
+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.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));
+
+ 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..a90dc7177
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtEnterTest.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.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,
+ @Binding.MethodName String methodName,
+ @Binding.MethodDesc String methodDesc
+ ) {
+ System.err.println("onEnter, object:" + object);
+ System.err.println("onEnter, methodName:" + methodName);
+ System.err.println("onEnter, methodDesc:" + methodDesc);
+ return 123L;
+ }
+
+ }
+
+
+
+ @Test
+ public void testEnter() throws Exception {
+ TestHelper helper = TestHelper.builder().interceptorClass(EnterInterceptor.class).methodMatcher("hello")
+ .reTransform(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..323e85863
--- /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")
+ .reTransform(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..1462500ad
--- /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")
+ .reTransform(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")
+ .reTransform(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..45024ce2d
--- /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")
+ .reTransform(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..f6c565829
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtInvokeTest.java
@@ -0,0 +1,105 @@
+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.Line int line,
+ @Binding.InvokeArgs Object[] args
+ ) {
+ System.err.println("onInvoke: line: " + line);
+ 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
+ // TODO fix com.taobao.arthas.bytekit.asm.location.Location.InvokeLocation satck save
+ 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..2bcba1672
--- /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("*")
+ .reTransform(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..91ccbb512
--- /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("*")
+ .reTransform(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..9aef147d4
--- /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("*")
+ .reTransform(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..d696ca978
--- /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")
+ .reTransform(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..aff87158b
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/TestHelper.java
@@ -0,0 +1,94 @@
+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;
+
+/**
+ *
+ * @author hengyunabc
+ *
+ */
+public class TestHelper {
+
+ private Class> interceptorClass;
+
+ private boolean redefine;
+
+ private boolean reTransform;
+
+ 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 reTransform(boolean reTransform) {
+ this.reTransform = reTransform;
+ 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);
+ }
+
+ if (reTransform) {
+ AgentUtils.reTransform(transform, bytes);
+ }
+
+ return bytes;
+ }
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/GroupFilterTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/GroupFilterTest.java
new file mode 100644
index 000000000..9aa9cae02
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/GroupFilterTest.java
@@ -0,0 +1,318 @@
+package com.taobao.arthas.bytekit.asm.location.filter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+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.MethodProcessor;
+import com.taobao.arthas.bytekit.asm.binding.Binding;
+import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtEnter;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExceptionExit;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExit;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvoke;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvokeException;
+import com.taobao.arthas.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser;
+import com.taobao.arthas.bytekit.asm.location.LocationType;
+import com.taobao.arthas.bytekit.utils.AsmUtils;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+import com.taobao.arthas.bytekit.utils.MatchUtils;
+import com.taobao.arthas.bytekit.utils.VerifyUtils;
+
+/**
+ *
+ * @author hengyunabc 2020-05-04
+ *
+ */
+public class GroupFilterTest {
+
+ public static class SpyInterceptor {
+
+ @AtEnter(inline = true)
+ public static void atEnter(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.MethodName String methodName, @Binding.MethodDesc String methodDesc,
+ @Binding.Args Object[] args) {
+ SpyAPI.atEnter(clazz, methodName, methodDesc, target, args);
+ }
+
+ @AtExit(inline = true)
+ public static void atExit(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.MethodName String methodName, @Binding.MethodDesc String methodDesc,
+ @Binding.Args Object[] args, @Binding.Return Object returnObj) {
+ SpyAPI.atExit(clazz, methodName, methodDesc, target, args, returnObj);
+ }
+
+ @AtExceptionExit(inline = true)
+ public static void atExceptionExit(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.MethodName String methodName, @Binding.MethodDesc String methodDesc,
+ @Binding.Args Object[] args, @Binding.Throwable Throwable throwable) {
+ SpyAPI.atExceptionExit(clazz, methodName, methodDesc, target, args, throwable);
+ }
+ }
+
+ public static class SpyTraceInterceptor {
+ @AtInvoke(name = "", inline = true, whenComplete = false, excludes = { "java.**", "**SpyAPI**" })
+ public static void onInvoke(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.InvokeInfo String invokeInfo) {
+ SpyAPI.atBeforeInvoke(clazz, invokeInfo, target);
+ }
+
+ @AtInvoke(name = "", inline = true, whenComplete = true, excludes = { "java.**", "**SpyAPI**" })
+ public static void onInvokeAfter(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.InvokeInfo String invokeInfo) {
+ SpyAPI.atAfterInvoke(clazz, invokeInfo, target);
+ }
+
+ @AtInvokeException(name = "", inline = true, excludes = { "java.**", "**SpyAPI**" })
+ public static void onInvokeException(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.InvokeInfo String invokeInfo, @Binding.Throwable Throwable throwable) {
+ SpyAPI.atInvokeException(clazz, invokeInfo, target, throwable);
+ }
+ }
+
+ public static class SpyTraceInterceptor2 {
+ @AtInvoke(name = "", inline = true, whenComplete = false, excludes = { "**SpyAPI**" })
+ public static void onInvoke(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.InvokeInfo String invokeInfo) {
+
+ SpyAPI.atBeforeInvoke(clazz, invokeInfo, target);
+ }
+
+ @AtInvoke(name = "", inline = true, whenComplete = true, excludes = { "**SpyAPI**" })
+ public static void onInvokeAfter(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.InvokeInfo String invokeInfo) {
+ SpyAPI.atAfterInvoke(clazz, invokeInfo, target);
+ }
+
+ @AtInvokeException(name = "", inline = true, excludes = { "**SpyAPI**" })
+ public static void onInvokeException(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.InvokeInfo String invokeInfo, @Binding.Throwable Throwable throwable) {
+ SpyAPI.atInvokeException(clazz, invokeInfo, target, throwable);
+ }
+ }
+
+ public static class SpyAPI {
+ public static void atEnter(Class> clazz, String methodName, String methodDesc, Object target, Object[] args) {
+
+ }
+
+ public static void atExceptionExit(Class> clazz, String methodName, String methodDesc, Object target,
+ Object[] args, Throwable throwable) {
+
+ }
+
+ public static void atExit(Class> clazz, String methodName, String methodDesc, Object target, Object[] args,
+ Object returnObj) {
+
+ }
+
+ public static void atBeforeInvoke(Class> clazz, String invokeInfo,
+ Object target) {
+
+ }
+
+ public static void atInvokeException(Class> clazz, String invokeInfo,
+ Object target, Throwable throwable) {
+
+ }
+
+ public static void atAfterInvoke(Class> clazz, String invokeInfo,
+ Object target) {
+
+ }
+
+ }
+
+ public static class TestAAA {
+
+ int i = 0;
+ long l = 0;
+
+ public TestAAA() {
+ xxx(1, 134L);
+ }
+
+ public String hello(String str) {
+
+ String result = "";
+
+ xxx(i, l);
+
+ return result;
+
+ }
+
+ public String xxx(int i, long l) {
+ return "" + i + l;
+ }
+
+ }
+
+ @Test
+ public void test() throws Exception {
+ boolean skipJDKTrace = false;
+
+ DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser();
+
+ List interceptorProcessors = defaultInterceptorClassParser
+ .parse(SpyInterceptor.class);
+
+ Class> spyTraceInterceptorClass = SpyTraceInterceptor2.class;
+ if (skipJDKTrace == false) {
+ spyTraceInterceptorClass = SpyTraceInterceptor.class;
+ }
+ List traceInvokeProcessors = defaultInterceptorClassParser
+ .parse(spyTraceInterceptorClass);
+ interceptorProcessors.addAll(traceInvokeProcessors);
+
+
+ ClassNode classNode = AsmUtils.loadClass(TestAAA.class);
+
+ List matchedMethods = new ArrayList();
+ for (MethodNode methodNode : classNode.methods) {
+ if (MatchUtils.wildcardMatch(methodNode.name, "*")) {
+ matchedMethods.add(methodNode);
+ }
+ }
+
+ GroupLocationFilter groupLocationFilter = new GroupLocationFilter();
+
+ LocationFilter enterFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atEnter",
+ LocationType.ENTER);
+ LocationFilter existFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atExit",
+ LocationType.EXIT);
+ LocationFilter exceptionFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class),
+ "atExceptionExit", LocationType.EXCEPTION_EXIT);
+
+ groupLocationFilter.addFilter(enterFilter);
+ groupLocationFilter.addFilter(existFilter);
+ groupLocationFilter.addFilter(exceptionFilter);
+
+ LocationFilter invokeBeforeFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), "atBeforeInvoke",
+ LocationType.INVOKE);
+ LocationFilter invokeAfterFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
+ "atInvokeException", LocationType.INVOKE_COMPLETED);
+ LocationFilter invokeExceptionFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
+ "atInvokeException", LocationType.INVOKE_EXCEPTION_EXIT);
+ groupLocationFilter.addFilter(invokeBeforeFilter);
+ groupLocationFilter.addFilter(invokeAfterFilter);
+ groupLocationFilter.addFilter(invokeExceptionFilter);
+
+
+ for(int i = 0 ; i < 20 ; ++i) {
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+ }
+
+
+ byte[] bytes = AsmUtils.toBytes(classNode);
+ VerifyUtils.asmVerify(bytes);
+
+ VerifyUtils.instanceVerity(bytes);
+
+ System.out.println(Decompiler.decompile(bytes));
+
+ ClassNode classNode2 = AsmUtils.toClassNode(bytes);
+ for (MethodNode methodNode : classNode2.methods) {
+ if (!methodNode.name.equals("xxx")) {
+ System.err.println("method name: " + methodNode.name);
+ Assertions.assertThat(
+ AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke"))
+ .size().isEqualTo(1);
+ }
+ }
+
+ }
+
+ @Test
+ public void test2() throws Exception {
+
+ DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser();
+
+ List interceptorProcessors = defaultInterceptorClassParser
+ .parse(SpyTraceInterceptor2.class);
+
+ ClassNode classNode = AsmUtils.loadClass(TestAAA.class);
+
+ List matchedMethods = new ArrayList();
+ for (MethodNode methodNode : classNode.methods) {
+ if (MatchUtils.wildcardMatch(methodNode.name, "*")) {
+ matchedMethods.add(methodNode);
+ }
+ }
+
+ LocationFilter enterFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), "atBeforeInvoke",
+ LocationType.INVOKE);
+ LocationFilter existFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
+ "atInvokeException", LocationType.INVOKE_COMPLETED);
+ LocationFilter exceptionFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
+ "atInvokeException", LocationType.INVOKE_EXCEPTION_EXIT);
+ GroupLocationFilter groupLocationFilter = new GroupLocationFilter();
+ groupLocationFilter.addFilter(enterFilter);
+ groupLocationFilter.addFilter(existFilter);
+ groupLocationFilter.addFilter(exceptionFilter);
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ byte[] bytes = AsmUtils.toBytes(classNode);
+ VerifyUtils.asmVerify(bytes);
+
+ VerifyUtils.instanceVerity(bytes);
+
+ System.out.println(Decompiler.decompile(bytes));
+
+ ClassNode classNode2 = AsmUtils.toClassNode(bytes);
+ for (MethodNode methodNode : classNode2.methods) {
+ if (!methodNode.name.equals("xxx")) {
+ System.err.println("method name: " + methodNode.name);
+ Assertions.assertThat(
+ AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke"))
+ .size().isEqualTo(1);
+ }
+ }
+
+ }
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeCheckLocationFilterTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeCheckLocationFilterTest.java
new file mode 100644
index 000000000..7fd58688d
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeCheckLocationFilterTest.java
@@ -0,0 +1,258 @@
+package com.taobao.arthas.bytekit.asm.location.filter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+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.MethodProcessor;
+import com.taobao.arthas.bytekit.asm.binding.Binding;
+import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvoke;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvokeException;
+import com.taobao.arthas.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser;
+import com.taobao.arthas.bytekit.asm.location.LocationType;
+import com.taobao.arthas.bytekit.utils.AsmUtils;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+import com.taobao.arthas.bytekit.utils.MatchUtils;
+import com.taobao.arthas.bytekit.utils.VerifyUtils;
+
+/**
+ *
+ * @author hengyunabc 2020-05-04
+ *
+ */
+public class InvokeCheckLocationFilterTest {
+
+ public static class SpyTraceInterceptor {
+ @AtInvoke(name = "", inline = true, whenComplete = false, excludes = { "java.**", "**SpyAPI**" })
+ public static void onInvoke(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.InvokeMethodDeclaration String methodDesc, @Binding.InvokeMethodOwner String owner,
+ @Binding.InvokeMethodName String methodName) {
+ SpyAPI.atBeforeInvoke(clazz, owner, methodName, methodDesc, target);
+ }
+
+ @AtInvoke(name = "", inline = true, whenComplete = true, excludes = { "java.**", "**SpyAPI**" })
+ public static void onInvokeAfter(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.InvokeMethodDeclaration String methodDesc, @Binding.InvokeMethodOwner String owner,
+ @Binding.InvokeMethodName String methodName) {
+ SpyAPI.atAfterInvoke(clazz, owner, methodName, methodDesc, target);
+ }
+
+ @AtInvokeException(name = "", inline = true, excludes = { "java.**", "**SpyAPI**" })
+ public static void onInvokeException(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.InvokeMethodDeclaration String methodDesc, @Binding.InvokeMethodOwner String owner,
+ @Binding.InvokeMethodName String methodName, @Binding.Throwable Throwable throwable) {
+ SpyAPI.atInvokeException(clazz, owner, methodName, methodDesc, target, throwable);
+ }
+ }
+
+ public static class SpyTraceInterceptor2 {
+ @AtInvoke(name = "", inline = true, whenComplete = false, excludes = { "**SpyAPI**" })
+ public static void onInvoke(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.InvokeMethodDeclaration String methodDesc, @Binding.InvokeMethodOwner String owner,
+ @Binding.InvokeMethodName String methodName) {
+ SpyAPI.atBeforeInvoke(clazz, owner, methodName, methodDesc, target);
+ }
+
+ @AtInvoke(name = "", inline = true, whenComplete = true, excludes = { "**SpyAPI**" })
+ public static void onInvokeAfter(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.InvokeMethodDeclaration String methodDesc, @Binding.InvokeMethodOwner String owner,
+ @Binding.InvokeMethodName String methodName) {
+ SpyAPI.atAfterInvoke(clazz, owner, methodName, methodDesc, target);
+ }
+
+ @AtInvokeException(name = "", inline = true, excludes = { "**SpyAPI**" })
+ public static void onInvokeException(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.InvokeMethodDeclaration String methodDesc, @Binding.InvokeMethodOwner String owner,
+ @Binding.InvokeMethodName String methodName, @Binding.Throwable Throwable throwable) {
+ SpyAPI.atInvokeException(clazz, owner, methodName, methodDesc, target, throwable);
+ }
+ }
+
+ public static class SpyAPI {
+
+ public static void atBeforeInvoke(Class> clazz, String owner, String methodName, String methodDesc,
+ Object target) {
+
+ }
+
+ public static void atInvokeException(Class> clazz, String owner, String methodName, String methodDesc,
+ Object target, Throwable throwable) {
+
+ }
+
+ public static void atAfterInvoke(Class> clazz, String owner, String methodName, String methodDesc,
+ Object target) {
+
+ }
+
+ }
+
+ public static class TestAAA {
+
+ int i = 0;
+ long l = 0;
+
+ public TestAAA() {
+ xxx(1, 134L);
+ }
+
+ public String hello(String str) {
+
+ String result = "";
+
+ xxx(i, l);
+
+ return result;
+
+ }
+
+ public String xxx(int i, long l) {
+ return "" + i + l;
+ }
+
+ }
+
+ @Test
+ public void test() throws Exception {
+
+ DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser();
+
+ List interceptorProcessors = defaultInterceptorClassParser
+ .parse(SpyTraceInterceptor.class);
+
+ ClassNode classNode = AsmUtils.loadClass(TestAAA.class);
+
+ List matchedMethods = new ArrayList();
+ for (MethodNode methodNode : classNode.methods) {
+ if (MatchUtils.wildcardMatch(methodNode.name, "*")) {
+ matchedMethods.add(methodNode);
+ }
+ }
+
+ LocationFilter enterFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), "atBeforeInvoke",
+ LocationType.INVOKE);
+ LocationFilter existFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
+ "atInvokeException", LocationType.INVOKE_COMPLETED);
+ LocationFilter exceptionFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
+ "atInvokeException", LocationType.INVOKE_EXCEPTION_EXIT);
+ GroupLocationFilter groupLocationFilter = new GroupLocationFilter();
+ groupLocationFilter.addFilter(enterFilter);
+ groupLocationFilter.addFilter(existFilter);
+ groupLocationFilter.addFilter(exceptionFilter);
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ byte[] bytes = AsmUtils.toBytes(classNode);
+ VerifyUtils.asmVerify(bytes);
+
+ VerifyUtils.instanceVerity(bytes);
+
+ System.out.println(Decompiler.decompile(bytes));
+
+ ClassNode classNode2 = AsmUtils.toClassNode(bytes);
+ for (MethodNode methodNode : classNode2.methods) {
+ if (!methodNode.name.equals("xxx")) {
+ System.err.println("method name: " + methodNode.name);
+ Assertions.assertThat(
+ AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke"))
+ .size().isEqualTo(1);
+ }
+ }
+
+ }
+
+
+ @Test
+ public void test2() throws Exception {
+
+ DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser();
+
+ List interceptorProcessors = defaultInterceptorClassParser
+ .parse(SpyTraceInterceptor2.class);
+
+ ClassNode classNode = AsmUtils.loadClass(TestAAA.class);
+
+ List matchedMethods = new ArrayList();
+ for (MethodNode methodNode : classNode.methods) {
+ if (MatchUtils.wildcardMatch(methodNode.name, "*")) {
+ matchedMethods.add(methodNode);
+ }
+ }
+
+ LocationFilter enterFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), "atBeforeInvoke",
+ LocationType.INVOKE);
+ LocationFilter existFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
+ "atInvokeException", LocationType.INVOKE_COMPLETED);
+ LocationFilter exceptionFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
+ "atInvokeException", LocationType.INVOKE_EXCEPTION_EXIT);
+ GroupLocationFilter groupLocationFilter = new GroupLocationFilter();
+ groupLocationFilter.addFilter(enterFilter);
+ groupLocationFilter.addFilter(existFilter);
+ groupLocationFilter.addFilter(exceptionFilter);
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ byte[] bytes = AsmUtils.toBytes(classNode);
+ VerifyUtils.asmVerify(bytes);
+
+ VerifyUtils.instanceVerity(bytes);
+
+ System.out.println(Decompiler.decompile(bytes));
+
+ ClassNode classNode2 = AsmUtils.toClassNode(bytes);
+ for (MethodNode methodNode : classNode2.methods) {
+ if (!methodNode.name.equals("xxx")) {
+ System.err.println("method name: " + methodNode.name);
+ Assertions.assertThat(
+ AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke"))
+ .size().isEqualTo(1);
+ }
+ }
+
+ }
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeContainLocationFilterTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeContainLocationFilterTest.java
new file mode 100644
index 000000000..6b12eb9b0
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeContainLocationFilterTest.java
@@ -0,0 +1,164 @@
+package com.taobao.arthas.bytekit.asm.location.filter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+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.MethodProcessor;
+import com.taobao.arthas.bytekit.asm.binding.Binding;
+import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtEnter;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExceptionExit;
+import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExit;
+import com.taobao.arthas.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser;
+import com.taobao.arthas.bytekit.asm.location.LocationType;
+import com.taobao.arthas.bytekit.utils.AsmUtils;
+import com.taobao.arthas.bytekit.utils.Decompiler;
+import com.taobao.arthas.bytekit.utils.MatchUtils;
+import com.taobao.arthas.bytekit.utils.VerifyUtils;
+
+/**
+ *
+ * @author hengyunabc 2020-05-04
+ *
+ */
+public class InvokeContainLocationFilterTest {
+
+ public static class SpyInterceptor {
+
+ @AtEnter(inline = true)
+ public static void atEnter(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.MethodName String methodName, @Binding.MethodDesc String methodDesc,
+ @Binding.Args Object[] args) {
+ SpyAPI.atEnter(clazz, methodName, methodDesc, target, args);
+ }
+
+ @AtExit(inline = true)
+ public static void atExit(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.MethodName String methodName, @Binding.MethodDesc String methodDesc,
+ @Binding.Args Object[] args, @Binding.Return Object returnObj) {
+ SpyAPI.atExit(clazz, methodName, methodDesc, target, args, returnObj);
+ }
+
+ @AtExceptionExit(inline = true)
+ public static void atExceptionExit(@Binding.This Object target, @Binding.Class Class> clazz,
+ @Binding.MethodName String methodName, @Binding.MethodDesc String methodDesc,
+ @Binding.Args Object[] args, @Binding.Throwable Throwable throwable) {
+ SpyAPI.atExceptionExit(clazz, methodName, methodDesc, target, args, throwable);
+ }
+ }
+
+ public static class SpyAPI {
+
+ public static void atEnter(Class> clazz, String methodName, String methodDesc, Object target, Object[] args) {
+
+ }
+
+ public static void atExceptionExit(Class> clazz, String methodName, String methodDesc, Object target,
+ Object[] args, Throwable throwable) {
+
+ }
+
+ public static void atExit(Class> clazz, String methodName, String methodDesc, Object target, Object[] args,
+ Object returnObj) {
+
+ }
+
+ }
+
+ public static class TestAAA {
+
+ int i = 0;
+ long l = 0;
+
+ public String hello(String str) {
+
+ String result = "";
+
+ xxx(i, l);
+
+ return result;
+
+ }
+
+ public String xxx(int i, long l) {
+ return "" + i + l;
+ }
+
+ }
+
+ @Test
+ public void test() throws Exception {
+
+ DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser();
+
+ List interceptorProcessors = defaultInterceptorClassParser.parse(SpyInterceptor.class);
+
+ ClassNode classNode = AsmUtils.loadClass(TestAAA.class);
+
+ List matchedMethods = new ArrayList();
+ for (MethodNode methodNode : classNode.methods) {
+ if (MatchUtils.wildcardMatch(methodNode.name, "*")) {
+ matchedMethods.add(methodNode);
+ }
+ }
+
+ LocationFilter enterFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atEnter",
+ LocationType.ENTER);
+ LocationFilter existFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atExit",
+ LocationType.EXIT);
+ LocationFilter exceptionFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class),
+ "atExceptionExit", LocationType.EXCEPTION_EXIT);
+ GroupLocationFilter groupLocationFilter = new GroupLocationFilter();
+ groupLocationFilter.addFilter(enterFilter);
+ groupLocationFilter.addFilter(existFilter);
+ groupLocationFilter.addFilter(exceptionFilter);
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ for (MethodNode methodNode : matchedMethods) {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ interceptor.process(methodProcessor);
+ }
+ }
+
+ byte[] bytes = AsmUtils.toBytes(classNode);
+ VerifyUtils.asmVerify(bytes);
+
+ System.out.println(Decompiler.decompile(bytes));
+
+ ClassNode classNode2 = AsmUtils.toClassNode(bytes);
+ for (MethodNode methodNode : classNode2.methods) {
+ System.err.println("method name: " + methodNode.name);
+ Assertions
+ .assertThat(AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atEnter"))
+ .size().isEqualTo(1);
+ Assertions
+ .assertThat(AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atExit"))
+ .size().isEqualTo(1);
+ Assertions
+ .assertThat(AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atExceptionExit"))
+ .size().isEqualTo(1);
+ }
+
+ }
+
+}
diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmAnnotationUtilsTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmAnnotationUtilsTest.java
new file mode 100644
index 000000000..366fac9a7
--- /dev/null
+++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmAnnotationUtilsTest.java
@@ -0,0 +1,78 @@
+package com.taobao.arthas.bytekit.utils;
+
+import java.io.IOException;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Arrays;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.springframework.stereotype.Service;
+
+import com.alibaba.arthas.deps.org.objectweb.asm.Type;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode;
+import com.taobao.arthas.bytekit.utils.AsmAnnotationUtils;
+import com.taobao.arthas.bytekit.utils.AsmUtils;
+
+/**
+ *
+ * @author hengyunabc 2020-05-04
+ *
+ */
+public class AsmAnnotationUtilsTest {
+
+ @Target(value = { ElementType.TYPE, ElementType.METHOD })
+ @Retention(value = RetentionPolicy.RUNTIME)
+ public @interface AdviceInfo {
+
+ public String[] adviceInfos();
+ }
+
+ @Service
+ @AdviceInfo(adviceInfos = { "xxxx", "yyy" })
+ static class AAA {
+
+ @AdviceInfo(adviceInfos = { "mmm", "yyy" })
+ public void test() {
+
+ }
+
+ }
+
+ @Service
+ static class BBB {
+ public void test() {
+ }
+ }
+
+ @Test
+ public void test() throws IOException {
+ ClassNode classNodeA = AsmUtils.loadClass(AAA.class);
+
+ ClassNode classNodeB = AsmUtils.loadClass(BBB.class);
+
+ Assertions.assertThat(AsmAnnotationUtils.queryAnnotationInfo(classNodeA.visibleAnnotations,
+ Type.getDescriptor(AdviceInfo.class), "adviceInfos")).isEqualTo(Arrays.asList("xxxx", "yyy"));
+
+ AsmAnnotationUtils.addAnnotationInfo(classNodeA.visibleAnnotations, Type.getDescriptor(AdviceInfo.class),
+ "adviceInfos", "fff");
+
+ Assertions
+ .assertThat(AsmAnnotationUtils.queryAnnotationInfo(classNodeA.visibleAnnotations,
+ Type.getDescriptor(AdviceInfo.class), "adviceInfos"))
+ .isEqualTo(Arrays.asList("xxxx", "yyy", "fff"));
+
+ Assertions.assertThat(AsmAnnotationUtils.queryAnnotationInfo(classNodeB.visibleAnnotations,
+ Type.getDescriptor(AdviceInfo.class), "adviceInfos")).isEmpty();
+
+ AsmAnnotationUtils.addAnnotationInfo(classNodeB.visibleAnnotations, Type.getDescriptor(AdviceInfo.class),
+ "adviceInfos", "fff");
+
+ Assertions.assertThat(AsmAnnotationUtils.queryAnnotationInfo(classNodeB.visibleAnnotations,
+ Type.getDescriptor(AdviceInfo.class), "adviceInfos")).isEqualTo(Arrays.asList("fff"));
+
+ }
+
+}
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() {
+
+ }
+
+}
diff --git a/common/src/main/java/com/taobao/arthas/common/FileUtils.java b/common/src/main/java/com/taobao/arthas/common/FileUtils.java
new file mode 100644
index 000000000..2b58caad5
--- /dev/null
+++ b/common/src/main/java/com/taobao/arthas/common/FileUtils.java
@@ -0,0 +1,150 @@
+package com.taobao.arthas.common;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ *
+ * @see org.apache.commons.io.FileUtils
+ * @author hengyunabc 2020-05-03
+ *
+ */
+public class FileUtils {
+
+ public static File getTempDirectory() {
+ return new File(System.getProperty("java.io.tmpdir"));
+ }
+
+ /**
+ * Writes a byte array to a file creating the file if it does not exist.
+ *
+ * NOTE: As from v1.3, the parent directories of the file will be created if
+ * they do not exist.
+ *
+ * @param file the file to write to
+ * @param data the content to write to the file
+ * @throws IOException in case of an I/O error
+ * @since 1.1
+ */
+ public static void writeByteArrayToFile(final File file, final byte[] data) throws IOException {
+ writeByteArrayToFile(file, data, false);
+ }
+
+ /**
+ * Writes a byte array to a file creating the file if it does not exist.
+ *
+ * @param file the file to write to
+ * @param data the content to write to the file
+ * @param append if {@code true}, then bytes will be added to the end of the
+ * file rather than overwriting
+ * @throws IOException in case of an I/O error
+ * @since 2.1
+ */
+ public static void writeByteArrayToFile(final File file, final byte[] data, final boolean append)
+ throws IOException {
+ writeByteArrayToFile(file, data, 0, data.length, append);
+ }
+
+ /**
+ * Writes {@code len} bytes from the specified byte array starting at offset
+ * {@code off} to a file, creating the file if it does not exist.
+ *
+ * @param file the file to write to
+ * @param data the content to write to the file
+ * @param off the start offset in the data
+ * @param len the number of bytes to write
+ * @throws IOException in case of an I/O error
+ * @since 2.5
+ */
+ public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len)
+ throws IOException {
+ writeByteArrayToFile(file, data, off, len, false);
+ }
+
+ /**
+ * Writes {@code len} bytes from the specified byte array starting at offset
+ * {@code off} to a file, creating the file if it does not exist.
+ *
+ * @param file the file to write to
+ * @param data the content to write to the file
+ * @param off the start offset in the data
+ * @param len the number of bytes to write
+ * @param append if {@code true}, then bytes will be added to the end of the
+ * file rather than overwriting
+ * @throws IOException in case of an I/O error
+ * @since 2.5
+ */
+ public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len,
+ final boolean append) throws IOException {
+ FileOutputStream out = null;
+ try {
+ out = openOutputStream(file, append);
+ out.write(data, off, len);
+ } finally {
+ IOUtils.close(out);
+ }
+ }
+
+ /**
+ * Opens a {@link FileOutputStream} for the specified file, checking and
+ * creating the parent directory if it does not exist.
+ *
+ * At the end of the method either the stream will be successfully opened, or an
+ * exception will have been thrown.
+ *
+ * The parent directory will be created if it does not exist. The file will be
+ * created if it does not exist. An exception is thrown if the file object
+ * exists but is a directory. An exception is thrown if the file exists but
+ * cannot be written to. An exception is thrown if the parent directory cannot
+ * be created.
+ *
+ * @param file the file to open for output, must not be {@code null}
+ * @param append if {@code true}, then bytes will be added to the end of the
+ * file rather than overwriting
+ * @return a new {@link FileOutputStream} for the specified file
+ * @throws IOException if the file object is a directory
+ * @throws IOException if the file cannot be written to
+ * @throws IOException if a parent directory needs creating but that fails
+ * @since 2.1
+ */
+ public static FileOutputStream openOutputStream(final File file, final boolean append) throws IOException {
+ if (file.exists()) {
+ if (file.isDirectory()) {
+ throw new IOException("File '" + file + "' exists but is a directory");
+ }
+ if (file.canWrite() == false) {
+ throw new IOException("File '" + file + "' cannot be written to");
+ }
+ } else {
+ final File parent = file.getParentFile();
+ if (parent != null) {
+ if (!parent.mkdirs() && !parent.isDirectory()) {
+ throw new IOException("Directory '" + parent + "' could not be created");
+ }
+ }
+ }
+ return new FileOutputStream(file, append);
+ }
+
+ /**
+ * Reads the contents of a file into a byte array.
+ * The file is always closed.
+ *
+ * @param file the file to read, must not be {@code null}
+ * @return the file contents, never {@code null}
+ * @throws IOException in case of an I/O error
+ * @since 1.1
+ */
+ public static byte[] readFileToByteArray(final File file) throws IOException {
+ InputStream in = null;
+ try {
+ in = new FileInputStream(file);
+ return IOUtils.getBytes(in);
+ } finally {
+ IOUtils.close(in);
+ }
+ }
+}
diff --git a/common/src/main/java/com/taobao/arthas/common/concurrent/ConcurrentWeakKeyHashMap.java b/common/src/main/java/com/taobao/arthas/common/concurrent/ConcurrentWeakKeyHashMap.java
new file mode 100644
index 000000000..a77770b29
--- /dev/null
+++ b/common/src/main/java/com/taobao/arthas/common/concurrent/ConcurrentWeakKeyHashMap.java
@@ -0,0 +1,1473 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you 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.
+ */
+/*
+ * Written by Doug Lea with assistance from members of JCP JSR-166
+ * Expert Group and released to the public domain, as explained at
+ * http://creativecommons.org/licenses/publicdomain
+ */
+package com.taobao.arthas.common.concurrent;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.AbstractCollection;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+
+/**
+ * An alternative weak-key {@link ConcurrentMap} which is similar to
+ * {@link ConcurrentHashMap}.
+ * @param the type of keys maintained by this map
+ * @param the type of mapped values
+ */
+public final class ConcurrentWeakKeyHashMap extends AbstractMap implements ConcurrentMap {
+
+ /*
+ * The basic strategy is to subdivide the table among Segments,
+ * each of which itself is a concurrently readable hash table.
+ */
+
+ /**
+ * The default initial capacity for this table, used when not otherwise
+ * specified in a constructor.
+ */
+ static final int DEFAULT_INITIAL_CAPACITY = 16;
+
+ /**
+ * The default load factor for this table, used when not otherwise specified
+ * in a constructor.
+ */
+ static final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+ /**
+ * The default concurrency level for this table, used when not otherwise
+ * specified in a constructor.
+ */
+ static final int DEFAULT_CONCURRENCY_LEVEL = 16;
+
+ /**
+ * The maximum capacity, used if a higher value is implicitly specified by
+ * either of the constructors with arguments. MUST be a power of two
+ * <= 1<<30 to ensure that entries are indexable using integers.
+ */
+ static final int MAXIMUM_CAPACITY = 1 << 30;
+
+ /**
+ * The maximum number of segments to allow; used to bound constructor
+ * arguments.
+ */
+ static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
+
+ /**
+ * Number of unsynchronized retries in size and containsValue methods before
+ * resorting to locking. This is used to avoid unbounded retries if tables
+ * undergo continuous modification which would make it impossible to obtain
+ * an accurate result.
+ */
+ static final int RETRIES_BEFORE_LOCK = 2;
+
+ /* ---------------- Fields -------------- */
+
+ /**
+ * Mask value for indexing into segments. The upper bits of a key's hash
+ * code are used to choose the segment.
+ */
+ final int segmentMask;
+
+ /**
+ * Shift value for indexing within segments.
+ */
+ final int segmentShift;
+
+ /**
+ * The segments, each of which is a specialized hash table
+ */
+ final Segment[] segments;
+
+ Set keySet;
+ Set> entrySet;
+ Collection values;
+
+ /* ---------------- Small Utilities -------------- */
+
+ /**
+ * Applies a supplemental hash function to a given hashCode, which defends
+ * against poor quality hash functions. This is critical because
+ * ConcurrentReferenceHashMap uses power-of-two length hash tables, that
+ * otherwise encounter collisions for hashCodes that do not differ in lower
+ * or upper bits.
+ */
+ private static int hash(int h) {
+ // Spread bits to regularize both segment and index locations,
+ // using variant of single-word Wang/Jenkins hash.
+ h += h << 15 ^ 0xffffcd7d;
+ h ^= h >>> 10;
+ h += h << 3;
+ h ^= h >>> 6;
+ h += (h << 2) + (h << 14);
+ return h ^ h >>> 16;
+ }
+
+ /**
+ * Returns the segment that should be used for key with given hash.
+ *
+ * @param hash the hash code for the key
+ * @return the segment
+ */
+ Segment segmentFor(int hash) {
+ return segments[hash >>> segmentShift & segmentMask];
+ }
+
+ private static int hashOf(Object key) {
+ return hash(key.hashCode());
+ }
+
+ /* ---------------- Inner Classes -------------- */
+
+ /**
+ * A weak-key reference which stores the key hash needed for reclamation.
+ */
+ static final class WeakKeyReference extends WeakReference {
+
+ final int hash;
+
+ WeakKeyReference(K key, int hash, ReferenceQueue