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 }