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 }