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