raphw / byte-buddy

Runtime code generation for the Java virtual machine.
https://bytebuddy.net
Apache License 2.0
6.23k stars 804 forks source link

AgentBuilder.RedefinitionStrategy.RETRANSFORMATION will cause method lost parameter names #1562

Closed yangrubing closed 10 months ago

yangrubing commented 10 months ago

Description

I am using ByteBuddy 1.12.13 to do bytecode enhancement , and it works well in most cases. Recently , our colleage report their app cann't get methods' parameter's name (they need parameter name to parse SpEL and already compiled their app with -parameters). Then, I checked our instrumentation code like below .

    protected ResettableClassFileTransformer buildClassFileTransformer(Instrumentation inst) {
        final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(false));
        InstrumentationListener instrumentationListener = new InstrumentationListener();
        AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy)
                .ignore(getAgentBuilderIgnoreCondition())
                .with(new AgentBuilder.CircularityLock.Default())
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                .with(new AgentBuilder.InjectionStrategy.UsingUnsafe.OfFactory(ClassInjector.UsingUnsafe.Factory.resolve(inst)))
                .with(new AgentBuilder.InitializationStrategy.SelfInjection.Eager())
                .with(instrumentationListener)
                .disableClassFormatChanges();

        ElementMatcher.Junction<Object> typeMatcher = getTypeMatcher();
        if (typeMatcher == null) {
            logger.info("Element type matcher for {} is null, will not do the transform", this.getClass());
            return null;
        }
        ResettableClassFileTransformer transformer = agentBuilder.type(typeMatcher).transform(getTransformer()).installOn(inst);
        instrumentationListener.outputLog(true);
        return transformer;
    }

And , I tested in my laptop the parameter name seems did lost

    @GetMapping("/param")
    public String param(String test) {
        try {
            return HelloController.class.getMethod("param", String.class).getParameters()[0].getName(); 
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

// OUTPUT: arg0  , should return test

And above code return arg0 which means the parameter names are lost . And I tried to modify our instrumentation code , only change the .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) to .with(AgentBuilder.RedefinitionStrategy.REDEFINITION) make our code work and the test code return 'test' correctly.

Ask for help

yangrubing commented 10 months ago

I compared the REDEFINITION and RETRANSFORMATION approaches and use jad to decopile the enhanced classes, and found the generated code are the same , even more confused now

raphw commented 10 months ago

This is unfortunately a bug in the JDK which forgets reconstruction of parameter names in some builds.

The difference is wether Instrumentation.redefineClasses or Instrumentation.retransformeClasses is used.

raphw commented 10 months ago

It's only the call to the VM. Since Java 6, retransformation is the preferred way.

yangrubing commented 10 months ago

Very helpful, thanks a lot, you are doing a great job👍

yangrubing commented 10 months ago

Sorry to reopen this again because I am more confused now , here is my code

image

The param method is my target method , and my ResettableClassFileTransformer

image

My interceptor class

image

And I dumped class generated by bytebuddy by using -Dnet.bytebuddy.dump , and I got two class in my directory

image

And then I executed javap -v for both classes, and both the class file don't have MethodParameters table in the output

image

Even the original class doesn't have MethodParameters

And I can confirm I compiled the class with -parameters as the class generated by my IDE indicated the MethodParameters exists as show below .

image image

So could you please help make this clear, why even the origin class don;t have MethodParameters in the class file and how to fix that , thank you in advance. BTW . I wrote a small java agent use javaassit API and it works OK without lost the parameters with the same JDK build

raphw commented 10 months ago

This is a JVM bug. In Mockito, we work around it like this: https://github.com/mockito/mockito/blob/757b37a1445be3abd08238c0da62a9a9e635f1ba/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java#L398

yangrubing commented 10 months ago

Thanks , tried the solution and it works now ,BTW , will this issue be fixed in later bytebuddy release ?

raphw commented 10 months ago

This is already fixed in newer JVM versions, I hope this just dies off.

yangrubing commented 10 months ago

Thanks.