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.Types; 39 import com.sun.tools.javac.tree.JCTree.JCAssign; 40 import com.sun.tools.javac.tree.JCTree.JCExpression; 41 import com.sun.tools.javac.tree.JCTree.JCMethodDecl; 42 import com.sun.tools.javac.tree.JCTree.JCVariableDecl; 43 import com.sun.tools.javac.tree.TreeMaker; 44 import com.sun.tools.javac.tree.TreeTranslator; 45 import com.sun.tools.javac.util.Context; 46 import com.sun.tools.javac.util.ListBuffer; 47 import com.sun.tools.javac.util.Name; 48 import com.sun.tools.javac.util.Names; 49 50 import static com.sun.tools.javac.code.Flags.FINAL; 51 import static com.sun.tools.javac.code.Flags.SYNTHETIC; 52 import static com.sun.tools.javac.tree.JCTree.Tag.VARDEF; 53 54 import com.sun.tools.javac.jvm.Target; 55 import com.sun.tools.javac.tree.JCTree; 56 import com.sun.tools.javac.tree.JCTree.JCClassDecl; 57 import com.sun.tools.javac.tree.JCTree.JCStatement; 58 import com.sun.tools.javac.tree.TreeInfo; 59 import com.sun.tools.javac.util.List; 60 import com.sun.tools.javac.util.Options; 61 62 /** This phase adds local variable proxies for strict fields that are read during the 63 * early construction phase (prologue) 64 * 65 * Assignments to the affected instance fields will be rewritten as assignments to a 66 * local proxy variable. Fields will be assigned to with its corresponding local variable 67 * proxy just before the super invocation and after its arguments, if any, have been evaluated. 68 * 69 * <p><b>This is NOT part of any supported API. 70 * If you write code that depends on this, you do so at your own risk. 71 * This code and its internal interfaces are subject to change or 72 * deletion without notice.</b> 73 */ 74 public class LocalProxyVarsGen extends TreeTranslator { 75 76 protected static final Context.Key<LocalProxyVarsGen> valueInitializersKey = new Context.Key<>(); 77 78 public static LocalProxyVarsGen instance(Context context) { 79 LocalProxyVarsGen instance = context.get(valueInitializersKey); 80 if (instance == null) 81 instance = new LocalProxyVarsGen(context); 82 return instance; 83 } 84 85 private final Types types; 86 private final Names names; 87 private final Target target; 88 private TreeMaker make; 89 private final UnsetFieldsInfo unsetFieldsInfo; 90 private ClassSymbol currentClass = null; 91 private java.util.List<JCVariableDecl> strictInstanceFields; 92 private Map<JCMethodDecl, Set<Symbol>> strictFieldsReadInPrologue = new HashMap<>(); 93 94 private final boolean noLocalProxyVars; 95 96 @SuppressWarnings("this-escape") 97 protected LocalProxyVarsGen(Context context) { 98 context.put(valueInitializersKey, this); 99 make = TreeMaker.instance(context); 100 types = Types.instance(context); 101 names = Names.instance(context); 102 target = Target.instance(context); 103 unsetFieldsInfo = UnsetFieldsInfo.instance(context); 104 Options options = Options.instance(context); 105 noLocalProxyVars = options.isSet("noLocalProxyVars"); 106 } 107 108 public void addStrictFieldReadInPrologue(JCMethodDecl constructor, Symbol sym) { 109 Set<Symbol> fieldSet = strictFieldsReadInPrologue.getOrDefault(constructor, new HashSet<>()); 110 fieldSet.add(sym); 111 strictFieldsReadInPrologue.put(constructor, fieldSet); 112 } 113 114 public JCTree translateTopLevelClass(JCTree cdef, TreeMaker make) { 115 if (!noLocalProxyVars) { 116 try { 117 this.make = make; 118 return translate(cdef); 119 } finally { 120 // note that recursive invocations of this method fail hard 121 this.make = null; 122 } 123 } else { 124 return cdef; 125 } 126 } 127 128 @Override 129 public void visitClassDef(JCClassDecl tree) { 130 ClassSymbol prevCurrentClass = currentClass; 131 java.util.List<JCVariableDecl> prevStrictInstanceFields = strictInstanceFields; 132 try { 133 currentClass = tree.sym; 134 strictInstanceFields = tree.defs.stream() 135 .filter(t -> t.hasTag(VARDEF)) 136 .map(t -> (JCVariableDecl)t) 137 .filter(vd -> vd.sym.isStrict() && !vd.sym.isStatic()) 138 .collect(List.collector()); 139 super.visitClassDef(tree); 140 } finally { 141 currentClass = prevCurrentClass; 142 strictInstanceFields = prevStrictInstanceFields; 143 } 144 } 145 146 public void visitMethodDef(JCMethodDecl tree) { 147 if (strictFieldsReadInPrologue.get(tree) != null) { 148 Set<Symbol> fieldSet = strictFieldsReadInPrologue.get(tree); 149 java.util.List<JCVariableDecl> strictFieldsRead = new ArrayList<>(); 150 for (JCVariableDecl sfield : strictInstanceFields) { 151 if (fieldSet.contains(sfield.sym)) { 152 strictFieldsRead.add(sfield); 153 } 154 } 155 addLocalProxiesFor(tree, strictFieldsRead); 156 strictFieldsReadInPrologue.remove(tree); 157 } 158 super.visitMethodDef(tree); 159 } 160 161 void addLocalProxiesFor(JCMethodDecl constructor, java.util.List<JCVariableDecl> multiAssignedStrictFields) { 162 ListBuffer<JCStatement> localDeclarations = new ListBuffer<>(); 163 Map<Symbol, Symbol> fieldToLocalMap = new LinkedHashMap<>(); 164 165 for (JCVariableDecl fieldDecl : multiAssignedStrictFields) { 166 long flags = SYNTHETIC; 167 VarSymbol proxy = new VarSymbol(flags, newLocalName(fieldDecl.name.toString()), fieldDecl.sym.erasure(types), constructor.sym); 168 fieldToLocalMap.put(fieldDecl.sym, proxy); 169 JCVariableDecl localDecl = make.at(constructor.pos).VarDef(proxy, fieldDecl.init); 170 localDecl.vartype = fieldDecl.vartype; 171 localDeclarations = localDeclarations.append(localDecl); 172 } 173 174 FieldRewriter fieldRewriter = new FieldRewriter(constructor, fieldToLocalMap); 175 ListBuffer<JCStatement> newBody = new ListBuffer<>(); 176 for (JCStatement st : constructor.body.stats) { 177 newBody = newBody.append(fieldRewriter.translate(st)); 178 } 179 localDeclarations.addAll(newBody); 180 ListBuffer<JCStatement> assigmentsBeforeSuper = new ListBuffer<>(); 181 for (Symbol vsym : fieldToLocalMap.keySet()) { 182 Symbol local = fieldToLocalMap.get(vsym); 183 assigmentsBeforeSuper.append(make.at(constructor.pos()).Assignment(vsym, make.at(constructor.pos()).Ident(local))); 184 } 185 constructor.body.stats = localDeclarations.toList(); 186 JCTree.JCMethodInvocation constructorCall = TreeInfo.findConstructorCall(constructor); 187 if (constructorCall.args.isEmpty()) { 188 // this is just a super invocation with no arguments we can set the fields just before the invocation 189 // and let Gen do the rest 190 TreeInfo.mapSuperCalls(constructor.body, supercall -> make.Block(0, assigmentsBeforeSuper.toList().append(supercall))); 191 } else { 192 // we need to generate fresh local variables to catch the values of the arguments, then 193 // assign the proxy locals to the fields and finally invoke the super with the fresh local variables 194 int argPosition = 0; 195 ListBuffer<JCStatement> superArgsProxies = new ListBuffer<>(); 196 for (JCExpression arg : constructorCall.args) { 197 long flags = SYNTHETIC | FINAL; 198 VarSymbol proxyForArgSym = new VarSymbol(flags, newLocalName("" + argPosition), types.erasure(arg.type), constructor.sym); 199 JCVariableDecl proxyForArgDecl = make.at(constructor.pos).VarDef(proxyForArgSym, arg); 200 superArgsProxies = superArgsProxies.append(proxyForArgDecl); 201 argPosition++; 202 } 203 List<JCStatement> superArgsProxiesList = superArgsProxies.toList(); 204 ListBuffer<JCExpression> newArgs = new ListBuffer<>(); 205 for (JCStatement argProxy : superArgsProxies) { 206 newArgs.add(make.at(argProxy.pos).Ident((JCVariableDecl) argProxy)); 207 } 208 constructorCall.args = newArgs.toList(); 209 TreeInfo.mapSuperCalls(constructor.body, 210 supercall -> make.Block(0, superArgsProxiesList.appendList(assigmentsBeforeSuper.toList()).append(supercall))); 211 } 212 } 213 214 Name newLocalName(String name) { 215 return names.fromString("local" + target.syntheticNameChar() + name); 216 } 217 218 class FieldRewriter extends TreeTranslator { 219 JCMethodDecl md; 220 Map<Symbol, Symbol> fieldToLocalMap; 221 boolean ctorPrologue = true; 222 223 public FieldRewriter(JCMethodDecl md, Map<Symbol, Symbol> fieldToLocalMap) { 224 this.md = md; 225 this.fieldToLocalMap = fieldToLocalMap; 226 } 227 228 @Override 229 public void visitIdent(JCTree.JCIdent tree) { 230 if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) { 231 result = make.at(md).Ident(fieldToLocalMap.get(tree.sym)); 232 } else { 233 result = tree; 234 } 235 } 236 237 @Override 238 public void visitSelect(JCTree.JCFieldAccess tree) { 239 super.visitSelect(tree); 240 if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) { 241 result = make.at(md).Ident(fieldToLocalMap.get(tree.sym)); 242 } else { 243 result = tree; 244 } 245 } 246 247 @Override 248 public void visitAssign(JCAssign tree) { 249 JCExpression previousLHS = tree.lhs; 250 super.visitAssign(tree); 251 if (ctorPrologue && previousLHS != tree.lhs) { 252 unsetFieldsInfo.removeUnsetFieldInfo(currentClass, tree); 253 } 254 } 255 256 @Override 257 public void visitApply(JCTree.JCMethodInvocation tree) { 258 Name methName = TreeInfo.name(tree.meth); 259 boolean isConstructorCall = methName == names._this || methName == names._super; 260 super.visitApply(tree); 261 if (isConstructorCall) { 262 ctorPrologue = false; 263 } 264 } 265 } 266 }