byte-buddy
byte-buddy copied to clipboard
Allow extracting line number from advice
If possible, allow accessing the line number from advice.
I see this with the 1.10.1 milestone. Was it actually implemented? If so is there any docs on how to use it? If not is there any immediate plans for it?
Sorry for the confusion, I use the field as "reported version" and close the issue once fixed.
I did not yet have time for it, unfortunately. Was doing a lot of Mockito stuff in the last weeks but I will certainly look into it at some point!
Something like this is what I had in mind...
@Advice.Origin int lineNumber
@Advice.Origin("#t.#m:#l") String stackFrame
But I'm curious to see how you decide to implement it.
I have looked into this, thinking it's a minor change but it turns out this would require some reorganization
The way Advice
is built is that it intercepts the first "code like" instruction to play off the enter advice. This code-like instruction might just be the discovery of a label onto which the line number is later applied. There is however no guarantee that the label is not used to do something else (it might even be used for multiple things). It's therefore a bit of a gamble to delay the enter code application; if the label was used for a jump target for example, the advised code might be able to escape back to the entry of the method by using this label, breaking the assumptions of the advice.
It can still be done by:
- caching all discovered labels until the first non-label/non-line-number instruction.
- storing the last line number that was discovered in a field
- replaying the labels and latest line number once the first non-label/non-line-number instruction is discoverd.
- writing back the line number with a synthetic label before (4).
I'm still planning to add this, it's just not as trivial as I hoped.
Thanks for the update! Sad that it's not straightforward.
I made some progress already, it's mainly due to the JVM not treating it as real metadata but it's overcomeable, I'm sure.
Just to let you know, I am a bit stuck on this despite looking into it for quite a while. The problem is that the line number is not known when the enter advice is dispatched. In the general (but unlikely case), it might not even be known in the exit advice. I do however have a prototype that you can plug yourself. I will continue thinking about this issue, maybe there is a way to get this done properly.
public class Sample {
public static void main(String[] args) throws Exception {
Advice advice = Advice.withCustomMapping()
.bind(LineNumber.class, new StackManipulation() {
@Override
public boolean isValid() {
return true;
}
@Override
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
Integer line = LineExtractingMethodVisitor.LINE.get();
methodVisitor.visitLdcInsn(line == null ? -1 : line);
return new Size(1, 1);
}
}, int.class)
.to(Sample.class);
Class<?> type = new ByteBuddy()
.redefine(Sample.class)
.name("linenumber.Sample")
.visit(new AsmVisitorWrapper.ForDeclaredMethods().method(named("bar"), new LineExtractingMethodVisitorWrapper()))
.visit(advice.on(named("bar")))
.make()
.load(Sample.class.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
.getLoaded();
Object instance = type.getConstructor().newInstance();
type.getMethod("bar").invoke(instance);
}
public void bar() {
System.out.println("In the method");
}
@Retention(RetentionPolicy.RUNTIME)
public @interface LineNumber { }
@Advice.OnMethodEnter
@Advice.OnMethodExit
public static void enter(@LineNumber int lineNumber) {
System.out.println(lineNumber);
}
public static class LineExtractingMethodVisitorWrapper implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper {
@Override
public MethodVisitor wrap(
TypeDescription instrumentedType,
MethodDescription instrumentedMethod,
MethodVisitor methodVisitor,
Implementation.Context implementationContext,
TypePool typePool,
int writerFlags,
int readerFlags
) {
return new LineExtractingMethodVisitor(methodVisitor);
}
}
public static class LineExtractingMethodVisitor extends MethodVisitor {
static final ThreadLocal<Integer> LINE = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return -1;
}
};
public LineExtractingMethodVisitor(MethodVisitor methodVisitor) {
super(Opcodes.ASM9, methodVisitor);
}
@Override
public void visitLineNumber(int line, Label start) {
LINE.set(line);
super.visitLineNumber(line, start);
}
@Override
public void visitEnd() {
super.visitEnd();
LINE.remove();
}
}
}
Thanks for the update!
Is there a major version of this feature? Are there sample tutorials to download?
You can use the code above but there is no plan for this feature right now.
I used the above example and found that adding the parameter @Advice.Origin Method method in advice OnMethodEnter will throw an exception,as shown in the following figure: How to solve this problem?
You can use an Executable rather then a Method type if you also want to process constructors (which cannot be represented as Method).
I understand the problem, I set all the function buried points by default, just remove the constructor, thank you very much