JDK-8213329 : Normalize inclusion of non-explicit parameters in JVM attributes
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 10
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2018-11-02
  • Updated: 2019-12-04
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Relates :  
Description
Method descriptors may include 3 different kinds of parameters:
- Explicit (present in source code)
- Implicit (aka "mandated"���present in the language model, but not source)
- Synthetic (part of the compiler's translation strategy)

There is not a clear policy for which of these kinds of parameters are recognized when generating Signature, Runtime*ParameterAnnotations, and Runtime*TypeAnnotations attributes. Synthetic and implicit parameters are often elided, making it impossible for a client to derive meaningful information from the Signature unless they are familiar with the compilation conventions.

The exclusion of some parameters has caused trouble for downstream clients who try to re-create the mapping between signatures and descriptors. See JDK-8067975 and JDK-8062582. It also leads to unpredictable behavior in reflection methods: e.g., Executable.getGenericParameterTypes may or may not include non-explicit parameters, depending on whether the types involved happen to be encoded with a Signature.

The JVM Spec includes some disclaimers about parameter mismatches (4.7.9.1, 4.7.18, 4.7.20.1, 4.7.24). These are a recent effort to try to cope with the javac bug by describing its behavior. But they would not be necessary if javac were more consistent.

Some examples of current javac Signature-generation behavior:

public class SignatureTest<T> {

    class Inner {
        // descriptor: (LSignatureTest;Ljava/lang/Object;)V
        // signature: (TT;)V
        public Inner(T arg) {}
    }
    
    void m(int x, T y) {
        class Local {
            // descriptor: (LSignatureTest;IILjava/lang/Object;)V
            // signature: (I)V
            public Local(int z) { int i = x+y.hashCode()+z; }
        }
        
        Object anon = new Object() {
            // constructor descriptor: (LSignatureTest;ILjava/lang/Object;)V
            // constructor signature: none
            int i = x+y.hashCode();
        };
        
        // lambda descriptor: (ILjava/lang/Object;Ljava/lang/Object;)V
        // lambda signature: none
        java.util.function.Consumer<T> c = (T arg) -> { int i = x+y.hashCode(); };
    }

    enum E {
        A(null),
        B(null);
        
        // descriptor: (Ljava/lang/String;ILjava/lang/Iterable;)V
        // signature: (Ljava/lang/Iterable<Ljava/lang/String;>;)V
        E(Iterable<String> arg) {}
        
    }
    
}

Three kinds of use cases to consider:

1) Methods/constructors that are part of a non-private API. For interoperability, these don't use synthetic parameters (another compiler wouldn't know the convention). They _may_ have implicit parameters (specifically, either parameters of an implicit member, or the outer class parameter of a constructor). Implicit parameters are just as "real" as explicit parameters, and should always be included in JVM attributes.

2) Synthetic methods/constructors, whose parameters are necessarily all synthetic. These are pure compiler artifacts that are typically ignored by tools that are after a language-level view of a class. It makes sense to simply elide Signature and annotation attributes. But if not, all parameters should be treated equally.

3) Explicitly- or implicitly-declared methods/constructors with synthetic parameters. These include constructors for local and anonymous classes, which are "enhanced" with compiler-specific calling conventions that introduce additional parameters. In this case, no interoperability is promised, and attempts to reflectively examine the members are a bit of a hack. On the other hand, the method/constructor is specified to exist, it's reasonable for someone to go looking for it. When they do, we have to at least give them consistent results���we can't have arity of getGenericParameterTypes varying depending on whether a parameterized type gets used, for example.

My inclination is to consistently count all parameters in the Signature and annotation attributes. We could discuss treatment of synthetic parameters further, though.

This change would have some compatibility impact, but I think for the better. Consumers of these attributes must already be designed to work with pretty unpredictable, "best effort" data. (Or they have a bug lurking because they didn't account for this possibility.)

Care should be taken that the javac class reader properly handles attributes in use case category (1) (categories (2) and (3) should never be read by javac).