JDK-8293578 : Duplicate ldc generated by javac
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 18.0.2.1,19
  • Priority: P2
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2022-09-09
  • Updated: 2022-10-17
  • Resolved: 2022-09-16
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.
JDK 11 JDK 17 JDK 19 JDK 20
11.0.18-oracleFixed 17.0.6-oracleFixed 19.0.2Fixed 20 b16Fixed
Description
A DESCRIPTION OF THE PROBLEM :
Given the test code provided, we would expect that 
- the call on line 20 ("".equals(newString)) evaluates to "false"
- the call on line 21 ("A string".equals(newString)) evaluates to "true"

Running the code, however, shows that
- the call on line 20 evaluates to "true"
- the call on line 21 evaluates to "false".

It is noteworthy that the calls on line 34 and 35 evaluate to the expected values. The only difference between this two methods is that in broken(), the variable input is final, where in ok(), it is not.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile and run the test code.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The call on line 20 ("".equals(newString)) should evaluates to "false".
The call on line 21 ("A string".equals(newString)) should evaluates to "true".
ACTUAL -
The call on line 20 ("".equals(newString)) evaluates to "true".
The call on line 21 ("A string".equals(newString)) evaluates to "false".

---------- BEGIN SOURCE ----------
import java.lang.reflect.InvocationTargetException;

class Scratch {
    public static void main(String... args)
            throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        broken();
        ok();
    }

    private static void broken()
            throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        final var input = "A string";
        final var theClass = input.getClass();
        final var constructor = theClass.getConstructor();
        final var newString = constructor.newInstance();

        System.out.printf(
                ("in broken(), \"\".equals(newString)         = %b\n" +
                 "in broken(), \"A string\".equals(newString) = %b\n"),
                "".equals(newString),          // line 20
                "A string".equals(newString)); // line 21
    }

    private static void ok()
            throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        var input = "A string";
        final var theClass = input.getClass();
        final var constructor = theClass.getConstructor();
        final var newString = constructor.newInstance();

        System.out.printf(
                ("in ok(), \"\".equals(newString)             = %b\n" +
                 "in ok(), \"A string\".equals(newString)     = %b\n"),
                "".equals(newString),          // line 34
                "A string".equals(newString)); // line 35
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
not define variable "input" as "final". 

FREQUENCY : always
Comments
Fix request [11u] Backport for parity with 11.0.18-oracle. Clean backport. Tests pass. Additional testing: * manually run ConstantTypes.java test to verify * run jtreg tier1, tier2, jck runtime, jck compiler (linux x64 platform) to make sure the change does not break anything Risk of the backport to break the VM: very low
30-09-2022

Fix request [17u] Backport for parity with 17.0.6-oracle. Clean backport. Tests pass. Additional testing: * manually run ConstantTypes.java test to verify * run jtreg tier1, tier2, jck runtime, jck compiler (linux x64 platform) to make sure the change does not break anything Risk of the backport to break the VM: very low
30-09-2022

A pull request was submitted for review. URL: https://git.openjdk.org/jdk11u-dev/pull/1381 Date: 2022-09-30 06:17:03 +0000
30-09-2022

A pull request was submitted for review. URL: https://git.openjdk.org/jdk17u-dev/pull/758 Date: 2022-09-30 06:12:32 +0000
30-09-2022

Fix Request javac generates incorrect code for a valid source. The cause is that constant propagation goes accidentally too far, and the fix is to stop the constant propagation at appropriate places, which should generally be safe. The tests are passing with the change.
16-09-2022

Changeset: 39cd1635 Author: Jan Lahoda <jlahoda@openjdk.org> Date: 2022-09-16 11:37:45 +0000 URL: https://git.openjdk.org/jdk/commit/39cd1635bf07f42857e1a704734db66b2c2fa882
16-09-2022

A pull request was submitted for review. URL: https://git.openjdk.org/jdk19u/pull/25 Date: 2022-09-16 12:00:41 +0000
16-09-2022

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/10272 Date: 2022-09-14 17:15:30 +0000
14-09-2022

This seems caused by an issue in the attribution check for getClass(). When we compute the result type for a getClass call, we do not call `baseType`. As a result, the constant type stays there, and ends up nested in a wildcard type first, and the a captured type. To add insult to the injury, calling `baseType` on the captured type does NOT normalize the bound of the captured type, so the constant type stays there (even though the logic for inferring the `var` type does have a call to `baseType`, precisely for this reason). The following patch seems to fix things: ``` --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -2603,7 +2603,7 @@ public class Attr extends JCTree.Visitor { argtypes.isEmpty()) { // as a special case, x.getClass() has type Class<? extends |X|> return new ClassType(restype.getEnclosingType(), - List.of(new WildcardType(types.erasure(qualifierType), + List.of(new WildcardType(types.erasure(qualifierType.baseType()), BoundKind.EXTENDS, syms.boundClass)), restype.tsym, ```
09-09-2022

Simpler testcase --- public class VarConstantTest { public static void main(String... args) throws Throwable { final var input = "A string"; final var theClass = input.getClass(); final var constructor = theClass.getConstructor(); final var newString = constructor.newInstance(); System.err.println(newString); } } --- This should print an empty string, but it actually prints "A string": --- $ ~/tools/jdk/jdk-18/bin/java /tmp/VarConstantTest.java A string --- Probably the constant types get propagated too far.
09-09-2022

Could be a good idea hardening the above fix my making sure that, when type-checking `var`, `baseType` is called on the result of the type projection (Check.checkLocalVarType). E.g. instead of: if (tree.isImplicitlyTyped()) { //fixup local variable type v.type = chk.checkLocalVarType(tree, tree.init.type.baseType(), tree.name); } do this: if (tree.isImplicitlyTyped()) { //fixup local variable type v.type = chk.checkLocalVarType(tree, tree.init.type, tree.name).baseType() } or, alternatively, add a call to `baseType` directly on `checkLocalVarType`.
09-09-2022