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 }