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.Options;
58
59 import static com.sun.tools.javac.util.List.of;
60 import static com.sun.tools.javac.tree.JCTree.Tag.*;
61
62 public class LocalProxyVariablesTests {
63 ReusableJavaCompiler tool;
64 Context context;
65 Options options;
66 MyLocalProxyVarsGen myLocalProxyVarsGen;
67
68 LocalProxyVariablesTests() {
69 context = new Context();
70 JavacFileManager.preRegister(context);
71 MyLocalProxyVarsGen.preRegister(context);
72 options = Options.instance(context);
73 options.put("--enable-preview", "--enable-preview");
74 options.put("--source", Integer.toString(Runtime.version().feature()));
75 tool = new ReusableJavaCompiler(context);
76 myLocalProxyVarsGen = (MyLocalProxyVarsGen)MyLocalProxyVarsGen.instance(context);
77 }
78
79 public static void main(String... args) throws Throwable {
80 new LocalProxyVariablesTests().tests();
81 }
82
83 void tests() throws Throwable {
84 doTest(
85 """
86 value class Test {
87 static String s0;
88 String s;
89 String ss;
90 Test(boolean b) {
91 s0 = null;
92 s = s0; // no local proxy variable for `s0` as it is static
93 ss = s; // but there should be a local proxy for `s`
94 super();
95 }
96 }
97 """, Set.of("local$s"));
98 doTest(
99 """
100 value class Test {
101 int i;
102 int j;
103 Test() {// javac should generate a proxy local var for `i`
104 i = 1;
105 j = i; // as here `i` is being read during the early construction phase, use the local var instead
106 super();
107 }
108 }
109 """, Set.of("local$i"));
110 doTest(
111 """
112 value class Test {
113 Integer x;
114 Integer y;
115 Test(boolean a, boolean b) {
116 if (a) {
117 x = 1;
118 if (b) {
119 y = 1;
120 } else {
121 y = 2;
122 }
123 } else {
124 x = y = 3;
125 }
126 super();
127 }
128 }
129 """, Set.of()); // no proxies in this case
130 doTest(
131 """
132 value class Test {
133 Integer x;
134 Integer y;
135 Test(boolean a) {
136 x = 1;
137 if (a) {
138 y = x;
139 } else {
140 y = 2;
141 }
142 super();
143 }
144 }
145 """, Set.of("local$x"));
146 }
147
148 void doTest(String src, Set<String> expectedLocalProxyNames) throws Throwable {
149 JavaSource source = new JavaSource(src);
150 tool.clear();
151 List<JavaFileObject> inputs = of(source);
152 myLocalProxyVarsGen.expectedLocalProxyNames(expectedLocalProxyNames);
153 try {
154 tool.compile(inputs);
155 } catch (Throwable ex) {
156 throw new AssertionError(ex);
157 }
158 if (tool.errorCount() > 0) {
159 throw new AssertionError("unexpected compilation error");
160 }
161 }
162
163 class JavaSource extends SimpleJavaFileObject {
164 String src;
165
166 JavaSource(String src) {
167 super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
168 this.src = src;
169 }
170
171 @Override
172 public CharSequence getCharContent(boolean ignoreEncodingErrors) {
173 return src;
174 }
175 }
176
177 static class MyLocalProxyVarsGen extends LocalProxyVarsGen {
178 static void preRegister(Context context) {
179 context.put(localProxyVarsGenKey, (Factory<LocalProxyVarsGen>) c -> new MyLocalProxyVarsGen(c));
180 }
181
182 void expectedLocalProxyNames(Set<String> expectedProxyNames) {
183 // so that we can remove elements from our copy
184 this.expectedProxyNames = new HashSet<>(expectedProxyNames);
185 }
186
187 Set<String> expectedProxyNames;
188
189 MyLocalProxyVarsGen(Context context) {
190 super(context);
191 }
192
193 @Override
194 public JCTree translateTopLevelClass(JCTree cdef, TreeMaker make) {
195 JCClassDecl transformed = (JCClassDecl)super.translateTopLevelClass(cdef, make);
196 analyzeTransformedClass(transformed);
197 return transformed;
198 }
199
200 /* we need to analyze the tree obtained from invoking `translateTopLevelClass` asap, we can't wait
201 * until the compilation ends as other phases continue transforming the AST
202 */
203 void analyzeTransformedClass(JCClassDecl transformed) {
204 for (JCTree def : transformed.defs) {
205 if (def instanceof JCMethodDecl methodDecl && methodDecl.name.toString().equals("<init>")) {
206 for (JCStatement stat : methodDecl.body.stats) {
207 if (stat instanceof JCVariableDecl variableDecl && variableDecl.sym.isSynthetic()) {
208 Assert.check(expectedProxyNames.contains(variableDecl.name.toString()));
209 expectedProxyNames.remove(variableDecl.name.toString());
210 }
211 }
212 }
213 }
214 Assert.check(expectedProxyNames.isEmpty());
215 }
216 }
217
218 static class ReusableJavaCompiler extends JavaCompiler {
219 ReusableJavaCompiler(Context context) {
220 super(context);
221 }
222
223 @Override
224 protected void checkReusable() {
225 // do nothing
226 }
227
228 @Override
229 public void close() {
230 // do nothing
231 }
232
233 void clear() {
234 chk.newRound();
235 enter.newRound();
236 newRound();
237 Modules.instance(context).newRound();
238 types.newRound();
239 annotate.newRound();
240 }
241 }
242 }