From 81587220a524755e141c542647cc806280c059ca Mon Sep 17 00:00:00 2001 From: hengyunabc Date: Sun, 7 Jun 2026 23:10:14 +0800 Subject: [PATCH] Implement frame-aware line instrumentation --- .../alibaba/bytekit/asm/MethodProcessor.java | 4 + .../asm/binding/LocalVarNamesBinding.java | 17 +- .../bytekit/asm/binding/LocalVarsBinding.java | 17 +- .../asm/interceptor/annotation/AtLine.java | 9 +- .../asm/location/LineDuplicatePolicy.java | 9 + .../asm/location/LineLocationMatcher.java | 213 ++++++- .../bytekit/asm/location/LineMode.java | 6 + .../bytekit/asm/location/Location.java | 87 ++- .../location/filter/AllLocationFilter.java | 32 + .../location/filter/AnyLocationFilter.java | 32 + .../location/filter/GroupLocationFilter.java | 30 +- .../com/alibaba/bytekit/utils/AsmOpUtils.java | 54 ++ .../asm/interceptor/AtLineFrameAwareTest.java | 595 ++++++++++++++++++ 13 files changed, 1065 insertions(+), 40 deletions(-) create mode 100644 bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/LineDuplicatePolicy.java create mode 100644 bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/LineMode.java create mode 100644 bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/filter/AllLocationFilter.java create mode 100644 bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/filter/AnyLocationFilter.java create mode 100644 bytekit-core/src/test/java/com/alibaba/bytekit/asm/interceptor/AtLineFrameAwareTest.java diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/MethodProcessor.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/MethodProcessor.java index d23d989..5fd86c5 100644 --- a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/MethodProcessor.java +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/MethodProcessor.java @@ -229,6 +229,10 @@ public LocalVariableNode initInvokeReturnVariableNode(String name, Type type) { return variableNode; } + public LocalVariableNode initLineStackVariableNode(String name, Type type) { + return this.addInterceptorLocalVariable(this.innerVariablePrefix + name, type.getDescriptor()); + } + public TryCatchBlock initTryCatchBlock() { return initTryCatchBlock(THROWABLE_TYPE.getInternalName()); } diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/LocalVarNamesBinding.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/LocalVarNamesBinding.java index bc40f2d..4d42f5e 100644 --- a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/LocalVarNamesBinding.java +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/LocalVarNamesBinding.java @@ -5,11 +5,14 @@ import java.util.List; import com.alibaba.bytekit.utils.MatchUtils; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.BasicValue; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.Frame; 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.LocalVariableNode; import com.alibaba.bytekit.utils.AsmOpUtils; +import com.alibaba.bytekit.asm.location.Location.LineLocation; public class LocalVarNamesBinding extends Binding { @@ -31,7 +34,9 @@ public LocalVarNamesBinding() { public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { AbstractInsnNode currentInsnNode = bindingContext.getLocation().getInsnNode(); - List localVariables = new LinkedList(bindingContext.getMethodProcessor().getMethodNode().localVariables); + List rawLocalVariables = bindingContext.getMethodProcessor().getMethodNode().localVariables; + List localVariables = rawLocalVariables == null ? new LinkedList() + : new LinkedList(rawLocalVariables); if (excludePattern != null && !excludePattern.isEmpty()){ Iterator it = localVariables.iterator(); while(it.hasNext()){ @@ -45,7 +50,8 @@ public void pushOntoStack(InsnList instructions, BindingContext bindingContext) } } - List results = AsmOpUtils.validVariables(localVariables, currentInsnNode); + List results = AsmOpUtils.validVariables(localVariables, currentInsnNode, + currentFrame(bindingContext)); AsmOpUtils.push(instructions, results.size()); AsmOpUtils.newArray(instructions, AsmOpUtils.STRING_TYPE); @@ -72,4 +78,11 @@ public String getExcludePattern() { public void setExcludePattern(String excludePattern) { this.excludePattern = excludePattern; } + + private Frame currentFrame(BindingContext bindingContext) { + if (bindingContext.getLocation() instanceof LineLocation) { + return ((LineLocation) bindingContext.getLocation()).getFrame(); + } + return null; + } } diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/LocalVarsBinding.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/LocalVarsBinding.java index 7416eee..405a782 100644 --- a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/LocalVarsBinding.java +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/binding/LocalVarsBinding.java @@ -5,11 +5,14 @@ import java.util.List; import com.alibaba.bytekit.utils.MatchUtils; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.BasicValue; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.Frame; 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.LocalVariableNode; import com.alibaba.bytekit.utils.AsmOpUtils; +import com.alibaba.bytekit.asm.location.Location.LineLocation; /** * TODO 增加一个配置,是否包含 method args @@ -34,7 +37,9 @@ public void pushOntoStack(InsnList instructions, BindingContext bindingContext) AbstractInsnNode currentInsnNode = bindingContext.getLocation().getInsnNode(); - List localVariables = new LinkedList(bindingContext.getMethodProcessor().getMethodNode().localVariables); + List rawLocalVariables = bindingContext.getMethodProcessor().getMethodNode().localVariables; + List localVariables = rawLocalVariables == null ? new LinkedList() + : new LinkedList(rawLocalVariables); if (excludePattern != null && !excludePattern.isEmpty()){ Iterator it = localVariables.iterator(); while(it.hasNext()){ @@ -48,7 +53,8 @@ public void pushOntoStack(InsnList instructions, BindingContext bindingContext) } } - List results = AsmOpUtils.validVariables(localVariables, currentInsnNode); + List results = AsmOpUtils.validVariables(localVariables, currentInsnNode, + currentFrame(bindingContext)); AsmOpUtils.push(instructions, results.size()); AsmOpUtils.newArray(instructions, AsmOpUtils.OBJECT_TYPE); @@ -79,4 +85,11 @@ public String getExcludePattern() { public void setExcludePattern(String excludePattern) { this.excludePattern = excludePattern; } + + private Frame currentFrame(BindingContext bindingContext) { + if (bindingContext.getLocation() instanceof LineLocation) { + return ((LineLocation) bindingContext.getLocation()).getFrame(); + } + return null; + } } diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/interceptor/annotation/AtLine.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/interceptor/annotation/AtLine.java index e7779f2..8a6cbaf 100644 --- a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/interceptor/annotation/AtLine.java +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/interceptor/annotation/AtLine.java @@ -10,7 +10,9 @@ import com.alibaba.bytekit.asm.interceptor.InterceptorProcessor; import com.alibaba.bytekit.asm.interceptor.annotation.AtLine.LineInterceptorProcessorParser; import com.alibaba.bytekit.asm.interceptor.parser.InterceptorProcessorParser; +import com.alibaba.bytekit.asm.location.LineDuplicatePolicy; import com.alibaba.bytekit.asm.location.LineLocationMatcher; +import com.alibaba.bytekit.asm.location.LineMode; import com.alibaba.bytekit.asm.location.LocationMatcher; @Documented @@ -26,6 +28,10 @@ int[] lines(); + LineMode mode() default LineMode.FRAME_AWARE; + + LineDuplicatePolicy duplicatePolicy() default LineDuplicatePolicy.DEFAULT; + class LineInterceptorProcessorParser implements InterceptorProcessorParser { @Override @@ -33,7 +39,8 @@ public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) AtLine atLine = (AtLine) annotationOnMethod; - LocationMatcher locationMatcher = new LineLocationMatcher(atLine.lines()); + LocationMatcher locationMatcher = new LineLocationMatcher(atLine.mode(), atLine.duplicatePolicy(), + atLine.lines()); return InterceptorParserUtils.createInterceptorProcessor(method, locationMatcher, diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/LineDuplicatePolicy.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/LineDuplicatePolicy.java new file mode 100644 index 0000000..195acf5 --- /dev/null +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/LineDuplicatePolicy.java @@ -0,0 +1,9 @@ +package com.alibaba.bytekit.asm.location; + +public enum LineDuplicatePolicy { + DEFAULT, + FIRST, + ALL, + REJECT_AFTER_CONTROL_FLOW, + FIRST_PER_LINE_BLOCK +} diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/LineLocationMatcher.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/LineLocationMatcher.java index 705c026..1a44dd0 100644 --- a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/LineLocationMatcher.java +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/LineLocationMatcher.java @@ -2,19 +2,51 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import com.alibaba.bytekit.asm.MethodProcessor; import com.alibaba.bytekit.asm.location.Location.LineLocation; import com.alibaba.bytekit.asm.location.filter.LocationFilter; +import com.alibaba.deps.org.objectweb.asm.Opcodes; +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.IincInsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.JumpInsnNode; import com.alibaba.deps.org.objectweb.asm.tree.LineNumberNode; +import com.alibaba.deps.org.objectweb.asm.tree.LookupSwitchInsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.MethodNode; +import com.alibaba.deps.org.objectweb.asm.tree.TableSwitchInsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.VarInsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.Analyzer; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.AnalyzerException; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.BasicInterpreter; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.BasicValue; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.BasicVerifier; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.Frame; public class LineLocationMatcher implements LocationMatcher { + private static final int ANALYSIS_MAX_STACK_HEADROOM = 64; + private List targetLines = Collections.emptyList(); + private LineMode mode; + private LineDuplicatePolicy duplicatePolicy; public LineLocationMatcher(int... targetLines) { + this(LineMode.FRAME_AWARE, LineDuplicatePolicy.DEFAULT, targetLines); + } + + public LineLocationMatcher(List targetLines) { + this(LineMode.FRAME_AWARE, LineDuplicatePolicy.DEFAULT, targetLines); + } + + public LineLocationMatcher(LineMode mode, LineDuplicatePolicy duplicatePolicy, int... targetLines) { + this.mode = mode == null ? LineMode.FRAME_AWARE : mode; + this.duplicatePolicy = duplicatePolicy == null ? LineDuplicatePolicy.DEFAULT : duplicatePolicy; if (targetLines != null) { ArrayList result = new ArrayList(targetLines.length); for (int targetLine : targetLines) { @@ -24,24 +56,67 @@ public LineLocationMatcher(int... targetLines) { } } - public LineLocationMatcher(List targetLines) { - this.targetLines = targetLines; + public LineLocationMatcher(LineMode mode, LineDuplicatePolicy duplicatePolicy, List targetLines) { + this.mode = mode == null ? LineMode.FRAME_AWARE : mode; + this.duplicatePolicy = duplicatePolicy == null ? LineDuplicatePolicy.DEFAULT : duplicatePolicy; + if (targetLines != null) { + this.targetLines = targetLines; + } } @Override public List match(MethodProcessor methodProcessor) { List locations = new ArrayList(); - LocationFilter locationFilter = methodProcessor.getLocationFilter(); + LineDuplicatePolicy effectivePolicy = effectiveDuplicatePolicy(); - AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode(); + LineFrames lineFrames = null; + if (mode == LineMode.FRAME_AWARE) { + lineFrames = analyze(methodProcessor); + if (lineFrames == null) { + return locations; + } + } + + AbstractInsnNode insnNode = startInsnNode(methodProcessor); + int lastLine = Integer.MIN_VALUE; + boolean controlFlowSinceLastLine = true; + int locationIndex = 0; + Set matchedLines = new HashSet(); + Set seenTargetLines = new HashSet(); + Map controlFlowAfterLine = new HashMap(); while (insnNode != null) { if (insnNode instanceof LineNumberNode) { LineNumberNode lineNumberNode = (LineNumberNode) insnNode; + boolean lineBlockBoundary = lineNumberNode.line != lastLine || controlFlowSinceLastLine; if (match(lineNumberNode.line)) { - if (locationFilter.allow(lineNumberNode, LocationType.LINE, false)) { - locations.add(new LineLocation(lineNumberNode, lineNumberNode.line)); + boolean rejectAfterControlFlow = effectivePolicy == LineDuplicatePolicy.REJECT_AFTER_CONTROL_FLOW + && seenTargetLines.contains(lineNumberNode.line) + && Boolean.TRUE.equals(controlFlowAfterLine.get(lineNumberNode.line)); + if (!rejectAfterControlFlow + && allowByDuplicatePolicy(effectivePolicy, matchedLines, lineNumberNode.line, + lineBlockBoundary) + && locationFilter.allow(lineNumberNode, LocationType.LINE, false)) { + if (mode == LineMode.FRAME_AWARE) { + Frame frame = frameOf(methodProcessor.getMethodNode(), lineFrames.frames, + lineNumberNode); + if (isFrameUsable(frame, lineFrames.precise)) { + locations.add(new LineLocation(lineNumberNode, lineNumberNode.line, frame, + locationIndex++)); + } + } else { + locations.add(new LineLocation(lineNumberNode, lineNumberNode.line)); + } } + seenTargetLines.add(lineNumberNode.line); + controlFlowAfterLine.put(lineNumberNode.line, Boolean.FALSE); + } + lastLine = lineNumberNode.line; + controlFlowSinceLastLine = false; + } else if (isControlFlow(insnNode)) { + controlFlowSinceLastLine = true; + for (Integer line : controlFlowAfterLine.keySet()) { + controlFlowAfterLine.put(line, Boolean.TRUE); } } insnNode = insnNode.getNext(); @@ -62,4 +137,130 @@ private boolean match(int line) { return false; } + private LineDuplicatePolicy effectiveDuplicatePolicy() { + if (duplicatePolicy != LineDuplicatePolicy.DEFAULT) { + return duplicatePolicy; + } + return mode == LineMode.LEGACY ? LineDuplicatePolicy.ALL : LineDuplicatePolicy.FIRST_PER_LINE_BLOCK; + } + + private AbstractInsnNode startInsnNode(MethodProcessor methodProcessor) { + return methodProcessor.getEnterInsnNode(); + } + + private boolean allowByDuplicatePolicy(LineDuplicatePolicy policy, Set matchedLines, int line, + boolean lineBlockBoundary) { + if (policy == LineDuplicatePolicy.ALL) { + return true; + } + if (policy == LineDuplicatePolicy.FIRST) { + return matchedLines.add(line); + } + if (policy == LineDuplicatePolicy.REJECT_AFTER_CONTROL_FLOW) { + return true; + } + if (policy == LineDuplicatePolicy.FIRST_PER_LINE_BLOCK) { + return lineBlockBoundary; + } + return true; + } + + private LineFrames analyze(MethodProcessor methodProcessor) { + MethodNode methodNode = methodProcessor.getMethodNode(); + int originMaxStack = methodNode.maxStack; + int originMaxLocals = methodNode.maxLocals; + try { + prepareAnalysisLimits(methodNode); + Analyzer analyzer = new Analyzer(new BasicVerifier()); + return new LineFrames(analyzer.analyze(methodProcessor.getOwner(), methodNode), true); + } catch (AnalyzerException e) { + try { + Analyzer analyzer = new Analyzer(new BasicInterpreter()); + return new LineFrames(analyzer.analyze(methodProcessor.getOwner(), methodNode), false); + } catch (AnalyzerException ignored) { + return null; + } + } finally { + methodNode.maxStack = originMaxStack; + methodNode.maxLocals = originMaxLocals; + } + } + + private void prepareAnalysisLimits(MethodNode methodNode) { + // transform 期 ClassWriter 还没重新计算 maxStack,前序 inline advice 可能让内存中的 + // MethodNode 临时超过原 maxStack。这里仅给 ASM Analyzer 预留保守余量,finally 会恢复原值。 + methodNode.maxStack = methodNode.maxStack + ANALYSIS_MAX_STACK_HEADROOM; + methodNode.maxLocals = Math.max(methodNode.maxLocals, requiredMaxLocals(methodNode)); + } + + private int requiredMaxLocals(MethodNode methodNode) { + int maxLocals = (methodNode.access & Opcodes.ACC_STATIC) == 0 ? 1 : 0; + Type[] argumentTypes = Type.getArgumentTypes(methodNode.desc); + for (Type argumentType : argumentTypes) { + maxLocals += argumentType.getSize(); + } + for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode + .getNext()) { + if (insnNode instanceof VarInsnNode) { + VarInsnNode varInsnNode = (VarInsnNode) insnNode; + maxLocals = Math.max(maxLocals, varInsnNode.var + localSize(varInsnNode.getOpcode())); + } else if (insnNode instanceof IincInsnNode) { + IincInsnNode iincInsnNode = (IincInsnNode) insnNode; + maxLocals = Math.max(maxLocals, iincInsnNode.var + 1); + } + } + return maxLocals; + } + + private int localSize(int opcode) { + return opcode == Opcodes.LLOAD || opcode == Opcodes.LSTORE || opcode == Opcodes.DLOAD + || opcode == Opcodes.DSTORE ? 2 : 1; + } + + private Frame frameOf(MethodNode methodNode, Frame[] frames, AbstractInsnNode insnNode) { + int index = methodNode.instructions.indexOf(insnNode); + if (index < 0 || index >= frames.length) { + return null; + } + return frames[index]; + } + + private boolean isFrameUsable(Frame frame, boolean precise) { + if (frame == null) { + return false; + } + if (!precise && frame.getStackSize() > 0) { + return false; + } + for (int i = 0; i < frame.getStackSize(); i++) { + BasicValue value = frame.getStack(i); + if (value == null || value == BasicValue.UNINITIALIZED_VALUE + || value == BasicValue.RETURNADDRESS_VALUE) { + return false; + } + Type type = value.getType(); + if (type == null || type == Type.VOID_TYPE || type.getSort() == Type.METHOD) { + return false; + } + } + return true; + } + + private boolean isControlFlow(AbstractInsnNode insnNode) { + int opcode = insnNode.getOpcode(); + return insnNode instanceof JumpInsnNode || insnNode instanceof TableSwitchInsnNode + || insnNode instanceof LookupSwitchInsnNode || opcode == Opcodes.ATHROW + || (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN); + } + + private static class LineFrames { + private Frame[] frames; + private boolean precise; + + private LineFrames(Frame[] frames, boolean precise) { + this.frames = frames; + this.precise = precise; + } + } + } diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/LineMode.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/LineMode.java new file mode 100644 index 0000000..d58c14a --- /dev/null +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/LineMode.java @@ -0,0 +1,6 @@ +package com.alibaba.bytekit.asm.location; + +public enum LineMode { + FRAME_AWARE, + LEGACY +} diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/Location.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/Location.java index 34686f3..94061bf 100644 --- a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/Location.java +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/Location.java @@ -1,11 +1,17 @@ package com.alibaba.bytekit.asm.location; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + 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.FieldInsnNode; import com.alibaba.deps.org.objectweb.asm.tree.InsnList; import com.alibaba.deps.org.objectweb.asm.tree.LocalVariableNode; import com.alibaba.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.BasicValue; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.Frame; import com.alibaba.bytekit.asm.MethodProcessor; import com.alibaba.bytekit.asm.binding.BindingContext; import com.alibaba.bytekit.asm.binding.StackSaver; @@ -97,16 +103,95 @@ public static class LineLocation extends Location { * the line at which the trigger point should be inserted */ private int targetLine; + private Frame frame; + private List stackTypes; + private int locationIndex; + private List stackLocalVariables; public LineLocation(AbstractInsnNode insnNode, int targetLine) { + this(insnNode, targetLine, null, -1); + } + + public LineLocation(AbstractInsnNode insnNode, int targetLine, Frame frame, int locationIndex) { super(insnNode); this.targetLine = targetLine; + this.frame = frame; + this.locationIndex = locationIndex; + this.stackTypes = resolveStackTypes(frame); + this.stackNeedSave = !this.stackTypes.isEmpty(); } public LocationType getLocationType() { return LocationType.LINE; } + public int getTargetLine() { + return targetLine; + } + + public Frame getFrame() { + return frame; + } + + @Override + public StackSaver getStackSaver() { + return new StackSaver() { + + @Override + public void store(InsnList instructions, BindingContext bindingContext) { + List localVariables = initStackLocalVariables(bindingContext.getMethodProcessor()); + for (int i = stackTypes.size() - 1; i >= 0; i--) { + AsmOpUtils.storeVar(instructions, stackTypes.get(i), localVariables.get(i).index); + } + } + + @Override + public void load(InsnList instructions, BindingContext bindingContext) { + List localVariables = initStackLocalVariables(bindingContext.getMethodProcessor()); + for (int i = 0; i < stackTypes.size(); i++) { + AsmOpUtils.loadVar(instructions, stackTypes.get(i), localVariables.get(i).index); + } + } + + @Override + public Type getType(BindingContext bindingContext) { + // LineLocation 只负责保存并恢复原始操作数栈,不支持用回调返回值替换栈值。 + throw new UnsupportedOperationException("LineLocation stack saver does not support getType()."); + } + }; + } + + private synchronized List initStackLocalVariables(MethodProcessor methodProcessor) { + if (stackLocalVariables == null) { + List localVariables = new ArrayList(stackTypes.size()); + for (int i = 0; i < stackTypes.size(); i++) { + String name = "line_" + targetLine + "_" + locationIndex + "_stack_" + i; + localVariables.add(methodProcessor.initLineStackVariableNode(name, stackTypes.get(i))); + } + stackLocalVariables = localVariables; + } + return stackLocalVariables; + } + + private List resolveStackTypes(Frame frame) { + List result = new ArrayList(); + if (frame == null) { + return result; + } + for (int i = 0; i < frame.getStackSize(); i++) { + BasicValue value = frame.getStack(i); + if (value == null) { + return Collections.emptyList(); + } + Type type = value.getType(); + if (type == null) { + return Collections.emptyList(); + } + result.add(type); + } + return result; + } + } /** @@ -692,4 +777,4 @@ public MethodInsnNode methodInsnNode() { return methodInsnNode; } } -} \ No newline at end of file +} diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/filter/AllLocationFilter.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/filter/AllLocationFilter.java new file mode 100644 index 0000000..6bf77b0 --- /dev/null +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/filter/AllLocationFilter.java @@ -0,0 +1,32 @@ +package com.alibaba.bytekit.asm.location.filter; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.bytekit.asm.location.LocationType; +import com.alibaba.deps.org.objectweb.asm.tree.AbstractInsnNode; + +public class AllLocationFilter implements LocationFilter { + + private List filters = new ArrayList(); + + public AllLocationFilter(LocationFilter... filters) { + for (LocationFilter filter : filters) { + this.filters.add(filter); + } + } + + public void addFilter(LocationFilter filter) { + this.filters.add(filter); + } + + @Override + public boolean allow(AbstractInsnNode insnNode, LocationType locationType, boolean complete) { + for (LocationFilter filter : filters) { + if (!filter.allow(insnNode, locationType, complete)) { + return false; + } + } + return true; + } +} diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/filter/AnyLocationFilter.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/filter/AnyLocationFilter.java new file mode 100644 index 0000000..67851b3 --- /dev/null +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/filter/AnyLocationFilter.java @@ -0,0 +1,32 @@ +package com.alibaba.bytekit.asm.location.filter; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.bytekit.asm.location.LocationType; +import com.alibaba.deps.org.objectweb.asm.tree.AbstractInsnNode; + +public class AnyLocationFilter implements LocationFilter { + + protected List filters = new ArrayList(); + + public AnyLocationFilter(LocationFilter... filters) { + for (LocationFilter filter : filters) { + this.filters.add(filter); + } + } + + public void addFilter(LocationFilter filter) { + this.filters.add(filter); + } + + @Override + public boolean allow(AbstractInsnNode insnNode, LocationType locationType, boolean complete) { + for (LocationFilter filter : filters) { + if (filter.allow(insnNode, locationType, complete)) { + return true; + } + } + return false; + } +} diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/filter/GroupLocationFilter.java b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/filter/GroupLocationFilter.java index 67a04ac..ded49af 100644 --- a/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/filter/GroupLocationFilter.java +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/asm/location/filter/GroupLocationFilter.java @@ -1,38 +1,12 @@ package com.alibaba.bytekit.asm.location.filter; -import java.util.ArrayList; -import java.util.List; - -import com.alibaba.deps.org.objectweb.asm.tree.AbstractInsnNode; -import com.alibaba.bytekit.asm.location.LocationType; - /** * * @author hengyunabc 2020-05-04 * */ -public class GroupLocationFilter implements LocationFilter { - - List filters = new ArrayList(); - +public class GroupLocationFilter extends AnyLocationFilter { public GroupLocationFilter(LocationFilter... filters) { - for (LocationFilter filter : filters) { - this.filters.add(filter); - } + super(filters); } - - public void addFilter(LocationFilter filter) { - this.filters.add(filter); - } - - @Override - public boolean allow(AbstractInsnNode insnNode, LocationType locationType, boolean complete) { - for (LocationFilter filter : filters) { - if (filter.allow(insnNode, locationType, complete)) { - return true; - } - } - return false; - } - } diff --git a/bytekit-core/src/main/java/com/alibaba/bytekit/utils/AsmOpUtils.java b/bytekit-core/src/main/java/com/alibaba/bytekit/utils/AsmOpUtils.java index 1bbef52..d8e8fbd 100644 --- a/bytekit-core/src/main/java/com/alibaba/bytekit/utils/AsmOpUtils.java +++ b/bytekit-core/src/main/java/com/alibaba/bytekit/utils/AsmOpUtils.java @@ -17,6 +17,8 @@ import com.alibaba.deps.org.objectweb.asm.tree.MethodNode; import com.alibaba.deps.org.objectweb.asm.tree.TypeInsnNode; import com.alibaba.deps.org.objectweb.asm.tree.VarInsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.BasicValue; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.Frame; public class AsmOpUtils { @@ -448,6 +450,9 @@ public static boolean isReturnCode(final int opcode) { public static List validVariables(List localVariables, AbstractInsnNode currentInsnNode) { List results = new ArrayList(); + if (localVariables == null) { + return results; + } // find out current valid local variables for (LocalVariableNode localVariableNode : localVariables) { @@ -462,4 +467,53 @@ public static List validVariables(List loc return results; } + + public static List validVariables(List localVariables, + AbstractInsnNode currentInsnNode, Frame frame) { + List results = validVariables(localVariables, currentInsnNode); + if (frame == null) { + return results; + } + List frameAwareResults = new ArrayList(); + for (LocalVariableNode localVariableNode : results) { + if (isReadableLocalVariable(localVariableNode, frame)) { + frameAwareResults.add(localVariableNode); + } + } + return frameAwareResults; + } + + private static boolean isReadableLocalVariable(LocalVariableNode localVariableNode, Frame frame) { + if (localVariableNode.index < 0 || localVariableNode.index >= frame.getLocals()) { + return false; + } + BasicValue value = frame.getLocal(localVariableNode.index); + if (value == null || value == BasicValue.UNINITIALIZED_VALUE || value == BasicValue.RETURNADDRESS_VALUE) { + return false; + } + Type expectedType = Type.getType(localVariableNode.desc); + Type frameType = value.getType(); + if (frameType == null) { + return false; + } + switch (expectedType.getSort()) { + case Type.BOOLEAN: + case Type.CHAR: + case Type.BYTE: + case Type.SHORT: + case Type.INT: + return Type.INT_TYPE.equals(frameType); + case Type.FLOAT: + return Type.FLOAT_TYPE.equals(frameType); + case Type.LONG: + return Type.LONG_TYPE.equals(frameType) && localVariableNode.index + 1 < frame.getLocals(); + case Type.DOUBLE: + return Type.DOUBLE_TYPE.equals(frameType) && localVariableNode.index + 1 < frame.getLocals(); + case Type.ARRAY: + case Type.OBJECT: + return value.isReference(); + default: + return false; + } + } } diff --git a/bytekit-core/src/test/java/com/alibaba/bytekit/asm/interceptor/AtLineFrameAwareTest.java b/bytekit-core/src/test/java/com/alibaba/bytekit/asm/interceptor/AtLineFrameAwareTest.java new file mode 100644 index 0000000..5f504b9 --- /dev/null +++ b/bytekit-core/src/test/java/com/alibaba/bytekit/asm/interceptor/AtLineFrameAwareTest.java @@ -0,0 +1,595 @@ +package com.alibaba.bytekit.asm.interceptor; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; + +import com.alibaba.bytekit.asm.MethodProcessor; +import com.alibaba.bytekit.asm.binding.Binding; +import com.alibaba.bytekit.asm.binding.BindingContext; +import com.alibaba.bytekit.asm.binding.StackSaver; +import com.alibaba.bytekit.asm.interceptor.annotation.AtEnter; +import com.alibaba.bytekit.asm.interceptor.annotation.AtExceptionExit; +import com.alibaba.bytekit.asm.interceptor.annotation.AtExit; +import com.alibaba.bytekit.asm.interceptor.annotation.AtLine; +import com.alibaba.bytekit.asm.interceptor.annotation.ExceptionHandler; +import com.alibaba.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser; +import com.alibaba.bytekit.asm.location.LineDuplicatePolicy; +import com.alibaba.bytekit.asm.location.LineLocationMatcher; +import com.alibaba.bytekit.asm.location.LineMode; +import com.alibaba.bytekit.asm.location.Location; +import com.alibaba.bytekit.asm.location.Location.LineLocation; +import com.alibaba.bytekit.utils.AsmUtils; +import com.alibaba.bytekit.utils.VerifyUtils; +import com.alibaba.deps.org.objectweb.asm.Opcodes; +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.ClassNode; +import com.alibaba.deps.org.objectweb.asm.tree.InsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.IntInsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.JumpInsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.LabelNode; +import com.alibaba.deps.org.objectweb.asm.tree.LineNumberNode; +import com.alibaba.deps.org.objectweb.asm.tree.LocalVariableNode; +import com.alibaba.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.MethodNode; +import com.alibaba.deps.org.objectweb.asm.tree.VarInsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.BasicValue; +import com.alibaba.deps.org.objectweb.asm.tree.analysis.Frame; + +public class AtLineFrameAwareTest { + + private static final AtomicInteger STACK_LINE_HITS = new AtomicInteger(); + private static final AtomicInteger SUPPRESS_HITS = new AtomicInteger(); + private static final AtomicInteger CONCURRENT_HITS = new AtomicInteger(); + public static final AtomicInteger ORDERED_LINE_HITS = new AtomicInteger(); + private static volatile Object[] capturedLocalVars; + private static volatile String[] capturedLocalVarNames; + + public static class ThrowingLineInterceptor { + @AtLine(lines = { 100 }, inline = false, suppress = RuntimeException.class, suppressHandler = SuppressHandler.class) + public static void atLine() { + STACK_LINE_HITS.incrementAndGet(); + throw new RuntimeException("line callback failure"); + } + } + + public static class SuppressHandler { + @ExceptionHandler(inline = false) + public static void onSuppress(@Binding.Throwable Throwable throwable) { + SUPPRESS_HITS.incrementAndGet(); + } + } + + public static class LocalVarsLineInterceptor { + @AtLine(lines = { 100 }, inline = false) + public static void atLine(@Binding.LocalVars Object[] localVars, + @Binding.LocalVarNames String[] localVarNames) { + capturedLocalVars = localVars; + capturedLocalVarNames = localVarNames; + } + } + + public static class ConcurrentLineInterceptor { + @AtLine(lines = { 100 }, inline = false) + public static void atLine() { + CONCURRENT_HITS.incrementAndGet(); + } + } + + public static class OrderedEnterInterceptor { + @AtEnter(inline = true) + public static void atEnter(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodInfo String methodInfo, @Binding.Args Object[] args) { + orderedEnter(target, clazz, methodInfo, args); + } + } + + public static class OrderedExitInterceptor { + @AtExit(inline = true) + public static void atExit(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodInfo String methodInfo, @Binding.Args Object[] args, @Binding.Return Object returnObj) { + orderedExit(target, clazz, methodInfo, args, returnObj); + } + } + + public static class OrderedExceptionExitInterceptor { + @AtExceptionExit(inline = true) + public static void atExceptionExit(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodInfo String methodInfo, @Binding.Args Object[] args, + @Binding.Throwable Throwable throwable) { + orderedExceptionExit(target, clazz, methodInfo, args, throwable); + } + } + + public static class OrderedLineInterceptor { + @AtLine(lines = { 100 }, inline = true) + public static void atLine() { + ORDERED_LINE_HITS.incrementAndGet(); + } + } + + public static class PrefixLineInterceptor { + @AtLine(lines = { 99 }, inline = false) + public static void atLine() { + } + } + + public static void orderedEnter(Object target, Class clazz, String methodInfo, Object[] args) { + } + + public static void orderedExit(Object target, Class clazz, String methodInfo, Object[] args, Object returnObj) { + } + + public static void orderedExceptionExit(Object target, Class clazz, String methodInfo, Object[] args, + Throwable throwable) { + } + + @Test + public void lineCallbackWithSuppressKeepsOriginalOperandStack() throws Exception { + STACK_LINE_HITS.set(0); + SUPPRESS_HITS.set(0); + TransformResult result = transform(stackLineClass("StackLineWithSuppress"), ThrowingLineInterceptor.class); + + Object instance = newInstance(result.classNode.name, result.bytes); + Object value = instance.getClass().getMethod("stackLine").invoke(instance); + + assertThat(result.locations).isEqualTo(1); + assertThat(value).isEqualTo(7); + assertThat(STACK_LINE_HITS.get()).isEqualTo(1); + assertThat(SUPPRESS_HITS.get()).isEqualTo(1); + } + + @Test + public void frameAwareLocalVariablesSkipUnreadableSlots() throws Exception { + capturedLocalVars = null; + capturedLocalVarNames = null; + TransformResult result = transform(localVariableClass("LocalVariableFrameAware"), LocalVarsLineInterceptor.class); + + Object instance = newInstance(result.classNode.name, result.bytes); + Object value = instance.getClass().getMethod("readLocal").invoke(instance); + + assertThat(result.locations).isEqualTo(1); + assertThat(value).isEqualTo(7); + assertThat(capturedLocalVarNames).contains("this", "value"); + assertThat(capturedLocalVarNames).doesNotContain("stale"); + assertThat(capturedLocalVars).hasSameSizeAs(capturedLocalVarNames); + } + + @Test + public void frameAwareLineInstrumentationIsSafeUnderConcurrentInvocation() throws Exception { + CONCURRENT_HITS.set(0); + TransformResult result = transform(stackLineClass("StackLineConcurrent"), ConcurrentLineInterceptor.class); + final Object instance = newInstance(result.classNode.name, result.bytes); + final Method method = instance.getClass().getMethod("stackLine"); + int threadCount = 8; + final int loops = 200; + final CountDownLatch start = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(threadCount); + final AtomicInteger failures = new AtomicInteger(); + + for (int i = 0; i < threadCount; i++) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + start.await(); + for (int j = 0; j < loops; j++) { + if (!Integer.valueOf(7).equals(method.invoke(instance))) { + failures.incrementAndGet(); + } + } + } catch (Throwable e) { + failures.incrementAndGet(); + } finally { + done.countDown(); + } + } + }); + thread.start(); + } + + start.countDown(); + done.await(); + + assertThat(result.locations).isEqualTo(1); + assertThat(failures.get()).isEqualTo(0); + assertThat(CONCURRENT_HITS.get()).isEqualTo(threadCount * loops); + } + + @Test + public void lineInstrumentationWorksAfterEnterExitInstrumentation() throws Exception { + ORDERED_LINE_HITS.set(0); + ClassNode classNode = lineAtMethodEntryClass("LineAfterNormalInterceptors"); + TransformResult result = transformInOrder(classNode, OrderedEnterInterceptor.class, OrderedExitInterceptor.class, + OrderedExceptionExitInterceptor.class, OrderedLineInterceptor.class); + + Object instance = newInstance(result.classNode.name, result.bytes); + Object value = instance.getClass().getMethod("calculate", int.class).invoke(instance, 3); + + assertThat(result.locations).isEqualTo(4); + assertThat(value).isEqualTo(8); + assertThat(ORDERED_LINE_HITS.get()).isEqualTo(1); + } + + @Test + public void lineMatcherIgnoresLineNumbersInsertedBeforeOriginalEnterInsn() throws Exception { + ClassNode classNode = lineAtMethodEntryClass("LineIgnoresInsertedPrefix"); + MethodNode methodNode = findMethod(classNode, "calculate", "(I)I"); + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode); + insertPrefixLine(methodNode, methodProcessor.getEnterInsnNode(), 99); + + int locations = process(methodProcessor, PrefixLineInterceptor.class); + + assertThat(locations).isEqualTo(0); + } + + @Test + public void lineStackSaverInitializesTemporaryLocalsOnceUnderConcurrentAccess() throws Exception { + ClassNode classNode = stackLineClass("LineStackSaverConcurrentInit"); + MethodNode methodNode = findMethod(classNode, "stackLine", "()I"); + BlockingLineStackMethodProcessor methodProcessor = new BlockingLineStackMethodProcessor(classNode, methodNode); + List locations = new LineLocationMatcher(100).match(methodProcessor); + assertThat(locations).hasSize(1); + + final Location location = locations.get(0); + final StackSaver stackSaver = location.getStackSaver(); + final BindingContext bindingContext = new BindingContext(location, methodProcessor, stackSaver); + final CountDownLatch firstCallerInside = new CountDownLatch(1); + final CountDownLatch releaseFirstCaller = new CountDownLatch(1); + final CountDownLatch firstDone = new CountDownLatch(1); + final CountDownLatch secondDone = new CountDownLatch(1); + final AtomicReference firstError = new AtomicReference(); + final AtomicReference secondError = new AtomicReference(); + methodProcessor.blockFirstLineStackVariable(firstCallerInside, releaseFirstCaller); + + Thread first = new Thread(new Runnable() { + @Override + public void run() { + try { + stackSaver.store(new InsnList(), bindingContext); + } catch (Throwable e) { + firstError.set(e); + } finally { + firstDone.countDown(); + } + } + }); + first.start(); + assertThat(firstCallerInside.await(5, TimeUnit.SECONDS)).isTrue(); + + Thread second = new Thread(new Runnable() { + @Override + public void run() { + try { + stackSaver.load(new InsnList(), bindingContext); + } catch (Throwable e) { + secondError.set(e); + } finally { + secondDone.countDown(); + } + } + }); + second.start(); + secondDone.await(200, TimeUnit.MILLISECONDS); + + releaseFirstCaller.countDown(); + assertThat(firstDone.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(secondDone.await(5, TimeUnit.SECONDS)).isTrue(); + first.join(); + second.join(); + + assertThat(firstError.get()).isNull(); + assertThat(secondError.get()).isNull(); + assertThat(methodProcessor.lineStackVariableCalls()).isEqualTo(1); + } + + @Test + public void rejectAfterControlFlowKeepsFirstOccurrenceAndRejectsLaterDuplicate() throws Exception { + ClassNode classNode = duplicateLineAfterControlFlowClass("LineRejectAfterControlFlow"); + MethodNode methodNode = findMethod(classNode, "duplicateLine", "(I)I"); + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode); + + List locations = new LineLocationMatcher(LineMode.FRAME_AWARE, + LineDuplicatePolicy.REJECT_AFTER_CONTROL_FLOW, 100).match(methodProcessor); + + assertThat(locations).hasSize(1); + assertThat(locations.get(0).getInsnNode()).isSameAs(firstLineNumberNode(methodNode, 100)); + } + + @Test + public void lineLocationTreatsUnknownStackTypeAsNoStackToSave() { + LabelNode labelNode = new LabelNode(); + LineNumberNode lineNumberNode = new LineNumberNode(100, labelNode); + Frame frame = new Frame(0, 1); + frame.push(BasicValue.UNINITIALIZED_VALUE); + + LineLocation location = new LineLocation(lineNumberNode, 100, frame, 0); + + assertThat(location.isStackNeedSave()).isFalse(); + } + + @Test + public void lineMatcherRestoresAnalysisLimitsWhenPreparationFails() { + ClassNode classNode = stackLineClass("LineAnalysisLimitRestore"); + MethodNode methodNode = findMethod(classNode, "stackLine", "()I"); + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode); + int originMaxStack = methodNode.maxStack; + int originMaxLocals = methodNode.maxLocals; + methodNode.instructions = null; + + Throwable thrown = null; + try { + new LineLocationMatcher(100).match(methodProcessor); + } catch (Throwable e) { + thrown = e; + } + + assertThat(thrown).isInstanceOf(NullPointerException.class); + assertThat(methodNode.maxStack).isEqualTo(originMaxStack); + assertThat(methodNode.maxLocals).isEqualTo(originMaxLocals); + } + + private TransformResult transform(ClassNode classNode, Class interceptorClass) throws Exception { + return transformInOrder(classNode, interceptorClass); + } + + private TransformResult transformInOrder(ClassNode classNode, Class... interceptorClasses) throws Exception { + DefaultInterceptorClassParser parser = new DefaultInterceptorClassParser(); + int locations = 0; + for (MethodNode methodNode : classNode.methods) { + if ("".equals(methodNode.name)) { + continue; + } + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode); + for (Class interceptorClass : interceptorClasses) { + locations += process(methodProcessor, interceptorClass, parser); + } + } + byte[] bytes = AsmUtils.toBytes(classNode); + VerifyUtils.asmVerify(bytes); + return new TransformResult(classNode, bytes, locations); + } + + private int process(MethodProcessor methodProcessor, Class interceptorClass) throws Exception { + return process(methodProcessor, interceptorClass, new DefaultInterceptorClassParser()); + } + + private int process(MethodProcessor methodProcessor, Class interceptorClass, + DefaultInterceptorClassParser parser) throws Exception { + int locations = 0; + List processors = parser.parse(interceptorClass); + for (InterceptorProcessor processor : processors) { + locations += processor.process(methodProcessor).size(); + } + return locations; + } + + private ClassNode stackLineClass(String simpleName) { + ClassNode classNode = newClassNode(simpleName); + classNode.methods.add(defaultConstructor()); + + MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC, "stackLine", "()I", null, null); + LabelNode start = new LabelNode(); + LabelNode line = new LabelNode(); + LabelNode end = new LabelNode(); + methodNode.instructions.add(start); + methodNode.instructions.add(new IntInsnNode(Opcodes.BIPUSH, 7)); + methodNode.instructions.add(line); + methodNode.instructions.add(new LineNumberNode(100, line)); + methodNode.instructions.add(new VarInsnNode(Opcodes.ISTORE, 1)); + methodNode.instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); + methodNode.instructions.add(new InsnNode(Opcodes.IRETURN)); + methodNode.instructions.add(end); + methodNode.localVariables = new ArrayList(); + methodNode.localVariables.add(new LocalVariableNode("this", "L" + classNode.name + ";", null, start, end, 0)); + methodNode.localVariables.add(new LocalVariableNode("value", "I", null, line, end, 1)); + methodNode.maxStack = 1; + methodNode.maxLocals = 2; + classNode.methods.add(methodNode); + return classNode; + } + + private ClassNode localVariableClass(String simpleName) { + ClassNode classNode = newClassNode(simpleName); + classNode.methods.add(defaultConstructor()); + + MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC, "readLocal", "()I", null, null); + LabelNode start = new LabelNode(); + LabelNode line = new LabelNode(); + LabelNode end = new LabelNode(); + methodNode.instructions.add(start); + methodNode.instructions.add(new IntInsnNode(Opcodes.BIPUSH, 7)); + methodNode.instructions.add(new VarInsnNode(Opcodes.ISTORE, 1)); + methodNode.instructions.add(line); + methodNode.instructions.add(new LineNumberNode(100, line)); + methodNode.instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); + methodNode.instructions.add(new InsnNode(Opcodes.IRETURN)); + methodNode.instructions.add(end); + methodNode.localVariables = new ArrayList(); + methodNode.localVariables.add(new LocalVariableNode("this", "L" + classNode.name + ";", null, start, end, 0)); + methodNode.localVariables.add(new LocalVariableNode("value", "I", null, start, end, 1)); + methodNode.localVariables.add(new LocalVariableNode("stale", "I", null, start, end, 2)); + methodNode.maxStack = 1; + methodNode.maxLocals = 3; + classNode.methods.add(methodNode); + return classNode; + } + + private ClassNode lineAtMethodEntryClass(String simpleName) { + ClassNode classNode = newClassNode(simpleName); + classNode.methods.add(defaultConstructor()); + + MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC, "calculate", "(I)I", null, null); + LabelNode start = new LabelNode(); + LabelNode secondLine = new LabelNode(); + LabelNode end = new LabelNode(); + methodNode.instructions.add(start); + methodNode.instructions.add(new LineNumberNode(100, start)); + methodNode.instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); + methodNode.instructions.add(new InsnNode(Opcodes.ICONST_1)); + methodNode.instructions.add(new InsnNode(Opcodes.IADD)); + methodNode.instructions.add(new VarInsnNode(Opcodes.ISTORE, 2)); + methodNode.instructions.add(secondLine); + methodNode.instructions.add(new LineNumberNode(101, secondLine)); + methodNode.instructions.add(new VarInsnNode(Opcodes.ILOAD, 2)); + methodNode.instructions.add(new InsnNode(Opcodes.ICONST_2)); + methodNode.instructions.add(new InsnNode(Opcodes.IMUL)); + methodNode.instructions.add(new InsnNode(Opcodes.IRETURN)); + methodNode.instructions.add(end); + methodNode.localVariables = new ArrayList(); + methodNode.localVariables.add(new LocalVariableNode("this", "L" + classNode.name + ";", null, start, end, 0)); + methodNode.localVariables.add(new LocalVariableNode("value", "I", null, start, end, 1)); + methodNode.localVariables.add(new LocalVariableNode("sum", "I", null, secondLine, end, 2)); + methodNode.maxStack = 2; + methodNode.maxLocals = 3; + classNode.methods.add(methodNode); + return classNode; + } + + private ClassNode duplicateLineAfterControlFlowClass(String simpleName) { + ClassNode classNode = newClassNode(simpleName); + classNode.methods.add(defaultConstructor()); + + MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC, "duplicateLine", "(I)I", null, null); + LabelNode start = new LabelNode(); + LabelNode duplicateLine = new LabelNode(); + LabelNode end = new LabelNode(); + methodNode.instructions.add(start); + methodNode.instructions.add(new LineNumberNode(100, start)); + methodNode.instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); + methodNode.instructions.add(new JumpInsnNode(Opcodes.IFLT, duplicateLine)); + methodNode.instructions.add(new InsnNode(Opcodes.ICONST_1)); + methodNode.instructions.add(new InsnNode(Opcodes.IRETURN)); + methodNode.instructions.add(duplicateLine); + methodNode.instructions.add(new LineNumberNode(100, duplicateLine)); + methodNode.instructions.add(new InsnNode(Opcodes.ICONST_2)); + methodNode.instructions.add(new InsnNode(Opcodes.IRETURN)); + methodNode.instructions.add(end); + methodNode.localVariables = new ArrayList(); + methodNode.localVariables.add(new LocalVariableNode("this", "L" + classNode.name + ";", null, start, end, 0)); + methodNode.localVariables.add(new LocalVariableNode("value", "I", null, start, end, 1)); + methodNode.maxStack = 1; + methodNode.maxLocals = 2; + classNode.methods.add(methodNode); + return classNode; + } + + private ClassNode newClassNode(String simpleName) { + ClassNode classNode = new ClassNode(); + classNode.version = Opcodes.V1_8; + classNode.access = Opcodes.ACC_PUBLIC; + classNode.name = "com/alibaba/bytekit/asm/interceptor/generated/" + simpleName; + classNode.superName = "java/lang/Object"; + classNode.methods = new ArrayList(); + return classNode; + } + + private MethodNode defaultConstructor() { + MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC, "", "()V", null, null); + methodNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + methodNode.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", + false)); + methodNode.instructions.add(new InsnNode(Opcodes.RETURN)); + methodNode.maxStack = 1; + methodNode.maxLocals = 1; + return methodNode; + } + + private MethodNode findMethod(ClassNode classNode, String name, String desc) { + for (MethodNode methodNode : classNode.methods) { + if (name.equals(methodNode.name) && desc.equals(methodNode.desc)) { + return methodNode; + } + } + throw new IllegalArgumentException("method not found: " + name + desc); + } + + private void insertPrefixLine(MethodNode methodNode, AbstractInsnNode enterInsnNode, int line) { + LabelNode prefix = new LabelNode(); + InsnList instructions = new InsnList(); + instructions.add(prefix); + instructions.add(new LineNumberNode(line, prefix)); + methodNode.instructions.insertBefore(enterInsnNode, instructions); + } + + private LineNumberNode firstLineNumberNode(MethodNode methodNode, int line) { + for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode + .getNext()) { + if (insnNode instanceof LineNumberNode && ((LineNumberNode) insnNode).line == line) { + return (LineNumberNode) insnNode; + } + } + throw new IllegalArgumentException("line not found: " + line); + } + + private Object newInstance(String internalName, byte[] bytes) throws Exception { + Class clazz = new TestClassLoader().define(internalName.replace('/', '.'), bytes); + return clazz.newInstance(); + } + + private static class TransformResult { + private final ClassNode classNode; + private final byte[] bytes; + private final int locations; + + private TransformResult(ClassNode classNode, byte[] bytes, int locations) { + this.classNode = classNode; + this.bytes = bytes; + this.locations = locations; + } + } + + private static class TestClassLoader extends ClassLoader { + private TestClassLoader() { + super(AtLineFrameAwareTest.class.getClassLoader()); + } + + private Class define(String name, byte[] bytes) { + return defineClass(name, bytes, 0, bytes.length); + } + } + + private static class BlockingLineStackMethodProcessor extends MethodProcessor { + private CountDownLatch firstCallerInside; + private CountDownLatch releaseFirstCaller; + private final AtomicInteger lineStackVariableCalls = new AtomicInteger(); + + private BlockingLineStackMethodProcessor(ClassNode classNode, MethodNode methodNode) { + super(classNode, methodNode); + } + + private void blockFirstLineStackVariable(CountDownLatch firstCallerInside, + CountDownLatch releaseFirstCaller) { + this.firstCallerInside = firstCallerInside; + this.releaseFirstCaller = releaseFirstCaller; + } + + @Override + public LocalVariableNode initLineStackVariableNode(String name, Type type) { + if (lineStackVariableCalls.incrementAndGet() == 1) { + firstCallerInside.countDown(); + try { + releaseFirstCaller.await(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException(e); + } + } + return super.initLineStackVariableNode(name, type); + } + + private int lineStackVariableCalls() { + return lineStackVariableCalls.get(); + } + } +}