Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow extracting line number from advice #741

Open
raphw opened this issue Oct 16, 2019 · 13 comments
Open

Allow extracting line number from advice #741

raphw opened this issue Oct 16, 2019 · 13 comments
Assignees
Milestone

Comments

@raphw
Copy link
Owner

raphw commented Oct 16, 2019

If possible, allow accessing the line number from advice.

@raphw raphw self-assigned this Oct 16, 2019
@raphw raphw added this to the 1.10.1 milestone Oct 16, 2019
@tylerbenson
Copy link

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?

@raphw
Copy link
Owner Author

raphw commented Aug 10, 2020

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!

@tylerbenson
Copy link

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.

@raphw
Copy link
Owner Author

raphw commented Sep 20, 2020

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:

  1. caching all discovered labels until the first non-label/non-line-number instruction.
  2. storing the last line number that was discovered in a field
  3. replaying the labels and latest line number once the first non-label/non-line-number instruction is discoverd.
  4. 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.

@tylerbenson
Copy link

Thanks for the update! Sad that it's not straightforward.

@raphw
Copy link
Owner Author

raphw commented Sep 21, 2020

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.

@raphw
Copy link
Owner Author

raphw commented Dec 15, 2020

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();
        }
    }
}

@tylerbenson
Copy link

Thanks for the update!

@bearxiongzhen
Copy link

Is there a major version of this feature?
Are there sample tutorials to download?

@raphw
Copy link
Owner Author

raphw commented Mar 30, 2022

You can use the code above but there is no plan for this feature right now.

@bearxiongzhen
Copy link

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:
image
How to solve this problem?

@raphw
Copy link
Owner Author

raphw commented Apr 2, 2022

You can use an Executable rather then a Method type if you also want to process constructors (which cannot be represented as Method).

@bearxiongzhen
Copy link

I understand the problem, I set all the function buried points by default, just remove the constructor, thank you very much

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants