1 /*
  2  * Copyright (c) 2024, 2025, 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.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package com.sun.tools.javac.comp;
 27 
 28 import java.util.ArrayList;
 29 import java.util.HashMap;
 30 import java.util.HashSet;
 31 import java.util.LinkedHashMap;
 32 import java.util.Map;
 33 import java.util.Set;
 34 
 35 import com.sun.tools.javac.code.Symbol;
 36 import com.sun.tools.javac.code.Symbol.ClassSymbol;
 37 import com.sun.tools.javac.code.Symbol.VarSymbol;
 38 import com.sun.tools.javac.code.Symtab;
 39 import com.sun.tools.javac.code.Types;
 40 import com.sun.tools.javac.tree.JCTree.JCAssign;
 41 import com.sun.tools.javac.tree.JCTree.JCExpression;
 42 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
 43 import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
 44 import com.sun.tools.javac.tree.TreeMaker;
 45 import com.sun.tools.javac.tree.TreeTranslator;
 46 import com.sun.tools.javac.util.Assert;
 47 import com.sun.tools.javac.util.Context;
 48 import com.sun.tools.javac.util.ListBuffer;
 49 import com.sun.tools.javac.util.Name;
 50 import com.sun.tools.javac.util.Names;
 51 
 52 import static com.sun.tools.javac.code.Flags.FINAL;
 53 import static com.sun.tools.javac.code.Flags.SYNTHETIC;
 54 import static com.sun.tools.javac.code.TypeTag.BOT;
 55 import static com.sun.tools.javac.tree.JCTree.Tag.VARDEF;
 56 
 57 import com.sun.tools.javac.jvm.Target;
 58 import com.sun.tools.javac.tree.JCTree;
 59 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
 60 import com.sun.tools.javac.tree.JCTree.JCStatement;
 61 import com.sun.tools.javac.tree.TreeInfo;
 62 import com.sun.tools.javac.util.List;
 63 import com.sun.tools.javac.util.Options;
 64 
 65 /** This phase adds local variable proxies for fields that are read during the
 66  *  early construction phase (prologue)
 67  *
 68  *  Assignments to the affected instance fields will be rewritten as assignments to a
 69  *  local proxy variable. Fields will be assigned to with its corresponding local variable
 70  *  proxy just before the super invocation and after its arguments, if any, have been evaluated.
 71  *
 72  *  <p><b>This is NOT part of any supported API.
 73  *  If you write code that depends on this, you do so at your own risk.
 74  *  This code and its internal interfaces are subject to change or
 75  *  deletion without notice.</b>
 76  */
 77 public class LocalProxyVarsGen extends TreeTranslator {
 78 
 79     protected static final Context.Key<LocalProxyVarsGen> valueInitializersKey = new Context.Key<>();
 80 
 81     public static LocalProxyVarsGen instance(Context context) {
 82         LocalProxyVarsGen instance = context.get(valueInitializersKey);
 83         if (instance == null)
 84             instance = new LocalProxyVarsGen(context);
 85         return instance;
 86     }
 87 
 88     private final Types types;
 89     private final Names names;
 90     private final Symtab syms;
 91     private final Target target;
 92     private TreeMaker make;
 93     private final UnsetFieldsInfo unsetFieldsInfo;
 94     private ClassSymbol currentClass = null;
 95     private java.util.List<JCVariableDecl> instanceFields;
 96     private Map<JCMethodDecl, Set<Symbol>> fieldsReadInPrologue = new HashMap<>();
 97 
 98     private final boolean noLocalProxyVars;
 99 
100     @SuppressWarnings("this-escape")
101     protected LocalProxyVarsGen(Context context) {
102         context.put(valueInitializersKey, this);
103         make = TreeMaker.instance(context);
104         types = Types.instance(context);
105         names = Names.instance(context);
106         syms = Symtab.instance(context);
107         target = Target.instance(context);
108         unsetFieldsInfo = UnsetFieldsInfo.instance(context);
109         Options options = Options.instance(context);
110         noLocalProxyVars = options.isSet("noLocalProxyVars");
111     }
112 
113     public void addFieldReadInPrologue(JCMethodDecl constructor, Symbol sym) {
114         Assert.checkNonNull(sym, "parameter 'sym' is null");
115         Set<Symbol> fieldSet = fieldsReadInPrologue.getOrDefault(constructor, new HashSet<>());
116         fieldSet.add(sym);
117         fieldsReadInPrologue.put(constructor, fieldSet);
118     }
119 
120     public JCTree translateTopLevelClass(JCTree cdef, TreeMaker make) {
121         if (!noLocalProxyVars) {
122             try {
123                 this.make = make;
124                 return translate(cdef);
125             } finally {
126                 // note that recursive invocations of this method fail hard
127                 this.make = null;
128             }
129         } else {
130             return cdef;
131         }
132     }
133 
134     @Override
135     public void visitClassDef(JCClassDecl tree) {
136         ClassSymbol prevCurrentClass = currentClass;
137         java.util.List<JCVariableDecl> prevInstanceFields = instanceFields;
138         try {
139             currentClass = tree.sym;
140             instanceFields = tree.defs.stream()
141                     .filter(t -> t.hasTag(VARDEF))
142                     .map(t -> (JCVariableDecl)t)
143                     .filter(vd -> !vd.sym.isStatic())
144                     .collect(List.collector());
145             super.visitClassDef(tree);
146         } finally {
147             currentClass = prevCurrentClass;
148             instanceFields = prevInstanceFields;
149         }
150     }
151 
152     public void visitMethodDef(JCMethodDecl tree) {
153         if (fieldsReadInPrologue.get(tree) != null) {
154             Set<Symbol> fieldSet = fieldsReadInPrologue.get(tree);
155             java.util.List<JCVariableDecl> fieldsRead = new ArrayList<>();
156             for (JCVariableDecl field : instanceFields) {
157                 if (fieldSet.contains(field.sym)) {
158                     fieldsRead.add(field);
159                 }
160             }
161             addLocalProxiesFor(tree, fieldsRead);
162             fieldsReadInPrologue.remove(tree);
163         }
164         super.visitMethodDef(tree);
165     }
166 
167     void addLocalProxiesFor(JCMethodDecl constructor, java.util.List<JCVariableDecl> fields) {
168         ListBuffer<JCStatement> localDeclarations = new ListBuffer<>();
169         Map<Symbol, Symbol> fieldToLocalMap = new LinkedHashMap<>();
170 
171         for (JCVariableDecl fieldDecl : fields) {
172             long flags = SYNTHETIC;
173             VarSymbol proxy = new VarSymbol(flags, newLocalName(fieldDecl.name.toString()), fieldDecl.sym.erasure(types), constructor.sym);
174             fieldToLocalMap.put(fieldDecl.sym, proxy);
175             JCVariableDecl localDecl;
176             JCExpression initializer = fieldDecl.init;
177             if (initializer == null && !fieldDecl.sym.isFinal() && !fieldDecl.sym.isStrict()) {
178                 initializer = fieldDecl.vartype.type.isPrimitive() ?
179                                     make.at(constructor.pos).Literal(0) :
180                                     make.at(constructor.pos).Literal(BOT, null).setType(syms.botType);
181             }
182             localDecl = make.at(constructor.pos).VarDef(proxy, initializer);
183             localDecl.vartype = fieldDecl.vartype;
184             localDeclarations = localDeclarations.append(localDecl);
185         }
186 
187         FieldRewriter fieldRewriter = new FieldRewriter(constructor, fieldToLocalMap);
188         ListBuffer<JCStatement> newBody = new ListBuffer<>();
189         for (JCStatement st : constructor.body.stats) {
190             newBody = newBody.append(fieldRewriter.translate(st));
191         }
192         localDeclarations.addAll(newBody);
193         ListBuffer<JCStatement> assigmentsBeforeSuper = new ListBuffer<>();
194         for (Symbol vsym : fieldToLocalMap.keySet()) {
195             Symbol local = fieldToLocalMap.get(vsym);
196             assigmentsBeforeSuper.append(make.at(constructor.pos()).Assignment(vsym, make.at(constructor.pos()).Ident(local)));
197         }
198         constructor.body.stats = localDeclarations.toList();
199         JCTree.JCMethodInvocation constructorCall = TreeInfo.findConstructorCall(constructor);
200         if (constructorCall.args.isEmpty()) {
201             // this is just a super invocation with no arguments we can set the fields just before the invocation
202             // and let Gen do the rest
203             TreeInfo.mapSuperCalls(constructor.body, supercall -> make.Block(0, assigmentsBeforeSuper.toList().append(supercall)));
204         } else {
205             // we need to generate fresh local variables to catch the values of the arguments, then
206             // assign the proxy locals to the fields and finally invoke the super with the fresh local variables
207             int argPosition = 0;
208             ListBuffer<JCStatement> superArgsProxies = new ListBuffer<>();
209             for (JCExpression arg : constructorCall.args) {
210                 long flags = SYNTHETIC | FINAL;
211                 VarSymbol proxyForArgSym = new VarSymbol(flags, newLocalName("" + argPosition), types.erasure(arg.type), constructor.sym);
212                 JCVariableDecl proxyForArgDecl = make.at(constructor.pos).VarDef(proxyForArgSym, arg);
213                 superArgsProxies = superArgsProxies.append(proxyForArgDecl);
214                 argPosition++;
215             }
216             List<JCStatement> superArgsProxiesList = superArgsProxies.toList();
217             ListBuffer<JCExpression> newArgs = new ListBuffer<>();
218             for (JCStatement argProxy : superArgsProxies) {
219                 newArgs.add(make.at(argProxy.pos).Ident((JCVariableDecl) argProxy));
220             }
221             constructorCall.args = newArgs.toList();
222             TreeInfo.mapSuperCalls(constructor.body,
223                     supercall -> make.Block(0, superArgsProxiesList.appendList(assigmentsBeforeSuper.toList()).append(supercall)));
224         }
225     }
226 
227     private Name newLocalName(String name) {
228         return names.fromString("local" + target.syntheticNameChar() + name);
229     }
230 
231     class FieldRewriter extends TreeTranslator {
232         JCMethodDecl md;
233         Map<Symbol, Symbol> fieldToLocalMap;
234         boolean ctorPrologue = true;
235 
236         public FieldRewriter(JCMethodDecl md, Map<Symbol, Symbol> fieldToLocalMap) {
237             this.md = md;
238             this.fieldToLocalMap = fieldToLocalMap;
239         }
240 
241         @Override
242         public void visitIdent(JCTree.JCIdent tree) {
243             if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) {
244                 result = make.at(md).Ident(fieldToLocalMap.get(tree.sym));
245             } else {
246                 result = tree;
247             }
248         }
249 
250         @Override
251         public void visitSelect(JCTree.JCFieldAccess tree) {
252             super.visitSelect(tree);
253             if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) {
254                 result = make.at(md).Ident(fieldToLocalMap.get(tree.sym));
255             } else {
256                 result = tree;
257             }
258         }
259 
260         @Override
261         public void visitAssign(JCAssign tree) {
262             JCExpression previousLHS = tree.lhs;
263             super.visitAssign(tree);
264             if (ctorPrologue && previousLHS != tree.lhs) {
265                 unsetFieldsInfo.removeUnsetFieldInfo(currentClass, tree);
266             }
267         }
268 
269         @Override
270         public void visitApply(JCTree.JCMethodInvocation tree) {
271             super.visitApply(tree);
272             if (TreeInfo.isConstructorCall(tree)) {
273                 ctorPrologue = false;
274             }
275         }
276     }
277 }