1 /*
  2  * Copyright (c) 2024, 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.  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.HashMap;
 29 import java.util.HashSet;
 30 import java.util.LinkedHashMap;
 31 import java.util.LinkedHashSet;
 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.VarSymbol;
 37 import com.sun.tools.javac.code.Symtab;
 38 import com.sun.tools.javac.code.Type;
 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.HASINIT;
 54 import static com.sun.tools.javac.code.Flags.STRICT;
 55 import static com.sun.tools.javac.code.Flags.SYNTHETIC;
 56 import static com.sun.tools.javac.code.TypeTag.BOT;
 57 
 58 import com.sun.tools.javac.jvm.Target;
 59 import com.sun.tools.javac.tree.JCTree;
 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 {
 78 
 79     protected static final Context.Key<LocalProxyVarsGen> localProxyVarsGenKey = new Context.Key<>();
 80 
 81     public static LocalProxyVarsGen instance(Context context) {
 82         LocalProxyVarsGen instance = context.get(localProxyVarsGenKey);
 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 Map<Symbol, Set<Symbol>> fieldsReadInPrologue = new HashMap<>();
 94 
 95     private final boolean noLocalProxyVars;
 96 
 97     @SuppressWarnings("this-escape")
 98     protected LocalProxyVarsGen(Context context) {
 99         context.put(localProxyVarsGenKey, this);
100         make = TreeMaker.instance(context);
101         types = Types.instance(context);
102         names = Names.instance(context);
103         syms = Symtab.instance(context);
104         target = Target.instance(context);
105         Options options = Options.instance(context);
106         noLocalProxyVars = options.isSet("noLocalProxyVars");
107     }
108 
109     public void addFieldReadInPrologue(Symbol owner, Symbol sym) {
110         Assert.checkNonNull(sym, "parameter 'sym' is null");
111         Set<Symbol> fieldSet = fieldsReadInPrologue.getOrDefault(owner, new LinkedHashSet<>());
112         fieldSet.add(sym);
113         fieldsReadInPrologue.put(owner, fieldSet);
114     }
115 
116     public void patchConstructor(JCMethodDecl tree, TreeMaker make) {
117         /* if some fields have initializers those probably were added using the enclosing class as their map key
118          * we need to recover those now and add them to this constructor
119          */
120         Set<Symbol> earlyReads = null;
121         if (fieldsReadInPrologue.get(tree.sym.owner) != null) {
122             earlyReads = fieldsReadInPrologue.get(tree.sym.owner);
123         }
124         if (fieldsReadInPrologue.get(tree.sym) != null) {
125             Set<Symbol> constructorEarlyReads = fieldsReadInPrologue.remove(tree.sym);
126             if (constructorEarlyReads != null) {
127                 if (earlyReads == null) {
128                     earlyReads = constructorEarlyReads;
129                 } else {
130                     earlyReads.addAll(constructorEarlyReads);
131                 }
132             }
133         }
134         if (earlyReads != null && !noLocalProxyVars) {
135             addLocalProxiesFor(tree, earlyReads, make);
136         }
137     }
138 
139     public void allFieldNormalized(Symbol.ClassSymbol csym) {
140         fieldsReadInPrologue.remove(csym);
141     }
142 
143     void addLocalProxiesFor(JCMethodDecl constructor, Set<Symbol> fields, TreeMaker make) {
144         ListBuffer<JCStatement> localDeclarations = new ListBuffer<>();
145         Map<Symbol, Symbol> fieldToLocalMap = new LinkedHashMap<>();
146 
147         for (Symbol field : fields) {
148             long flags = SYNTHETIC;
149             VarSymbol proxy = new VarSymbol(flags, newLocalName(field.name.toString()), field.erasure(types), constructor.sym);
150             fieldToLocalMap.put(field, proxy);
151             JCVariableDecl localDecl;
152             JCExpression initializer = null;
153             if ((field.flags() & (HASINIT | FINAL | STRICT)) == 0) {
154                 initializer = field.type.isPrimitive() ?
155                                     make.at(constructor.pos).Literal(0) :
156                                     make.at(constructor.pos).Literal(BOT, null).setType(syms.botType);
157             }
158             localDecl = make.at(constructor.pos).VarDef(proxy, initializer);
159             localDeclarations = localDeclarations.append(localDecl);
160         }
161 
162         FieldRewriter fieldRewriter = new FieldRewriter(constructor, fieldToLocalMap);
163         ListBuffer<JCStatement> newBody = new ListBuffer<>();
164         for (JCStatement st : constructor.body.stats) {
165             newBody = newBody.append(fieldRewriter.translate(st));
166         }
167         localDeclarations.addAll(newBody);
168         ListBuffer<JCStatement> assigmentsBeforeSuper = new ListBuffer<>();
169         for (Symbol vsym : fieldToLocalMap.keySet()) {
170             Symbol local = fieldToLocalMap.get(vsym);
171             assigmentsBeforeSuper.append(make.at(constructor.pos()).Assignment(vsym, make.at(constructor.pos()).Ident(local)));
172         }
173         constructor.body.stats = localDeclarations.toList();
174         JCTree.JCMethodInvocation constructorCall = TreeInfo.findConstructorCall(constructor);
175         if (constructorCall.args.isEmpty()) {
176             // this is just a super invocation with no arguments we can set the fields just before the invocation
177             // and let Gen do the rest
178             TreeInfo.mapSuperCalls(constructor.body, supercall -> make.Block(0, assigmentsBeforeSuper.toList().append(supercall)));
179         } else {
180             // we need to generate fresh local variables to catch the values of the arguments, then
181             // assign the proxy locals to the fields and finally invoke the super with the fresh local variables
182             int argPosition = 0;
183             ListBuffer<JCStatement> superArgsProxies = new ListBuffer<>();
184             Symbol.MethodSymbol constructorCallSymbol = (Symbol.MethodSymbol) TreeInfo.symbolFor(constructorCall.meth);
185             List<Type> allDeclaredArgs = constructorCallSymbol.externalType(types).getParameterTypes();
186             for (JCExpression arg : constructorCall.args) {
187                 Type declaredType = allDeclaredArgs.head;
188                 long flags = SYNTHETIC | FINAL;
189                 VarSymbol proxyForArgSym = new VarSymbol(flags, newLocalName("" + argPosition), types.erasure(declaredType), constructor.sym);
190                 JCVariableDecl proxyForArgDecl = make.at(constructor.pos).VarDef(proxyForArgSym, arg);
191                 superArgsProxies = superArgsProxies.append(proxyForArgDecl);
192                 argPosition++;
193                 allDeclaredArgs = allDeclaredArgs.tail;
194             }
195             List<JCStatement> superArgsProxiesList = superArgsProxies.toList();
196             ListBuffer<JCExpression> newArgs = new ListBuffer<>();
197             for (JCStatement argProxy : superArgsProxies) {
198                 newArgs.add(make.at(argProxy.pos).Ident((JCVariableDecl) argProxy));
199             }
200             constructorCall.args = newArgs.toList();
201             TreeInfo.mapSuperCalls(constructor.body,
202                     supercall -> make.Block(0, superArgsProxiesList.appendList(assigmentsBeforeSuper.toList()).append(supercall)));
203         }
204     }
205 
206     private Name newLocalName(String name) {
207         return names.fromString("local" + target.syntheticNameChar() + name);
208     }
209 
210     class FieldRewriter extends TreeTranslator {
211         JCMethodDecl md;
212         Map<Symbol, Symbol> fieldToLocalMap;
213         boolean ctorPrologue = true;
214 
215         public FieldRewriter(JCMethodDecl md, Map<Symbol, Symbol> fieldToLocalMap) {
216             this.md = md;
217             this.fieldToLocalMap = fieldToLocalMap;
218         }
219 
220         @Override
221         public void visitIdent(JCTree.JCIdent tree) {
222             if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) {
223                 result = make.at(md).Ident(fieldToLocalMap.get(tree.sym));
224             } else {
225                 result = tree;
226             }
227         }
228 
229         @Override
230         public void visitSelect(JCTree.JCFieldAccess tree) {
231             super.visitSelect(tree);
232             if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) {
233                 result = make.at(md).Ident(fieldToLocalMap.get(tree.sym));
234             } else {
235                 result = tree;
236             }
237         }
238 
239         @Override
240         public void visitApply(JCTree.JCMethodInvocation tree) {
241             super.visitApply(tree);
242             if (TreeInfo.isConstructorCall(tree)) {
243                 ctorPrologue = false;
244             }
245         }
246     }
247 }