1 /*
  2  * Copyright (c) 2018, 2019, 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.jvm;
 27 
 28 import com.sun.tools.javac.code.Flags;
 29 import com.sun.tools.javac.code.Scope.LookupKind;
 30 import com.sun.tools.javac.code.Symbol;
 31 import com.sun.tools.javac.code.Symbol.MethodSymbol;
 32 import com.sun.tools.javac.code.Symbol.VarSymbol;
 33 import com.sun.tools.javac.code.Symtab;
 34 import com.sun.tools.javac.code.Type;
 35 import com.sun.tools.javac.code.Type.ClassType;
 36 import com.sun.tools.javac.code.Type.ClassType.Flavor;
 37 import com.sun.tools.javac.code.Type.MethodType;
 38 import com.sun.tools.javac.code.Types;
 39 import com.sun.tools.javac.tree.JCTree;
 40 import com.sun.tools.javac.tree.JCTree.JCAssign;
 41 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
 42 import com.sun.tools.javac.tree.JCTree.JCExpression;
 43 import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
 44 import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
 45 import com.sun.tools.javac.tree.JCTree.JCIdent;
 46 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
 47 import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
 48 import com.sun.tools.javac.tree.JCTree.JCNewClass;
 49 import com.sun.tools.javac.tree.JCTree.JCReturn;
 50 import com.sun.tools.javac.tree.JCTree.JCStatement;
 51 import com.sun.tools.javac.tree.TreeInfo;
 52 import com.sun.tools.javac.tree.TreeMaker;
 53 import com.sun.tools.javac.tree.TreeTranslator;
 54 import com.sun.tools.javac.util.Assert;
 55 import com.sun.tools.javac.util.Context;
 56 import com.sun.tools.javac.util.List;
 57 import com.sun.tools.javac.util.Name;
 58 import com.sun.tools.javac.util.Names;
 59 
 60 import java.util.HashMap;
 61 import java.util.Map;
 62 
 63 import static com.sun.tools.javac.code.Flags.STATIC;
 64 import static com.sun.tools.javac.code.Kinds.Kind.MTH;
 65 import static com.sun.tools.javac.code.Kinds.Kind.TYP;
 66 import static com.sun.tools.javac.code.Kinds.Kind.VAR;
 67 import static com.sun.tools.javac.tree.JCTree.Tag.APPLY;
 68 import static com.sun.tools.javac.tree.JCTree.Tag.EXEC;
 69 import static com.sun.tools.javac.tree.JCTree.Tag.IDENT;
 70 
 71 /**
 72  * This pass translates primitive class constructors into static factory methods and patches up constructor
 73  * calls to become invocations of those static factory methods.
 74  *
 75  * We get commissioned as a subpass of Gen. Constructor trees undergo plenty of change in Lower
 76  * (enclosing instance injection, captured locals ...) and in Gen (instance field initialization,
 77  * see normalizeDefs) and so it is most effective to wait until things reach a quiescent state
 78  * before undertaking the tinkering that we do.
 79  *
 80  * See https://bugs.openjdk.java.net/browse/JDK-8198749 for the kind of transformations we do.
 81  *
 82  */
 83 public class TransPrimitiveClass extends TreeTranslator {
 84 
 85     protected static final Context.Key<TransPrimitiveClass> transPrimitiveClass = new Context.Key<>();
 86 
 87     private Symtab syms;
 88     private TreeMaker make;
 89     private Types types;
 90     private Names names;
 91 
 92     /* Is an assignment undergoing translation just an assignment statement ?
 93        Or is also a value ??
 94     */
 95     private boolean requireRVal;
 96 
 97     // class currently undergoing translation.
 98     private JCClassDecl currentClass;
 99 
100     // method currently undergoing translation.
101     private JCMethodDecl currentMethod;
102 
103     // list of factories synthesized so far.
104     private List<JCTree> staticFactories;
105 
106     // Map from constructor symbols to factory symbols.
107     private Map<MethodSymbol, MethodSymbol> init2factory = new HashMap<>();
108 
109     public static TransPrimitiveClass instance(Context context) {
110         TransPrimitiveClass instance = context.get(transPrimitiveClass);
111         if (instance == null)
112             instance = new TransPrimitiveClass(context);
113         return instance;
114     }
115 
116     protected TransPrimitiveClass(Context context) {
117         context.put(transPrimitiveClass, this);
118         syms = Symtab.instance(context);
119         make = TreeMaker.instance(context);
120         types = Types.instance(context);
121         names = Names.instance(context);
122     }
123 
124     @SuppressWarnings("unchecked")
125     public <T extends JCTree> T translate(T tree, boolean requireRVal) {
126         boolean priorRequireRVal = this.requireRVal;
127         try {
128             this.requireRVal = requireRVal;
129             if (tree == null) {
130                 return null;
131             } else {
132                 tree.accept(this);
133                 JCTree tmpResult = this.result;
134                 this.result = null;
135                 return (T)tmpResult; // XXX cast
136             }
137         } finally {
138              this.requireRVal = priorRequireRVal;
139         }
140     }
141 
142     @Override
143     public <T extends JCTree> T translate(T tree) {
144         return translate(tree, true);
145     }
146 
147     public JCClassDecl translateTopLevelClass(JCClassDecl classDecl, TreeMaker make) {
148         try {
149             this.make = make;
150             translate(classDecl);
151         } finally {
152             // note that recursive invocations of this method fail hard
153             this.make = null;
154         }
155         init2factory = new HashMap<>();
156         return classDecl;
157     }
158 
159     @Override
160     public void visitClassDef(JCClassDecl classDecl) {
161         JCClassDecl previousClass = currentClass;
162         List<JCTree> previousFactories = staticFactories;
163         staticFactories = List.nil();
164         currentClass = classDecl;
165         try {
166             super.visitClassDef(classDecl);
167             classDecl.defs = classDecl.defs.appendList(staticFactories);
168             staticFactories = List.nil();
169         }
170         finally {
171             currentClass = previousClass;
172             staticFactories = previousFactories;
173         }
174     }
175 
176     @Override
177     public void visitMethodDef(JCMethodDecl tree) {
178         JCMethodDecl previousMethod = currentMethod;
179         currentMethod = tree;
180         try {
181             if (constructingPrimitiveObject()) {
182 
183                 // Mutate this primitive class constructor into an equivalent static factory
184                 make.at(tree.pos());
185                 JCExpressionStatement exec = chainedConstructorCall(tree);
186                 Assert.check(exec != null && TreeInfo.isSelfCall(exec));
187                 JCMethodInvocation call = (JCMethodInvocation) exec.expr;
188 
189                 /* Unlike the reference construction sequence where `this' is allocated ahead of time and
190                    is passed as an argument into the <init> method, the primitive static factory must allocate the
191                    instance that forms the `product' by itself. We do that by injecting a prologue here.
192                 */
193                 ClassType productType = (ClassType) currentClass.sym.erasure(types);
194                 if (currentClass.sym.isReferenceFavoringPrimitiveClass())
195                     productType = new ClassType(productType.getEnclosingType(), List.nil(), productType.tsym, productType.getMetadata(), Flavor.Q_TypeOf_L);
196                 VarSymbol product = currentMethod.factoryProduct = new VarSymbol(0, names.dollarValue, productType, currentMethod.sym); // TODO: owner needs rewiring
197                 JCExpression rhs;
198 
199                 final Name name = TreeInfo.name(call.meth);
200                 MethodSymbol symbol = (MethodSymbol)TreeInfo.symbol(call.meth);
201                 if (names._super.equals(name)) { // "initial" constructor.
202                     // Synthesize code to allocate factory "product" via: V $this = V.default;
203                     Assert.check(symbol.type.getParameterTypes().size() == 0);
204                     final JCExpression type = make.Type(productType);
205                     rhs = make.DefaultValue(type);
206                     rhs.type = productType;
207                 } else {
208                     // This must be a chained call of form `this(args)'; Mutate it into a factory invocation i.e V $this = V.init(args);
209                     Assert.check(TreeInfo.name(TreeInfo.firstConstructorCall(tree).meth) == names._this);
210                     MethodSymbol factory = getPrimitiveObjectFactory(symbol);
211                     final JCIdent ident = make.Ident(factory);
212                     rhs = make.App(ident, call.args);
213                     ((JCMethodInvocation)rhs).varargsElement = call.varargsElement;
214                 }
215 
216                 /* The static factory product allocation prologue must precede any synthetic inits !!!
217                    as these may reference `this' which gets pre-allocated for references but
218                    not for primitive objects.
219                 */
220                 JCStatement prologue = make.VarDef(product, rhs);
221                 tree.body.stats = tree.body.stats.prepend(prologue).diff(List.of(exec));
222                 tree.body = translate(tree.body);
223 
224                 MethodSymbol factorySym = getPrimitiveObjectFactory(tree.sym);
225                 currentMethod.setType(factorySym.type);
226                 currentMethod.factoryProduct = product;
227                 currentClass.sym.members().remove(tree.sym);
228                 tree.sym = factorySym;
229                 currentClass.sym.members().enter(factorySym);
230                 tree.mods.flags |= STATIC;
231 
232                 /* We may need an epilogue that returns the factory product, but we can't eagerly insert
233                    a return here, since we don't know much about control flow here. Gen#genMethod
234                    will insert a return of the factory product if control does reach the end and would
235                    "fall off the cliff" otherwise.
236                 */
237 
238                 result = tree;
239                 return;
240             }
241             super.visitMethodDef(tree);
242         } finally {
243             currentMethod = previousMethod;
244         }
245     }
246 
247     @Override
248     public void visitReturn(JCReturn tree) {
249         if (constructingPrimitiveObject()) {
250             result = make.Return(make.Ident(currentMethod.factoryProduct));
251         } else {
252             super.visitReturn(tree);
253         }
254     }
255 
256     /* Note: 1. Assignop does not call for any translation, since primitive class instance fields are final and
257        so cannot be AssignedOped. 2. Any redundantly qualified this would have been lowered already.
258     */
259     @Override
260     public void visitAssign(JCAssign tree) {
261         if (constructingPrimitiveObject()) {
262             Symbol symbol = null;
263             switch(tree.lhs.getTag()) {
264                 case IDENT:
265                     symbol = ((JCIdent)tree.lhs).sym;
266                     break;
267                 case SELECT:
268                     JCFieldAccess fieldAccess = (JCFieldAccess) tree.lhs;
269                     if (fieldAccess.selected.hasTag(IDENT) && ((JCIdent)fieldAccess.selected).name == names._this) {
270                         symbol = fieldAccess.sym;
271                     }
272                     break;
273                 default:
274                     break;
275             }
276             if (isInstanceMemberAccess(symbol)) {
277                 final JCIdent facHandle = make.Ident(currentMethod.factoryProduct);
278                 result = make.Assign(facHandle, make.WithField(make.Select(facHandle, symbol), translate(tree.rhs)).setType(currentClass.type)).setType(currentClass.type);
279                 if (requireRVal) {
280                     result = make.Select(make.Parens((JCExpression) result).setType(currentClass.type), symbol);
281                 }
282                 return;
283             }
284         }
285         super.visitAssign(tree);
286     }
287 
288     @Override
289     public void visitExec(JCExpressionStatement tree) {
290         if (constructingPrimitiveObject()) {
291             tree.expr = translate(tree.expr, false);
292             result = tree;
293         } else {
294             super.visitExec(tree);
295         }
296     }
297 
298     @Override
299     public void visitIdent(JCIdent ident) {
300         if (constructingPrimitiveObject()) {
301             Symbol symbol = ident.sym;
302             if (isInstanceMemberAccess(symbol)) {
303                 final JCIdent facHandle = make.Ident(currentMethod.factoryProduct);
304                 result = make.Select(facHandle, symbol);
305                 return;
306             } else if (symbol.name == names._this) {
307                 result = make.Ident(currentMethod.factoryProduct);
308                 return;
309             }
310         }
311         super.visitIdent(ident);
312     }
313 
314     @Override
315     public void visitSelect(JCFieldAccess fieldAccess) {
316         if (constructingPrimitiveObject()) { // Qualified this would have been lowered already.
317             if (fieldAccess.selected.hasTag(IDENT) && ((JCIdent)fieldAccess.selected).name == names._this) {
318                 Symbol symbol = fieldAccess.sym;
319                 if (isInstanceMemberAccess(symbol)) {
320                     final JCIdent facHandle = make.Ident(currentMethod.factoryProduct);
321                     result = make.Select(facHandle, symbol);
322                     return;
323                 }
324             }
325         }
326         /* If a static member is being selected via a V.ref as a TYP, rewrite
327            V.ref.member to V.member
328         */
329         fieldAccess.selected = translate(fieldAccess.selected);
330         if (fieldAccess.name != names._class) {  // TODO: this and super ??
331             Symbol sym = TreeInfo.symbol(fieldAccess);
332             Symbol sitesym = TreeInfo.symbol(fieldAccess.selected);
333             Type selectedType = fieldAccess.selected.type;
334             if (selectedType.isReferenceProjection()) {
335                 switch (sym.kind) {
336                     case MTH:
337                     case VAR:
338                         if (sym.isStatic() && sitesym != null && sitesym.kind == TYP) {
339                             fieldAccess.selected = make.Type(types.erasure(selectedType.asValueType()));
340                         }
341                         break;
342                     case TYP:
343                         fieldAccess.selected = make.Type(types.erasure(selectedType.asValueType()));
344                         break;
345                 }
346             }
347         }
348         result = fieldAccess;
349     }
350 
351     // Translate a reference style instance creation attempt on a primitive class to a static factory call.
352     @Override
353     public void visitNewClass(JCNewClass tree) {
354         if (tree.clazz.type.tsym.isPrimitiveClass()) {
355             // Enclosing instances or anonymous classes should have been eliminated by now.
356             Assert.check(tree.encl == null && tree.def == null);
357             tree.args = translate(tree.args);
358             Assert.check(tree.def == null);
359             MethodSymbol sFactory = getPrimitiveObjectFactory((MethodSymbol) tree.constructor);
360             make.at(tree.pos());
361             JCExpression declClass = make.Type(tree.constructor.owner.type);
362             JCExpression meth = make.Select(declClass, sFactory);
363             meth.type = types.erasure(meth.type);
364             final JCMethodInvocation apply = make.Apply(tree.typeargs, meth, tree.args);
365             apply.varargsElement = tree.varargsElement;
366             apply.type = meth.type.getReturnType();
367             result = apply;
368             return;
369         }
370         super.visitNewClass(tree);
371     }
372 
373     // Utility methods ...
374     private boolean constructingPrimitiveObject() {
375         return currentClass != null && (currentClass.sym.flags() & Flags.PRIMITIVE_CLASS) != 0 && currentMethod != null && currentMethod.sym.isConstructor();
376     }
377 
378     private boolean isInstanceMemberAccess(Symbol symbol) {
379         return symbol != null
380                 && (symbol.name != names._this && symbol.name != names._super)
381                 && (symbol.kind == VAR || symbol.kind == MTH)
382                 && symbol.owner == currentClass.sym && !symbol.isStatic();
383     }
384 
385     private MethodSymbol getPrimitiveObjectFactory(MethodSymbol init) {
386         Assert.check(init.name.equals(names.init));
387         Assert.check(init.owner.isPrimitiveClass());
388         MethodSymbol factory = init2factory.get(init);
389         if (factory != null)
390             return factory;
391 
392         MethodType factoryType = new MethodType(init.type.getParameterTypes(),
393                                                 init.owner.type.asValueType(),
394                                                 init.type.getThrownTypes(),
395                                                 init.owner.type.tsym);
396         factory = new MethodSymbol(init.flags_field | STATIC,
397                                         names.init,
398                                         factoryType,
399                                         init.owner);
400         factory.params = init.params;
401         // Re-patch the return type on the erased method type, or code generation will fail
402         factory.erasure_field = new MethodType(init.erasure(types).getParameterTypes(),
403                 init.owner.type.asValueType(),
404                 init.type.getThrownTypes(),
405                 init.owner.type.tsym);
406         factory.setAttributes(init);
407         init2factory.put(init, factory);
408         return factory;
409     }
410 
411     /** Return the *statement* in the constructor that `chains' to another constructor call either
412      *  in the same class or its superclass. One MUST exist except for jlO, though may be buried
413      *  under synthetic initializations.
414      */
415     private JCExpressionStatement chainedConstructorCall(JCMethodDecl md) {
416         if (md.name == names.init && md.body != null) {
417             for (JCStatement statement : md.body.stats) {
418                 if (statement.hasTag(EXEC)) {
419                     JCExpressionStatement exec = (JCExpressionStatement)statement;
420                     if (exec.expr.hasTag(APPLY)) {
421                         JCMethodInvocation apply = (JCMethodInvocation)exec.expr;
422                         Name name = TreeInfo.name(apply.meth);
423                         if (name == names._super || name == names._this)
424                             return exec;
425                     }
426                 }
427             }
428         }
429         return null;
430     }
431 
432     private MethodSymbol getDefaultConstructor(Symbol klass) {
433         for (Symbol method : klass.members().getSymbolsByName(names.init, s->s.kind == MTH && s.type.getParameterTypes().size() == 0, LookupKind.NON_RECURSIVE)) {
434             return (MethodSymbol) method;
435         }
436         // class defines a non-nullary but no nullary constructor, fabricate a symbol.
437         MethodType dctorType = new MethodType(List.nil(),
438                 klass.type,
439                 List.nil(),
440                 klass.type.tsym);
441         return new MethodSymbol(Flags.PUBLIC,
442                 names.init,
443                 dctorType,
444                 klass);
445     }
446 }