JDK-8321207 : javac is not accepting correct code
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 22
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2023-12-02
  • Updated: 2023-12-11
  • Resolved: 2023-12-06
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 22
22 b27Fixed
Related Reports
Relates :  
Description
This code is failing to compile:

import java.util.*;
import java.util.function.*;

class Test {
    public interface X {
        Object O = new Object() {
            Function<String, String> x(Map<String, String> m) {
                // Error: local variables referenced from a lambda expression must be final or effectively final
                return s -> m.get(s);
            }
        };
    }
}

reported by Paul Sandoz
Comments
Changeset: aaaae3ee Author: Vicente Romero <vromero@openjdk.org> Date: 2023-12-06 02:36:02 +0000 URL: https://git.openjdk.org/jdk/commit/aaaae3ee3cc966d05f6cf6fa81cecc122a8f9294
06-12-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/16963 Date: 2023-12-05 02:24:19 +0000
05-12-2023

Here's what seems to be happening. First, notice that if you change "Object O = " to "static Object O = " then the bug does not happen, even though the "static" modifier is completely superfluous (because Test is an interface and interfaces don't have non-static fields). In turn this happens because, internally, the synthetic method that gets created for O's initializer is not flagged as STATIC, even though of course it should be (interfaces don't have instance initializers). This seems like its own bug that could lie in wait and cause similar problems in other unsuspecting code. In other words, the addition of a completely superfluous modifier (like "static" or "final" on an interface field) should not change the compiler's internal data model. This unexpected inconsistency was tripping up the logic in the new method forEachInitializer() that was added in JDK-8194743. So this fixed the problem for me: diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java index d07b3a41ccb..479f0e7db74 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java @@ -487,10 +487,11 @@ protected void forEachInitializer(JCClassDecl classDef, boolean isStatic, Consum return; JCClassDecl initScanClassPrev = initScanClass; initScanClass = classDef; + boolean iface = (classDef.mods.flags & INTERFACE) != 0; try { for (List<JCTree> defs = classDef.defs; defs.nonEmpty(); defs = defs.tail) { JCTree def = defs.head; - if (!def.hasTag(METHODDEF) && ((TreeInfo.flags(def) & STATIC) != 0) == isStatic) + if (!def.hasTag(METHODDEF) && ((iface || (TreeInfo.flags(def) & STATIC) != 0) == isStatic)) handler.accept(def); } } finally { However, I think this is only a workaround and that there is a better fix, which would be to ensure that synthetic static initializer methods created for interfaces are always flagged as STATIC.
02-12-2023

Here's a slightly simpler reproducer: import java.util.*; import java.util.function.*; interface Test { Object O = new Object() { IntSupplier x(int m) { return () -> m; } }; } The bug definitely appeared with JDK-8194743. A cursory review doesn't reveal anything obvious.
02-12-2023

could be caused by JDK-8194743
02-12-2023