1 /*
2 * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 /*
25 * @test
26 * @bug 8376843
27 * @summary add more regression tests for local variable proxies
28 * @modules jdk.compiler/com.sun.tools.javac.code
29 * jdk.compiler/com.sun.tools.javac.comp
30 * jdk.compiler/com.sun.tools.javac.file
31 * jdk.compiler/com.sun.tools.javac.main
32 * jdk.compiler/com.sun.tools.javac.tree
33 * jdk.compiler/com.sun.tools.javac.util
34 */
35
36 import java.net.URI;
37
38 import java.util.HashSet;
39 import java.util.Set;
40
41 import javax.tools.JavaFileObject;
42 import javax.tools.SimpleJavaFileObject;
43
44 import com.sun.tools.javac.comp.AttrContext;
45 import com.sun.tools.javac.comp.Env;
46 import com.sun.tools.javac.comp.LocalProxyVarsGen;
47 import com.sun.tools.javac.comp.Modules;
48 import com.sun.tools.javac.file.JavacFileManager;
49 import com.sun.tools.javac.main.JavaCompiler;
50 import com.sun.tools.javac.tree.JCTree;
51 import com.sun.tools.javac.tree.JCTree.*;
52 import com.sun.tools.javac.tree.TreeMaker;
53 import com.sun.tools.javac.util.Assert;
54 import com.sun.tools.javac.util.Context;
55 import com.sun.tools.javac.util.Context.Factory;
56 import com.sun.tools.javac.util.List;
57 import com.sun.tools.javac.util.ListBuffer;
58 import com.sun.tools.javac.util.Options;
59
60 import static com.sun.tools.javac.util.List.of;
61 import static com.sun.tools.javac.tree.JCTree.Tag.*;
62
63 public class LocalProxyVariablesTests {
64 ReusableJavaCompiler tool;
65 Context context;
66 Options options;
67 MyLocalProxyVarsGen myLocalProxyVarsGen;
68
69 LocalProxyVariablesTests() {
70 context = new Context();
71 JavacFileManager.preRegister(context);
72 MyLocalProxyVarsGen.preRegister(context);
73 options = Options.instance(context);
74 options.put("--enable-preview", "--enable-preview");
75 options.put("--source", Integer.toString(Runtime.version().feature()));
76 tool = new ReusableJavaCompiler(context);
77 myLocalProxyVarsGen = (MyLocalProxyVarsGen)MyLocalProxyVarsGen.instance(context);
78 }
79
80 public static void main(String... args) throws Throwable {
81 new LocalProxyVariablesTests().tests();
82 }
83
84 void tests() throws Throwable {
85 doTest(
86 """
87 value class Test {
88 static String s0;
89 String s;
90 String ss;
91 Test(boolean b) {
92 s0 = null;
93 s = s0; // no local proxy variable for `s0` as it is static
94 ss = s; // but there should be a local proxy for `s`
95 super();
96 }
97 }
98 """, Set.of("local$s"));
99 doTest(
100 """
101 value class Test {
102 int i;
103 int j;
104 Test() {// javac should generate a proxy local var for `i`
105 i = 1;
106 j = i; // as here `i` is being read during the early construction phase, use the local var instead
107 super();
108 }
109 }
110 """, Set.of("local$i"));
111 doTest(
112 """
113 value class Test {
114 Integer x;
115 Integer y;
116 Test(boolean a, boolean b) {
117 if (a) {
118 x = 1;
119 if (b) {
120 y = 1;
121 } else {
122 y = 2;
123 }
124 } else {
125 x = y = 3;
126 }
127 super();
128 }
129 }
130 """, Set.of()); // no proxies in this case
131 doTest(
132 """
133 value class Test {
134 Integer x;
135 Integer y;
136 Test(boolean a) {
137 x = 1;
138 if (a) {
139 y = x;
140 } else {
141 y = 2;
142 }
143 super();
144 }
145 }
146 """, Set.of("local$x"));
147 doTest(
148 """
149 class Outer {
150 abstract value class Super {
151 Super(Object o) { }
152 }
153 }
154 value class MustPatchNullSuperArg extends Outer.Super {
155 int i;
156 int j;
157 MustPatchNullSuperArg(Outer outer) {
158 i = 1;
159 j = i; // forces local proxy for i
160 outer.super(null);
161 }
162 }
163 """, "MustPatchNullSuperArg", Set.of("local$i", "local$0", "local$1"));
164 doTest(
165 """
166 class Test {
167 static int seed() { return 42; }
168 static value class V {
169 int x = seed();
170 int y = x;
171 V() { super(); }
172 }
173 public static void main(String[] args) {
174 new V();
175 }
176 }
177 """, Set.of("local$x"));
178 }
179
180 void doTest(String src, Set<String> expectedLocalProxyNames) throws Throwable {
181 doTest(src, "", expectedLocalProxyNames);
182 }
183
184 void doTest(String src, String className, Set<String> expectedLocalProxyNames) throws Throwable {
185 JavaSource source = new JavaSource(src);
186 tool.clear();
187 List<JavaFileObject> inputs = of(source);
188 myLocalProxyVarsGen.expectedLocalProxyNames(expectedLocalProxyNames, className);
189 try {
190 tool.compile(inputs);
191 } catch (Throwable ex) {
192 throw new AssertionError(ex);
193 }
194 if (tool.errorCount() > 0) {
195 throw new AssertionError("unexpected compilation error");
196 }
197 }
198
199 class JavaSource extends SimpleJavaFileObject {
200 String src;
201
202 JavaSource(String src) {
203 super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
204 this.src = src;
205 }
206
207 @Override
208 public CharSequence getCharContent(boolean ignoreEncodingErrors) {
209 return src;
210 }
211 }
212
213 static class MyLocalProxyVarsGen extends LocalProxyVarsGen {
214 static void preRegister(Context context) {
215 context.put(localProxyVarsGenKey, (Factory<LocalProxyVarsGen>) c -> new MyLocalProxyVarsGen(c));
216 }
217
218 void expectedLocalProxyNames(Set<String> expectedProxyNames, String className) {
219 // so that we can remove elements from our copy
220 this.expectedProxyNames = new HashSet<>(expectedProxyNames);
221 this.className = className;
222 }
223
224 String className;
225 Set<String> expectedProxyNames;
226
227 MyLocalProxyVarsGen(Context context) {
228 super(context);
229 }
230
231 @Override
232 public void patchConstructor(JCMethodDecl mdef, TreeMaker make) {
233 super.patchConstructor(mdef, make);
234 // if className is "" then we process any class name, usually there will be only one
235 // if not we check only the class with name equals to className
236 if (className.equals("") || className.equals(mdef.sym.owner.name.toString())) {
237 analyzeTransformedMethod(mdef);
238 }
239 }
240
241 /* we need to analyze the tree obtained from patchConstructor asap, we can't wait
242 * until the compilation ends as other phases continue transforming the AST
243 */
244 void analyzeTransformedMethod(JCMethodDecl transformed) {
245 if (transformed instanceof JCMethodDecl methodDecl && methodDecl.name.toString().equals("<init>")) {
246 ListBuffer<JCStatement> statsToAnalyze = new ListBuffer<>();
247 for (JCStatement stat : methodDecl.body.stats) {
248 /* if the super constructor invocation has arguments, then it could be that some proxy variables
249 * had been created to store the value of those arguments and all this synthetic code is generated
250 * inside a block, so we need to see inside blocks
251 */
252 if (stat instanceof JCBlock block) {
253 statsToAnalyze.addAll(block.stats);
254 } else {
255 statsToAnalyze.add(stat);
256 }
257 }
258 for (JCStatement stat : statsToAnalyze) {
259 if (stat instanceof JCVariableDecl variableDecl && variableDecl.sym.isSynthetic()) {
260 Assert.check(expectedProxyNames.contains(variableDecl.name.toString()), variableDecl.name.toString() + " was not expected");
261 expectedProxyNames.remove(variableDecl.name.toString());
262 }
263 }
264 }
265 Assert.check(expectedProxyNames.isEmpty(), "not all expected proxy names were found at " + transformed);
266 }
267 }
268
269 static class ReusableJavaCompiler extends JavaCompiler {
270 ReusableJavaCompiler(Context context) {
271 super(context);
272 }
273
274 @Override
275 protected void checkReusable() {
276 // do nothing
277 }
278
279 @Override
280 public void close() {
281 // do nothing
282 }
283
284 void clear() {
285 chk.newRound();
286 enter.newRound();
287 newRound();
288 Modules.instance(context).newRound();
289 types.newRound();
290 annotate.newRound();
291 }
292 }
293 }