diff --git a/README.md b/README.md index 6d36ea7..59a792b 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ bytecode kit for java. * `@Binding.Line` 行号 * `@Binding.Monitor` 同步块里监控的对象 +* `@Binding.MethodFirstLine` 方法的首次执行的行号 +* `@Binding.MethodLastLine` 方法的末次执行的行号 ### 3. 可编程的异常处理 diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/Binding.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/Binding.java index 0049879..3d5f115 100644 --- a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/Binding.java +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/Binding.java @@ -434,4 +434,38 @@ public Binding parse(Annotation annotation) { return new MonitorBinding(); } } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = MethodFirstLineBindingParser.class) + public static @interface MethodFirstLine { + + boolean optional() default false; + + } + + public static class MethodFirstLineBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new MethodFirstLineBinding(); + } + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = MethodLastLineBindingParser.class) + public static @interface MethodLastLine { + + boolean optional() default false; + + } + + public static class MethodLastLineBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new MethodLastLineBinding(); + } + } } diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/MethodFirstLineBinding.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/MethodFirstLineBinding.java new file mode 100644 index 0000000..e347698 --- /dev/null +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/MethodFirstLineBinding.java @@ -0,0 +1,40 @@ +package com.alibaba.bytekit.asm.binding; + +import com.alibaba.deps.org.objectweb.asm.Type; +import com.alibaba.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.deps.org.objectweb.asm.tree.LineNumberNode; +import com.alibaba.bytekit.utils.AsmOpUtils; + +/** + * Binding for method first line number + * @author lbs + * + */ +public class MethodFirstLineBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + int line = -1; + + // Start from the very first instruction of the method + AbstractInsnNode insnNode = bindingContext.getMethodProcessor().getMethodNode().instructions.getFirst(); + + // Find the first LineNumberNode + while (insnNode != null) { + if (insnNode instanceof LineNumberNode) { + line = ((LineNumberNode) insnNode).line; + break; + } + insnNode = insnNode.getNext(); + } + + AsmOpUtils.push(instructions, line); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(int.class); + } + +} diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/MethodLastLineBinding.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/MethodLastLineBinding.java new file mode 100644 index 0000000..87f93b4 --- /dev/null +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/MethodLastLineBinding.java @@ -0,0 +1,44 @@ +package com.alibaba.bytekit.asm.binding; + +import com.alibaba.deps.org.objectweb.asm.Type; +import com.alibaba.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.deps.org.objectweb.asm.tree.LineNumberNode; +import com.alibaba.bytekit.utils.AsmOpUtils; + +/** + * Binding for method last line number + * @author lbs + * + */ +public class MethodLastLineBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + int line = -1; + LineNumberNode lastLineNumberNode = null; + + // Start from the very first instruction of the method + AbstractInsnNode insnNode = bindingContext.getMethodProcessor().getMethodNode().instructions.getFirst(); + + // Find the last LineNumberNode + while (insnNode != null) { + if (insnNode instanceof LineNumberNode) { + lastLineNumberNode = (LineNumberNode) insnNode; + } + insnNode = insnNode.getNext(); + } + + if (lastLineNumberNode != null) { + line = lastLineNumberNode.line; + } + + AsmOpUtils.push(instructions, line); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(int.class); + } + +} diff --git a/bytekit-core/src/test/java/com/alibaba/bytekit/asm/binding/MethodFirstLineBindingTest.java b/bytekit-core/src/test/java/com/alibaba/bytekit/asm/binding/MethodFirstLineBindingTest.java new file mode 100644 index 0000000..ac575f5 --- /dev/null +++ b/bytekit-core/src/test/java/com/alibaba/bytekit/asm/binding/MethodFirstLineBindingTest.java @@ -0,0 +1,127 @@ +package com.alibaba.bytekit.asm.binding; + +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.alibaba.bytekit.asm.interceptor.annotation.AtEnter; +import com.alibaba.bytekit.asm.interceptor.annotation.AtExit; +import com.alibaba.bytekit.asm.interceptor.annotation.AtExceptionExit; +import com.alibaba.bytekit.utils.Decompiler; +import com.alibaba.bytekit.asm.interceptor.TestHelper; + +public class MethodFirstLineBindingTest { + + @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 EnterInterceptor { + + @AtEnter(inline = true) + public static void onEnter( + @Binding.This Object object, + @Binding.MethodFirstLine int firstLine, + @Binding.MethodName String methodName + ) { + System.err.println("AtEnter, methodName:" + methodName + ", firstLine:" + firstLine); + } + + } + + public static class ExitInterceptor { + + @AtExit(inline = true) + public static void onExit( + @Binding.This Object object, + @Binding.MethodFirstLine int firstLine, + @Binding.MethodName String methodName, + @Binding.Return Object returnObject + ) { + System.err.println("AtExit, methodName:" + methodName + ", firstLine:" + firstLine + ", return:" + returnObject); + } + + } + + public static class ExceptionExitInterceptor { + + @AtExceptionExit(inline = true, onException = RuntimeException.class) + public static void onExceptionExit( + @Binding.This Object object, + @Binding.MethodFirstLine int firstLine, + @Binding.MethodName String methodName, + @Binding.Throwable RuntimeException ex + ) { + System.err.println("AtExceptionExit, methodName:" + methodName + ", firstLine:" + firstLine + ", ex:" + ex.getMessage()); + } + + } + + @Test + public void testMethodLineAtEnter() 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("AtEnter, methodName:hello, firstLine:"); + } + + @Test + public void testMethodLineAtExit() throws Exception { + TestHelper helper = TestHelper.builder().interceptorClass(ExitInterceptor.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("AtExit, methodName:hello, firstLine:"); + } + + @Test + public void testMethodLineAtExceptionExit() throws Exception { + TestHelper helper = TestHelper.builder().interceptorClass(ExceptionExitInterceptor.class).methodMatcher("hello") + .reTransform(true); + byte[] bytes = helper.process(Sample.class); + + try { + new Sample().hello("abc", true); + } catch (RuntimeException e) { + // expected + } + + System.err.println(Decompiler.decompile(bytes)); + + assertThat(capture.toString()).contains("AtExceptionExit, methodName:hello, firstLine:"); + } + +} diff --git a/bytekit-core/src/test/java/com/alibaba/bytekit/asm/binding/MethodLastLineBindingTest.java b/bytekit-core/src/test/java/com/alibaba/bytekit/asm/binding/MethodLastLineBindingTest.java new file mode 100644 index 0000000..4d7255c --- /dev/null +++ b/bytekit-core/src/test/java/com/alibaba/bytekit/asm/binding/MethodLastLineBindingTest.java @@ -0,0 +1,127 @@ +package com.alibaba.bytekit.asm.binding; + +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.alibaba.bytekit.asm.interceptor.annotation.AtEnter; +import com.alibaba.bytekit.asm.interceptor.annotation.AtExit; +import com.alibaba.bytekit.asm.interceptor.annotation.AtExceptionExit; +import com.alibaba.bytekit.utils.Decompiler; +import com.alibaba.bytekit.asm.interceptor.TestHelper; + +public class MethodLastLineBindingTest { + + @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 EnterInterceptor { + + @AtEnter(inline = true) + public static void onEnter( + @Binding.This Object object, + @Binding.MethodLastLine int lastLine, + @Binding.MethodName String methodName + ) { + System.err.println("AtEnter, methodName:" + methodName + ", lastLine:" + lastLine); + } + + } + + public static class ExitInterceptor { + + @AtExit(inline = true) + public static void onExit( + @Binding.This Object object, + @Binding.MethodLastLine int lastLine, + @Binding.MethodName String methodName, + @Binding.Return Object returnObject + ) { + System.err.println("AtExit, methodName:" + methodName + ", lastLine:" + lastLine + ", return:" + returnObject); + } + + } + + public static class ExceptionExitInterceptor { + + @AtExceptionExit(inline = true, onException = RuntimeException.class) + public static void onExceptionExit( + @Binding.This Object object, + @Binding.MethodLastLine int lastLine, + @Binding.MethodName String methodName, + @Binding.Throwable RuntimeException ex + ) { + System.err.println("AtExceptionExit, methodName:" + methodName + ", lastLine:" + lastLine + ", ex:" + ex.getMessage()); + } + + } + + @Test + public void testMethodLastLineAtEnter() 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("AtEnter, methodName:hello, lastLine:"); + } + + @Test + public void testMethodLastLineAtExit() throws Exception { + TestHelper helper = TestHelper.builder().interceptorClass(ExitInterceptor.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("AtExit, methodName:hello, lastLine:"); + } + + @Test + public void testMethodLastLineAtExceptionExit() throws Exception { + TestHelper helper = TestHelper.builder().interceptorClass(ExceptionExitInterceptor.class).methodMatcher("hello") + .reTransform(true); + byte[] bytes = helper.process(Sample.class); + + try { + new Sample().hello("abc", true); + } catch (RuntimeException e) { + // expected + } + + System.err.println(Decompiler.decompile(bytes)); + + assertThat(capture.toString()).contains("AtExceptionExit, methodName:hello, lastLine:"); + } + +}