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 }