diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java index f30c81109ac..875251118b8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java @@ -407,6 +407,10 @@ public boolean isStrict() { return (flags() & STRICT) != 0; } + public boolean isStrictInstance() { + return (flags() & STRICT) != 0 && (flags() & STATIC) == 0; + } + public boolean hasStrict() { return (flags() & HAS_STRICT) != 0; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/CompileStates.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/CompileStates.java index 4896ecbe728..7cc506440ed 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/CompileStates.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/CompileStates.java @@ -63,7 +63,8 @@ public enum CompileState { TRANSPATTERNS(8), LOWER(9), UNLAMBDA(10), - GENERATE(11); + STRICT_FIELDS_PROXIES(11), + GENERATE(12); CompileState(int value) { this.value = value; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java new file mode 100644 index 00000000000..6f62bc2ee07 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.VarSymbol; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.tree.JCTree.JCAssign; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.tree.TreeTranslator; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Names; + +import static com.sun.tools.javac.code.Flags.FINAL; +import static com.sun.tools.javac.code.Flags.SYNTHETIC; +import static com.sun.tools.javac.tree.JCTree.Tag.VARDEF; + +import com.sun.tools.javac.jvm.Target; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.TreeInfo; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Options; + +/** This phase adds local variable proxies for strict fields that are read during the + * early construction phase (prologue) + * + * Assignments to the affected instance fields will be rewritten as assignments to a + * local proxy variable. Fields will be assigned to with its corresponding local variable + * proxy just before the super invocation and after its arguments, if any, have been evaluated. + * + * <p><b>This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice.</b> + */ +public class LocalProxyVarsGen extends TreeTranslator { + + protected static final Context.Key<LocalProxyVarsGen> valueInitializersKey = new Context.Key<>(); + + public static LocalProxyVarsGen instance(Context context) { + LocalProxyVarsGen instance = context.get(valueInitializersKey); + if (instance == null) + instance = new LocalProxyVarsGen(context); + return instance; + } + + private final Types types; + private final Names names; + private final Target target; + private TreeMaker make; + private final UnsetFieldsInfo unsetFieldsInfo; + private ClassSymbol currentClass = null; + private java.util.List<JCVariableDecl> strictInstanceFields; + private Map<JCMethodDecl, Set<Symbol>> strictFieldsReadInPrologue = new HashMap<>(); + + private final boolean noLocalProxyVars; + + @SuppressWarnings("this-escape") + protected LocalProxyVarsGen(Context context) { + context.put(valueInitializersKey, this); + make = TreeMaker.instance(context); + types = Types.instance(context); + names = Names.instance(context); + target = Target.instance(context); + unsetFieldsInfo = UnsetFieldsInfo.instance(context); + Options options = Options.instance(context); + noLocalProxyVars = options.isSet("noLocalProxyVars"); + } + + public void addStrictFieldReadInPrologue(JCMethodDecl constructor, Symbol sym) { + Set<Symbol> fieldSet = strictFieldsReadInPrologue.getOrDefault(constructor, new HashSet<>()); + fieldSet.add(sym); + strictFieldsReadInPrologue.put(constructor, fieldSet); + } + + public JCTree translateTopLevelClass(JCTree cdef, TreeMaker make) { + if (!noLocalProxyVars) { + try { + this.make = make; + return translate(cdef); + } finally { + // note that recursive invocations of this method fail hard + this.make = null; + } + } else { + return cdef; + } + } + + @Override + public void visitClassDef(JCClassDecl tree) { + ClassSymbol prevCurrentClass = currentClass; + java.util.List<JCVariableDecl> prevStrictInstanceFields = strictInstanceFields; + try { + currentClass = tree.sym; + strictInstanceFields = tree.defs.stream() + .filter(t -> t.hasTag(VARDEF)) + .map(t -> (JCVariableDecl)t) + .filter(vd -> vd.sym.isStrict() && !vd.sym.isStatic()) + .collect(List.collector()); + super.visitClassDef(tree); + } finally { + currentClass = prevCurrentClass; + strictInstanceFields = prevStrictInstanceFields; + } + } + + public void visitMethodDef(JCMethodDecl tree) { + if (strictFieldsReadInPrologue.get(tree) != null) { + Set<Symbol> fieldSet = strictFieldsReadInPrologue.get(tree); + java.util.List<JCVariableDecl> strictFieldsRead = new ArrayList<>(); + for (JCVariableDecl sfield : strictInstanceFields) { + if (fieldSet.contains(sfield.sym)) { + strictFieldsRead.add(sfield); + } + } + addLocalProxiesFor(tree, strictFieldsRead); + strictFieldsReadInPrologue.remove(tree); + } + super.visitMethodDef(tree); + } + + void addLocalProxiesFor(JCMethodDecl constructor, java.util.List<JCVariableDecl> multiAssignedStrictFields) { + ListBuffer<JCStatement> localDeclarations = new ListBuffer<>(); + Map<Symbol, Symbol> fieldToLocalMap = new LinkedHashMap<>(); + + for (JCVariableDecl fieldDecl : multiAssignedStrictFields) { + long flags = SYNTHETIC; + VarSymbol proxy = new VarSymbol(flags, newLocalName(fieldDecl.name.toString()), fieldDecl.sym.erasure(types), constructor.sym); + fieldToLocalMap.put(fieldDecl.sym, proxy); + JCVariableDecl localDecl = make.at(constructor.pos).VarDef(proxy, fieldDecl.init); + localDecl.vartype = fieldDecl.vartype; + localDeclarations = localDeclarations.append(localDecl); + } + + FieldRewriter fieldRewriter = new FieldRewriter(constructor, fieldToLocalMap); + ListBuffer<JCStatement> newBody = new ListBuffer<>(); + for (JCStatement st : constructor.body.stats) { + newBody = newBody.append(fieldRewriter.translate(st)); + } + localDeclarations.addAll(newBody); + ListBuffer<JCStatement> assigmentsBeforeSuper = new ListBuffer<>(); + for (Symbol vsym : fieldToLocalMap.keySet()) { + Symbol local = fieldToLocalMap.get(vsym); + assigmentsBeforeSuper.append(make.at(constructor.pos()).Assignment(vsym, make.at(constructor.pos()).Ident(local))); + } + constructor.body.stats = localDeclarations.toList(); + JCTree.JCMethodInvocation constructorCall = TreeInfo.findConstructorCall(constructor); + if (constructorCall.args.isEmpty()) { + // this is just a super invocation with no arguments we can set the fields just before the invocation + // and let Gen do the rest + TreeInfo.mapSuperCalls(constructor.body, supercall -> make.Block(0, assigmentsBeforeSuper.toList().append(supercall))); + } else { + // we need to generate fresh local variables to catch the values of the arguments, then + // assign the proxy locals to the fields and finally invoke the super with the fresh local variables + int argPosition = 0; + ListBuffer<JCStatement> superArgsProxies = new ListBuffer<>(); + for (JCExpression arg : constructorCall.args) { + long flags = SYNTHETIC | FINAL; + VarSymbol proxyForArgSym = new VarSymbol(flags, newLocalName("" + argPosition), types.erasure(arg.type), constructor.sym); + JCVariableDecl proxyForArgDecl = make.at(constructor.pos).VarDef(proxyForArgSym, arg); + superArgsProxies = superArgsProxies.append(proxyForArgDecl); + argPosition++; + } + List<JCStatement> superArgsProxiesList = superArgsProxies.toList(); + ListBuffer<JCExpression> newArgs = new ListBuffer<>(); + for (JCStatement argProxy : superArgsProxies) { + newArgs.add(make.at(argProxy.pos).Ident((JCVariableDecl) argProxy)); + } + constructorCall.args = newArgs.toList(); + TreeInfo.mapSuperCalls(constructor.body, + supercall -> make.Block(0, superArgsProxiesList.appendList(assigmentsBeforeSuper.toList()).append(supercall))); + } + } + + Name newLocalName(String name) { + return names.fromString("local" + target.syntheticNameChar() + name); + } + + class FieldRewriter extends TreeTranslator { + JCMethodDecl md; + Map<Symbol, Symbol> fieldToLocalMap; + boolean ctorPrologue = true; + + public FieldRewriter(JCMethodDecl md, Map<Symbol, Symbol> fieldToLocalMap) { + this.md = md; + this.fieldToLocalMap = fieldToLocalMap; + } + + @Override + public void visitIdent(JCTree.JCIdent tree) { + if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) { + result = make.at(md).Ident(fieldToLocalMap.get(tree.sym)); + } else { + result = tree; + } + } + + @Override + public void visitSelect(JCTree.JCFieldAccess tree) { + super.visitSelect(tree); + if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) { + result = make.at(md).Ident(fieldToLocalMap.get(tree.sym)); + } else { + result = tree; + } + } + + @Override + public void visitAssign(JCAssign tree) { + JCExpression previousLHS = tree.lhs; + super.visitAssign(tree); + if (ctorPrologue && previousLHS != tree.lhs) { + unsetFieldsInfo.removeUnsetFieldInfo(currentClass, tree); + } + } + + @Override + public void visitApply(JCTree.JCMethodInvocation tree) { + Name methName = TreeInfo.name(tree.meth); + boolean isConstructorCall = methName == names._this || methName == names._super; + super.visitApply(tree); + if (isConstructorCall) { + ctorPrologue = false; + } + } + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java index 19c4c18b167..ad7a66ae7ee 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java @@ -115,6 +115,7 @@ public class Resolve { final EnumSet<VerboseResolutionMode> verboseResolutionMode; final boolean dumpMethodReferenceSearchResults; final boolean dumpStacktraceOnError; + private final LocalProxyVarsGen localProxyVarsGen; WriteableScope polymorphicSignatureScope; @@ -154,6 +155,7 @@ protected Resolve(Context context) { allowRecords = Feature.RECORDS.allowedInSource(source); dumpMethodReferenceSearchResults = options.isSet("debug.dumpMethodReferenceSearchResults"); dumpStacktraceOnError = options.isSet("dev") || options.isSet(DOE); + localProxyVarsGen = LocalProxyVarsGen.instance(context); } /** error symbols, which are returned when resolution fails @@ -1537,7 +1539,12 @@ Symbol findVar(DiagnosticPosition pos, Env<AttrContext> env, Name name) { return new StaticError(sym); if (env1.info.ctorPrologue && !isAllowedEarlyReference(pos, env1, (VarSymbol)sym)) { if (!env.tree.hasTag(ASSIGN) || !TreeInfo.isIdentOrThisDotIdent(((JCAssign)env.tree).lhs)) { - return new RefBeforeCtorCalledError(sym); + if (!sym.isStrictInstance()) { + return new RefBeforeCtorCalledError(sym); + } else { + localProxyVarsGen.addStrictFieldReadInPrologue(env.enclMethod, sym); + return sym; + } } } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/UnsetFieldsInfo.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/UnsetFieldsInfo.java index ecd8ecd2d04..fea2b4f9c61 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/UnsetFieldsInfo.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/UnsetFieldsInfo.java @@ -88,4 +88,11 @@ public Set<VarSymbol> getUnsetFields(ClassSymbol csym, JCTree tree) { } return null; } + + public void removeUnsetFieldInfo(ClassSymbol csym, JCTree tree) { + Map<JCTree, Set<VarSymbol>> treeToFieldsMap = unsetFieldsMap.get(csym); + if (treeToFieldsMap != null) { + treeToFieldsMap.remove(tree); + } + } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Code.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Code.java index 6f8b1a3b79d..afa15e8f080 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Code.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Code.java @@ -190,6 +190,8 @@ public boolean checkLimits(DiagnosticPosition pos, Log log) { private Map<Integer, Set<VarSymbol>> cpToUnsetFieldsMap = new HashMap<>(); + public Set<VarSymbol> initialUnsetFields; + public Set<VarSymbol> currentUnsetFields; boolean generateAssertUnsetFieldsFrame; @@ -1355,7 +1357,7 @@ void emitStackMapFrame(int pc, int localsSize) { } Set<VarSymbol> unsetFieldsAtPC = cpToUnsetFieldsMap.get(pc); - boolean generateAssertUnsetFieldsEntry = unsetFieldsAtPC != null && generateAssertUnsetFieldsFrame; + boolean generateAssertUnsetFieldsEntry = unsetFieldsAtPC != null && generateAssertUnsetFieldsFrame && !lastFrame.unsetFields.equals(unsetFieldsAtPC) ; if (stackMapTableBuffer == null) { stackMapTableBuffer = new StackMapTableEntry[20]; @@ -1366,10 +1368,10 @@ void emitStackMapFrame(int pc, int localsSize) { } if (generateAssertUnsetFieldsEntry) { - if (lastFrame.unsetFields == null || !lastFrame.unsetFields.equals(unsetFieldsAtPC)) { - stackMapTableBuffer[stackMapBufferSize++] = new StackMapTableEntry.AssertUnsetFields(pc, unsetFieldsAtPC); - frame.unsetFields = unsetFieldsAtPC; - } + stackMapTableBuffer[stackMapBufferSize++] = new StackMapTableEntry.AssertUnsetFields(pc, unsetFieldsAtPC); + frame.unsetFields = unsetFieldsAtPC; + } else { + frame.unsetFields = lastFrame.unsetFields; } stackMapTableBuffer[stackMapBufferSize++] = StackMapTableEntry.getInstance(frame, lastFrame, types, pc); @@ -1403,6 +1405,7 @@ StackMapFrame getInitialFrame() { } frame.pc = -1; frame.stack = null; + frame.unsetFields = initialUnsetFields; return frame; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java index 71272d92471..22f7bd973a6 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java @@ -586,11 +586,11 @@ void rewriteInitializersIfNeeded(JCMethodDecl md, List<JCStatement> initCode) { } } - class InitializerVisitor extends TreeScanner { + public static class InitializerVisitor extends TreeScanner { JCMethodDecl md; Set<JCExpression> exprSet; - InitializerVisitor(JCMethodDecl md, Set<JCExpression> exprSet) { + public InitializerVisitor(JCMethodDecl md, Set<JCExpression> exprSet) { this.md = md; this.exprSet = exprSet; } @@ -998,6 +998,7 @@ else if (tree.body != null) { Set<VarSymbol> prevUnsetFields = code.currentUnsetFields; if (meth.isConstructor()) { code.currentUnsetFields = unsetFieldsInfo.getUnsetFields(env.enclClass.sym, tree.body); + code.initialUnsetFields = unsetFieldsInfo.getUnsetFields(env.enclClass.sym, tree.body); } try { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java index 05efcf7c041..6337b3ce991 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java @@ -85,8 +85,6 @@ import static com.sun.tools.javac.code.Kinds.Kind.*; -import com.sun.tools.javac.code.Lint; -import com.sun.tools.javac.code.Lint.LintCategory; import com.sun.tools.javac.code.Symbol.ModuleSymbol; import com.sun.tools.javac.resources.CompilerProperties.Errors; @@ -1563,6 +1561,8 @@ class ScanNested extends TreeScanner { Set<Env<AttrContext>> dependencies = new LinkedHashSet<>(); protected boolean hasLambdas; protected boolean hasPatterns; + protected boolean hasValueClasses; + protected boolean hasStrictFields; @Override public void visitClassDef(JCClassDecl node) { Type st = types.supertype(node.sym.type); @@ -1574,6 +1574,7 @@ public void visitClassDef(JCClassDecl node) { if (dependencies.add(stEnv)) { boolean prevHasLambdas = hasLambdas; boolean prevHasPatterns = hasPatterns; + boolean prevHasStrictFields = hasStrictFields; try { scan(stEnv.tree); } finally { @@ -1586,12 +1587,14 @@ public void visitClassDef(JCClassDecl node) { */ hasLambdas = prevHasLambdas; hasPatterns = prevHasPatterns; + hasStrictFields = prevHasStrictFields; } } envForSuperTypeFound = true; } st = types.supertype(st); } + hasValueClasses = node.sym.isValueClass(); super.visitClassDef(node); } @Override @@ -1631,12 +1634,18 @@ public void visitSwitchExpression(JCSwitchExpression tree) { hasPatterns |= tree.patternSwitch; super.visitSwitchExpression(tree); } + + @Override + public void visitVarDef(JCVariableDecl tree) { + hasStrictFields |= tree.sym.isStrict(); + super.visitVarDef(tree); + } } ScanNested scanner = new ScanNested(); scanner.scan(env.tree); for (Env<AttrContext> dep: scanner.dependencies) { - if (!compileStates.isDone(dep, CompileState.WARN)) - desugaredEnvs.put(dep, desugar(warn(flow(attribute(dep))))); + if (!compileStates.isDone(dep, CompileState.WARN)) + desugaredEnvs.put(dep, desugar(warn(flow(attribute(dep))))); } //We need to check for error another time as more classes might @@ -1716,6 +1725,15 @@ public void visitSwitchExpression(JCSwitchExpression tree) { compileStates.put(env, CompileState.UNLAMBDA); } + if (scanner.hasValueClasses || scanner.hasStrictFields) { + if (shouldStop(CompileState.STRICT_FIELDS_PROXIES)) + return; + for (JCTree def : cdefs) { + LocalProxyVarsGen.instance(context).translateTopLevelClass(def, localMake); + } + compileStates.put(env, CompileState.STRICT_FIELDS_PROXIES); + } + //generate code for each class for (List<JCTree> l = cdefs; l.nonEmpty(); l = l.tail) { JCClassDecl cdef = (JCClassDecl)l.head; diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFinalInstanceFieldsTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFinalInstanceFieldsTest.java index 32210d8d8a8..68c52f3bd0d 100644 --- a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFinalInstanceFieldsTest.java +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFinalInstanceFieldsTest.java @@ -24,7 +24,7 @@ /* * @test * @enablePreview - * @compile --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED -XDgenerateAssertUnsetFieldsFrame StrictFinalInstanceFieldsTest.java + * @compile --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED -XDgenerateAssertUnsetFieldsFrame -XDnoLocalProxyVars StrictFinalInstanceFieldsTest.java * @run main/othervm -Xlog:verification StrictFinalInstanceFieldsTest */ diff --git a/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.java b/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.java index 91459cabc8d..6c4a1de0818 100644 --- a/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.java +++ b/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.java @@ -5,7 +5,7 @@ * @compile/fail/ref=ValueClassSuperInitFails.out -XDrawDiagnostics ValueClassSuperInitFails.java * @enablePreview */ - +import java.util.function.Function; abstract value class AR<V> implements java.io.Serializable { public AR(V initialValue) { } @@ -172,7 +172,7 @@ public ValueClassSuperInitFails(float[][] x) { } public ValueClassSuperInitFails(int[][] z) { - super((Runnable)() -> x); // this should FAIL + super((Function<Integer, Integer>) f -> x); } public ValueClassSuperInitFails(long[][] z) { diff --git a/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.out b/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.out index 3531acd3b91..914a97b63aa 100644 --- a/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.out +++ b/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.out @@ -12,7 +12,6 @@ ValueClassSuperInitFails.java:135:9: compiler.err.cant.ref.before.ctor.called: t ValueClassSuperInitFails.java:143:9: compiler.err.non.canonical.constructor.invoke.another.constructor: ValueClassSuperInitFails.Record1 ValueClassSuperInitFails.java:148:9: compiler.err.non.canonical.constructor.invoke.another.constructor: ValueClassSuperInitFails.Record2 ValueClassSuperInitFails.java:161:41: compiler.err.cant.ref.before.ctor.called: this -ValueClassSuperInitFails.java:175:31: compiler.err.cant.ref.before.ctor.called: x ValueClassSuperInitFails.java:179:15: compiler.err.cant.ref.before.ctor.called: this ValueClassSuperInitFails.java:43:13: compiler.err.call.must.only.appear.in.ctor ValueClassSuperInitFails.java:47:14: compiler.err.call.must.only.appear.in.ctor @@ -25,4 +24,4 @@ ValueClassSuperInitFails.java:99:13: compiler.err.return.before.superclass.initi ValueClassSuperInitFails.java:170:18: compiler.err.call.must.only.appear.in.ctor - compiler.note.preview.filename: ValueClassSuperInitFails.java, DEFAULT - compiler.note.preview.recompile -25 errors +24 errors diff --git a/test/langtools/tools/javac/valhalla/value-objects/ValueObjectCompilationTests.java b/test/langtools/tools/javac/valhalla/value-objects/ValueObjectCompilationTests.java index 0fd0acf4cf3..4ca45e7cebd 100644 --- a/test/langtools/tools/javac/valhalla/value-objects/ValueObjectCompilationTests.java +++ b/test/langtools/tools/javac/valhalla/value-objects/ValueObjectCompilationTests.java @@ -26,7 +26,7 @@ * * @test * @bug 8287136 8292630 8279368 8287136 8287770 8279840 8279672 8292753 8287763 8279901 8287767 8293183 8293120 - * 8329345 8341061 8340984 + * 8329345 8341061 8340984 8334484 * @summary Negative compilation tests, and positive compilation (smoke) tests for Value Objects * @library /lib/combo /tools/lib * @modules @@ -916,11 +916,11 @@ value class V { } """ ); - assertFail("compiler.err.cant.ref.before.ctor.called", + assertOK( """ value class Test { Test t = null; - Runnable r = () -> { System.err.println(t); }; + Runnable r = () -> { System.err.println(t); }; // compiler will generate a local proxy for `t` } """ ); @@ -934,12 +934,12 @@ value class Test { } """ ); - assertFail("compiler.err.cant.ref.before.ctor.called", + assertOK( """ value class V { int x; int y = x + 1; // allowed - V1() { + V() { x = 12; // super(); } @@ -968,7 +968,7 @@ value class V3 extends AV1 { } """ ); - assertFail("compiler.err.cant.ref.before.ctor.called", + assertOK( """ value class V4 { int x; @@ -998,7 +998,7 @@ value class V { } """ ); - assertFail("compiler.err.cant.ref.before.ctor.called", + assertOK( """ value class V { int x = "abc".length(); @@ -1451,6 +1451,7 @@ void testAssertUnsetFieldsSMEntry() throws Exception { "--enable-preview", "-source", Integer.toString(Runtime.version().feature()), "-XDgenerateAssertUnsetFieldsFrame", + "-XDnoLocalProxyVars", "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED" }; setCompileOptions(testOptions); @@ -1523,7 +1524,7 @@ class Test { Assert.check(data.expectedFrameTypes().length == stackMapTable.entries.length, "unexpected stackmap length"); int expectedUnsetFieldsIndex = 0; for (StackMapTable_attribute.stack_map_entry entry : stackMapTable.entries) { - Assert.check(data.expectedFrameTypes()[entryIndex++] == entry.entry_type); + Assert.check(data.expectedFrameTypes()[entryIndex++] == entry.entry_type, "expected " + data.expectedFrameTypes()[entryIndex - 1] + " found " + entry.entry_type); if (entry.entry_type == 246) { StackMapTable_attribute.assert_unset_fields auf = (StackMapTable_attribute.assert_unset_fields)entry; Assert.check(data.expectedUnsetFields()[expectedUnsetFieldsIndex].length == auf.number_of_unset_fields); @@ -1577,4 +1578,65 @@ private void checkAttributeNotPresent(Attributes attributes, Class<? extends Att } } } + + @Test + void testLocalProxyVars() throws Exception { + String[] previousOptions = getCompileOptions(); + try { + String[] testOptions = { + "--enable-preview", + "-source", Integer.toString(Runtime.version().feature()), + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED" + }; + setCompileOptions(testOptions); + String[] sources = new String[] { + """ + value class Test { + int i; + int j; + Test() {// javac should generate a proxy local var for `i` + i = 1; + j = i; // as here `i` is being read during the early construction phase, use the local var instead + super(); + System.err.println(i); + } + } + """, + """ + import jdk.internal.vm.annotation.Strict; + class Test { + @Strict + int i; + @Strict + int j; + Test() { + i = 1; + j = i; + super(); + System.err.println(i); + } + } + """ + }; + for (String source : sources) { + File dir = assertOK(true, source); + File fileEntry = dir.listFiles()[0]; + ClassFile classFile = ClassFile.read(fileEntry); + String expectedCodeSequence = "iconst_1,istore_1,aload_0,iload_1,putfield,aload_0,iload_1,putfield," + + "aload_0,invokespecial,getstatic,aload_0,getfield,invokevirtual,return,"; + for (Method method : classFile.methods) { + if (method.getName(classFile.constant_pool).equals("<init>")) { + Code_attribute code = (Code_attribute)method.attributes.get("Code"); + String foundCodeSequence = ""; + for (Instruction inst: code.getInstructions()) { + foundCodeSequence += inst.getMnemonic() + ","; + } + Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence); + } + } + } + } finally { + setCompileOptions(previousOptions); + } + } }