From 2314f08ce55fa1ff0c01e48ce1eab2f3c6439cd3 Mon Sep 17 00:00:00 2001 From: lbs <2322701154@qq.com> Date: Sun, 11 Jan 2026 00:43:55 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9E@Binding.MethodFirst?= =?UTF-8?q?Line=E5=92=8C@Binding.MethodLastLine=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + .../alibaba/bytekit/asm/binding/Binding.java | 34 +++++ .../asm/binding/MethodFirstLineBinding.java | 40 ++++++ .../asm/binding/MethodLastLineBinding.java | 44 ++++++ .../binding/MethodFirstLineBindingTest.java | 133 ++++++++++++++++++ .../binding/MethodLastLineBindingTest.java | 127 +++++++++++++++++ 6 files changed, 380 insertions(+) create mode 100644 bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/MethodFirstLineBinding.java create mode 100644 bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/MethodLastLineBinding.java create mode 100644 bytekit-core/src/test/java/com/alibaba/bytekit/asm/binding/MethodFirstLineBindingTest.java create mode 100644 bytekit-core/src/test/java/com/alibaba/bytekit/asm/binding/MethodLastLineBindingTest.java 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..421cf32 --- /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 first instruction of the method + AbstractInsnNode insnNode = bindingContext.getMethodProcessor().getEnterInsnNode(); + + // 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..ee02a8f --- /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 first instruction of the method + AbstractInsnNode insnNode = bindingContext.getMethodProcessor().getEnterInsnNode(); + + // 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..8d9e668 --- /dev/null +++ b/bytekit-core/src/test/java/com/alibaba/bytekit/asm/binding/MethodFirstLineBindingTest.java @@ -0,0 +1,133 @@ +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.MethodLastLine int lastLine, + @Binding.MethodName String methodName + ) { + System.err.println("AtEnter, methodName:" + methodName + ", firstLine:" + firstLine + ", lastLine:" + lastLine); + } + + } + + public static class ExitInterceptor { + + @AtExit(inline = true) + public static void onExit( + @Binding.This Object object, + @Binding.MethodFirstLine int firstLine, + @Binding.MethodLastLine int lastLine, + @Binding.MethodName String methodName, + @Binding.Return Object returnObject + ) { + System.err.println("AtExit, methodName:" + methodName + ", firstLine:" + firstLine + ", lastLine:" + lastLine + ", 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.MethodLastLine int lastLine, + @Binding.MethodName String methodName, + @Binding.Throwable RuntimeException ex + ) { + System.err.println("AtExceptionExit, methodName:" + methodName + ", firstLine:" + firstLine + ", lastLine:" + lastLine + ", ex:" + ex.getMessage()); + } + + } + + @Test + public void testMethodFirstLineAtEnter() 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:"); + assertThat(capture.toString()).contains("lastLine:"); + } + + @Test + public void testMethodFirstLineAtExit() 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:"); + assertThat(capture.toString()).contains("lastLine:"); + } + + @Test + public void testMethodFirstLineAtExceptionExit() 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:"); + assertThat(capture.toString()).contains("lastLine:"); + } + +} 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:"); + } + +} From b450666e2835bdbd94ea8e872bd21147ecc845fa Mon Sep 17 00:00:00 2001 From: lbs <2322701154@qq.com> Date: Sun, 11 Jan 2026 20:10:36 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=E4=BD=BF=E7=94=A8instructions.getFirs?= =?UTF-8?q?t()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../asm/binding/MethodFirstLineBinding.java | 4 ++-- .../asm/binding/MethodLastLineBinding.java | 4 ++-- .../binding/MethodFirstLineBindingTest.java | 18 ++++++------------ 3 files changed, 10 insertions(+), 16 deletions(-) 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 index 421cf32..e347698 100644 --- 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 @@ -17,8 +17,8 @@ public class MethodFirstLineBinding extends Binding { public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { int line = -1; - // Start from the first instruction of the method - AbstractInsnNode insnNode = bindingContext.getMethodProcessor().getEnterInsnNode(); + // Start from the very first instruction of the method + AbstractInsnNode insnNode = bindingContext.getMethodProcessor().getMethodNode().instructions.getFirst(); // Find the first LineNumberNode while (insnNode != null) { 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 index ee02a8f..87f93b4 100644 --- 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 @@ -18,8 +18,8 @@ public void pushOntoStack(InsnList instructions, BindingContext bindingContext) int line = -1; LineNumberNode lastLineNumberNode = null; - // Start from the first instruction of the method - AbstractInsnNode insnNode = bindingContext.getMethodProcessor().getEnterInsnNode(); + // Start from the very first instruction of the method + AbstractInsnNode insnNode = bindingContext.getMethodProcessor().getMethodNode().instructions.getFirst(); // Find the last LineNumberNode while (insnNode != null) { 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 index 8d9e668..ac575f5 100644 --- 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 @@ -46,10 +46,9 @@ public static class EnterInterceptor { public static void onEnter( @Binding.This Object object, @Binding.MethodFirstLine int firstLine, - @Binding.MethodLastLine int lastLine, @Binding.MethodName String methodName ) { - System.err.println("AtEnter, methodName:" + methodName + ", firstLine:" + firstLine + ", lastLine:" + lastLine); + System.err.println("AtEnter, methodName:" + methodName + ", firstLine:" + firstLine); } } @@ -60,11 +59,10 @@ public static class ExitInterceptor { public static void onExit( @Binding.This Object object, @Binding.MethodFirstLine int firstLine, - @Binding.MethodLastLine int lastLine, @Binding.MethodName String methodName, @Binding.Return Object returnObject ) { - System.err.println("AtExit, methodName:" + methodName + ", firstLine:" + firstLine + ", lastLine:" + lastLine + ", return:" + returnObject); + System.err.println("AtExit, methodName:" + methodName + ", firstLine:" + firstLine + ", return:" + returnObject); } } @@ -75,17 +73,16 @@ public static class ExceptionExitInterceptor { public static void onExceptionExit( @Binding.This Object object, @Binding.MethodFirstLine int firstLine, - @Binding.MethodLastLine int lastLine, @Binding.MethodName String methodName, @Binding.Throwable RuntimeException ex ) { - System.err.println("AtExceptionExit, methodName:" + methodName + ", firstLine:" + firstLine + ", lastLine:" + lastLine + ", ex:" + ex.getMessage()); + System.err.println("AtExceptionExit, methodName:" + methodName + ", firstLine:" + firstLine + ", ex:" + ex.getMessage()); } } @Test - public void testMethodFirstLineAtEnter() throws Exception { + public void testMethodLineAtEnter() throws Exception { TestHelper helper = TestHelper.builder().interceptorClass(EnterInterceptor.class).methodMatcher("hello") .reTransform(true); byte[] bytes = helper.process(Sample.class); @@ -95,11 +92,10 @@ public void testMethodFirstLineAtEnter() throws Exception { System.err.println(Decompiler.decompile(bytes)); assertThat(capture.toString()).contains("AtEnter, methodName:hello, firstLine:"); - assertThat(capture.toString()).contains("lastLine:"); } @Test - public void testMethodFirstLineAtExit() throws Exception { + public void testMethodLineAtExit() throws Exception { TestHelper helper = TestHelper.builder().interceptorClass(ExitInterceptor.class).methodMatcher("hello") .reTransform(true); byte[] bytes = helper.process(Sample.class); @@ -109,11 +105,10 @@ public void testMethodFirstLineAtExit() throws Exception { System.err.println(Decompiler.decompile(bytes)); assertThat(capture.toString()).contains("AtExit, methodName:hello, firstLine:"); - assertThat(capture.toString()).contains("lastLine:"); } @Test - public void testMethodFirstLineAtExceptionExit() throws Exception { + public void testMethodLineAtExceptionExit() throws Exception { TestHelper helper = TestHelper.builder().interceptorClass(ExceptionExitInterceptor.class).methodMatcher("hello") .reTransform(true); byte[] bytes = helper.process(Sample.class); @@ -127,7 +122,6 @@ public void testMethodFirstLineAtExceptionExit() throws Exception { System.err.println(Decompiler.decompile(bytes)); assertThat(capture.toString()).contains("AtExceptionExit, methodName:hello, firstLine:"); - assertThat(capture.toString()).contains("lastLine:"); } }