|
| 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 | +} |
0 commit comments