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     public Map<JCMethodDecl, Set<Symbol>> initializersAlreadyInConst = new HashMap<>();
 98 
 99     private final boolean noLocalProxyVars;
100 
101     @SuppressWarnings("this-escape")
102     protected LocalProxyVarsGen(Context context) {
103         context.put(valueInitializersKey, this);
104         make = TreeMaker.instance(context);
105         types = Types.instance(context);
106         names = Names.instance(context);
107         syms = Symtab.instance(context);
108         target = Target.instance(context);
109         unsetFieldsInfo = UnsetFieldsInfo.instance(context);
110         Options options = Options.instance(context);
111         noLocalProxyVars = options.isSet("noLocalProxyVars");
112     }
113 
114     public void addFieldReadInPrologue(JCMethodDecl constructor, Symbol sym) {
115         Assert.checkNonNull(sym, "parameter 'sym' is null");
116         Set<Symbol> fieldSet = fieldsReadInPrologue.getOrDefault(constructor, new HashSet<>());
117         fieldSet.add(sym);
118         fieldsReadInPrologue.put(constructor, fieldSet);
119     }
120 
121     private void addIncludedInitializer(JCMethodDecl constructor, Symbol varSymbol) {
122         Set<Symbol> fieldSet = initializersAlreadyInConst.getOrDefault(constructor, new HashSet<>());
123         fieldSet.add(varSymbol);
124         initializersAlreadyInConst.put(constructor, fieldSet);
125     }
126 
127     public JCTree translateTopLevelClass(JCTree cdef, TreeMaker make) {
128         if (!noLocalProxyVars) {
129             try {
130                 this.make = make;
131                 return translate(cdef);
132             } finally {
133                 // note that recursive invocations of this method fail hard
134                 this.make = null;
135             }
136         } else {
137             return cdef;
138         }
139     }
140 
141     @Override
142     public void visitClassDef(JCClassDecl tree) {
143         ClassSymbol prevCurrentClass = currentClass;
144         java.util.List<JCVariableDecl> prevInstanceFields = instanceFields;
145         try {
146             currentClass = tree.sym;
147             instanceFields = tree.defs.stream()
148                     .filter(t -> t.hasTag(VARDEF))
149                     .map(t -> (JCVariableDecl)t)
150                     .filter(vd -> !vd.sym.isStatic())
151                     .collect(List.collector());
152             super.visitClassDef(tree);
153         } finally {
154             currentClass = prevCurrentClass;
155             instanceFields = prevInstanceFields;
156         }
157     }
158 
159     public void visitMethodDef(JCMethodDecl tree) {
160         if (fieldsReadInPrologue.get(tree) != null) {
161             Set<Symbol> fieldSet = fieldsReadInPrologue.get(tree);
162             java.util.List<JCVariableDecl> fieldsRead = new ArrayList<>();
163             for (JCVariableDecl field : instanceFields) {
164                 if (fieldSet.contains(field.sym)) {
165                     fieldsRead.add(field);
166                 }
167             }
168             addLocalProxiesFor(tree, fieldsRead);
169             fieldsReadInPrologue.remove(tree);
170         }
171         super.visitMethodDef(tree);
172     }
173 
174     void addLocalProxiesFor(JCMethodDecl constructor, java.util.List<JCVariableDecl> fields) {
175         ListBuffer<JCStatement> localDeclarations = new ListBuffer<>();
176         Map<Symbol, Symbol> fieldToLocalMap = new LinkedHashMap<>();
177 
178         for (JCVariableDecl fieldDecl : fields) {
179             long flags = SYNTHETIC;
180             VarSymbol proxy = new VarSymbol(flags, newLocalName(fieldDecl.name.toString()), fieldDecl.sym.erasure(types), constructor.sym);
181             fieldToLocalMap.put(fieldDecl.sym, proxy);
182             JCVariableDecl localDecl;
183             JCExpression initializer = fieldDecl.init;
184             if (initializer == null && !fieldDecl.sym.isFinal() && !fieldDecl.sym.isStrict()) {
185                 initializer = fieldDecl.vartype.type.isPrimitive() ?
186                                     make.at(constructor.pos).Literal(0) :
187                                     make.at(constructor.pos).Literal(BOT, null).setType(syms.botType);
188             } else {
189                 addIncludedInitializer(constructor, fieldDecl.sym);
190             }
191             localDecl = make.at(constructor.pos).VarDef(proxy, initializer);
192             localDecl.vartype = fieldDecl.vartype;
193             localDeclarations = localDeclarations.append(localDecl);
194         }
195 
196         FieldRewriter fieldRewriter = new FieldRewriter(constructor, fieldToLocalMap);
197         ListBuffer<JCStatement> newBody = new ListBuffer<>();
198         for (JCStatement st : constructor.body.stats) {
199             newBody = newBody.append(fieldRewriter.translate(st));
200         }
201         localDeclarations.addAll(newBody);
202         ListBuffer<JCStatement> assigmentsBeforeSuper = new ListBuffer<>();
203         for (Symbol vsym : fieldToLocalMap.keySet()) {
204             Symbol local = fieldToLocalMap.get(vsym);
205             assigmentsBeforeSuper.append(make.at(constructor.pos()).Assignment(vsym, make.at(constructor.pos()).Ident(local)));
206         }
207         constructor.body.stats = localDeclarations.toList();
208         JCTree.JCMethodInvocation constructorCall = TreeInfo.findConstructorCall(constructor);
209         if (constructorCall.args.isEmpty()) {
210             // this is just a super invocation with no arguments we can set the fields just before the invocation
211             // and let Gen do the rest
212             TreeInfo.mapSuperCalls(constructor.body, supercall -> make.Block(0, assigmentsBeforeSuper.toList().append(supercall)));
213         } else {
214             // we need to generate fresh local variables to catch the values of the arguments, then
215             // assign the proxy locals to the fields and finally invoke the super with the fresh local variables
216             int argPosition = 0;
217             ListBuffer<JCStatement> superArgsProxies = new ListBuffer<>();
218             for (JCExpression arg : constructorCall.args) {
219                 long flags = SYNTHETIC | FINAL;
220                 VarSymbol proxyForArgSym = new VarSymbol(flags, newLocalName("" + argPosition), types.erasure(arg.type), constructor.sym);
221                 JCVariableDecl proxyForArgDecl = make.at(constructor.pos).VarDef(proxyForArgSym, arg);
222                 superArgsProxies = superArgsProxies.append(proxyForArgDecl);
223                 argPosition++;
224             }
225             List<JCStatement> superArgsProxiesList = superArgsProxies.toList();
226             ListBuffer<JCExpression> newArgs = new ListBuffer<>();
227             for (JCStatement argProxy : superArgsProxies) {
228                 newArgs.add(make.at(argProxy.pos).Ident((JCVariableDecl) argProxy));
229             }
230             constructorCall.args = newArgs.toList();
231             TreeInfo.mapSuperCalls(constructor.body,
232                     supercall -> make.Block(0, superArgsProxiesList.appendList(assigmentsBeforeSuper.toList()).append(supercall)));
233         }
234     }
235 
236     private Name newLocalName(String name) {
237         return names.fromString("local" + target.syntheticNameChar() + name);
238     }
239 
240     class FieldRewriter extends TreeTranslator {
241         JCMethodDecl md;
242         Map<Symbol, Symbol> fieldToLocalMap;
243         boolean ctorPrologue = true;
244 
245         public FieldRewriter(JCMethodDecl md, Map<Symbol, Symbol> fieldToLocalMap) {
246             this.md = md;
247             this.fieldToLocalMap = fieldToLocalMap;
248         }
249 
250         @Override
251         public void visitIdent(JCTree.JCIdent tree) {
252             if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) {
253                 result = make.at(md).Ident(fieldToLocalMap.get(tree.sym));
254             } else {
255                 result = tree;
256             }
257         }
258 
259         @Override
260         public void visitSelect(JCTree.JCFieldAccess tree) {
261             super.visitSelect(tree);
262             if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) {
263                 result = make.at(md).Ident(fieldToLocalMap.get(tree.sym));
264             } else {
265                 result = tree;
266             }
267         }
268 
269         @Override
270         public void visitAssign(JCAssign tree) {
271             JCExpression previousLHS = tree.lhs;
272             super.visitAssign(tree);
273             if (ctorPrologue && previousLHS != tree.lhs) {
274                 unsetFieldsInfo.removeUnsetFieldInfo(currentClass, tree);
275             }
276         }
277 
278         @Override
279         public void visitApply(JCTree.JCMethodInvocation tree) {
280             super.visitApply(tree);
281             if (TreeInfo.isConstructorCall(tree)) {
282                 ctorPrologue = false;
283             }
284         }
285     }
286 }