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 }