1 /*
2  * Copyright (c) 2017, 2020, 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 com.sun.tools.javac.code.Flags;
29 import com.sun.tools.javac.code.Symbol;
30 import com.sun.tools.javac.code.Symbol.BindingSymbol;
31 import com.sun.tools.javac.code.Symbol.VarSymbol;
32 import com.sun.tools.javac.code.Symtab;
33 import com.sun.tools.javac.code.Type;
34 import com.sun.tools.javac.code.Types;
35 import com.sun.tools.javac.tree.JCTree;
36 import com.sun.tools.javac.tree.JCTree.JCAssign;
37 import com.sun.tools.javac.tree.JCTree.JCBinary;
38 import com.sun.tools.javac.tree.JCTree.JCConditional;
39 import com.sun.tools.javac.tree.JCTree.JCExpression;
40 import com.sun.tools.javac.tree.JCTree.JCForLoop;
41 import com.sun.tools.javac.tree.JCTree.JCIdent;
42 import com.sun.tools.javac.tree.JCTree.JCIf;
43 import com.sun.tools.javac.tree.JCTree.JCInstanceOf;
44 import com.sun.tools.javac.tree.JCTree.JCLabeledStatement;
45 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
46 import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
47 import com.sun.tools.javac.tree.JCTree.JCBindingPattern;
48 import com.sun.tools.javac.tree.JCTree.JCWhileLoop;
49 import com.sun.tools.javac.tree.JCTree.Tag;
50 import com.sun.tools.javac.tree.TreeMaker;
51 import com.sun.tools.javac.tree.TreeTranslator;
52 import com.sun.tools.javac.util.Assert;
53 import com.sun.tools.javac.util.Context;
54 import com.sun.tools.javac.util.ListBuffer;
55 import com.sun.tools.javac.util.Log;
56 import com.sun.tools.javac.util.Names;
57 import com.sun.tools.javac.util.Options;
58 
59 import java.util.Map;
60 import java.util.Map.Entry;
61 
62 import com.sun.tools.javac.code.Symbol.MethodSymbol;
63 import static com.sun.tools.javac.code.TypeTag.BOT;
64 import com.sun.tools.javac.jvm.Target;
65 import com.sun.tools.javac.tree.JCTree;
66 import com.sun.tools.javac.tree.JCTree.JCBlock;
67 import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop;
68 import com.sun.tools.javac.tree.JCTree.JCLambda;
69 import com.sun.tools.javac.tree.JCTree.JCStatement;
70 import com.sun.tools.javac.tree.JCTree.LetExpr;
71 import com.sun.tools.javac.util.List;
72 import java.util.HashMap;
73 
74 /**
75  * This pass translates pattern-matching constructs, such as instanceof <pattern>.
76  */
77 public class TransPatterns extends TreeTranslator {
78 
79     protected static final Context.Key<TransPatterns> transPatternsKey = new Context.Key<>();
80 
instance(Context context)81     public static TransPatterns instance(Context context) {
82         TransPatterns instance = context.get(transPatternsKey);
83         if (instance == null)
84             instance = new TransPatterns(context);
85         return instance;
86     }
87 
88     private final Symtab syms;
89     private final Types types;
90     private final Operators operators;
91     private final Log log;
92     private final ConstFold constFold;
93     private final Names names;
94     private final Target target;
95     private TreeMaker make;
96 
97     BindingContext bindingContext = new BindingContext() {
98         @Override
99         VarSymbol bindingDeclared(BindingSymbol varSymbol) {
100             return null;
101         }
102 
103         @Override
104         VarSymbol getBindingFor(BindingSymbol varSymbol) {
105             return null;
106         }
107 
108         @Override
109         JCStatement decorateStatement(JCStatement stat) {
110             return stat;
111         }
112 
113         @Override
114         JCExpression decorateExpression(JCExpression expr) {
115             return expr;
116         }
117 
118         @Override
119         BindingContext pop() {
120             //do nothing
121             return this;
122         }
123 
124         @Override
125         boolean tryPrepend(BindingSymbol binding, JCVariableDecl var) {
126             return false;
127         }
128     };
129 
130     JCLabeledStatement pendingMatchLabel = null;
131 
132     boolean debugTransPatterns;
133 
134     private MethodSymbol currentMethodSym = null;
135 
TransPatterns(Context context)136     protected TransPatterns(Context context) {
137         context.put(transPatternsKey, this);
138         syms = Symtab.instance(context);
139         make = TreeMaker.instance(context);
140         types = Types.instance(context);
141         operators = Operators.instance(context);
142         log = Log.instance(context);
143         constFold = ConstFold.instance(context);
144         names = Names.instance(context);
145         target = Target.instance(context);
146         debugTransPatterns = Options.instance(context).isSet("debug.patterns");
147     }
148 
149     @Override
visitTypeTest(JCInstanceOf tree)150     public void visitTypeTest(JCInstanceOf tree) {
151         if (tree.pattern.hasTag(Tag.BINDINGPATTERN)) {
152             //E instanceof T N
153             //=>
154             //(let T' N$temp = E; N$temp instanceof T && (N = (T) N$temp == (T) N$temp))
155             JCBindingPattern patt = (JCBindingPattern)tree.pattern;
156             VarSymbol pattSym = patt.symbol;
157             Type tempType = tree.expr.type.hasTag(BOT) ?
158                     syms.objectType
159                     : tree.expr.type;
160             VarSymbol temp = new VarSymbol(pattSym.flags() | Flags.SYNTHETIC,
161                     names.fromString(pattSym.name.toString() + target.syntheticNameChar() + "temp"),
162                     tempType,
163                     patt.symbol.owner);
164             JCExpression translatedExpr = translate(tree.expr);
165             Type castTargetType = types.boxedTypeOrType(pattSym.erasure(types));
166 
167             result = makeTypeTest(make.Ident(temp), make.Type(castTargetType));
168 
169             VarSymbol bindingVar = bindingContext.bindingDeclared(patt.symbol);
170             if (bindingVar != null) { //TODO: cannot be null here?
171                 JCAssign fakeInit = (JCAssign)make.at(tree.pos).Assign(
172                         make.Ident(bindingVar), convert(make.Ident(temp), castTargetType)).setType(bindingVar.erasure(types));
173                 result = makeBinary(Tag.AND, (JCExpression)result,
174                         makeBinary(Tag.EQ, fakeInit, convert(make.Ident(temp), castTargetType)));
175             }
176             result = make.at(tree.pos).LetExpr(make.VarDef(temp, translatedExpr), (JCExpression)result).setType(syms.booleanType);
177             ((LetExpr) result).needsCond = true;
178         } else {
179             super.visitTypeTest(tree);
180         }
181     }
182 
183     @Override
visitBinary(JCBinary tree)184     public void visitBinary(JCBinary tree) {
185         bindingContext = new BasicBindingContext();
186         try {
187             super.visitBinary(tree);
188             result = bindingContext.decorateExpression(tree);
189         } finally {
190             bindingContext.pop();
191         }
192     }
193 
194     @Override
visitConditional(JCConditional tree)195     public void visitConditional(JCConditional tree) {
196         bindingContext = new BasicBindingContext();
197         try {
198             super.visitConditional(tree);
199             result = bindingContext.decorateExpression(tree);
200         } finally {
201             bindingContext.pop();
202         }
203     }
204 
205     @Override
visitIf(JCIf tree)206     public void visitIf(JCIf tree) {
207         bindingContext = new BasicBindingContext();
208         try {
209             super.visitIf(tree);
210             result = bindingContext.decorateStatement(tree);
211         } finally {
212             bindingContext.pop();
213         }
214     }
215 
216     @Override
visitForLoop(JCForLoop tree)217     public void visitForLoop(JCForLoop tree) {
218         bindingContext = new BasicBindingContext();
219         try {
220             super.visitForLoop(tree);
221             result = bindingContext.decorateStatement(tree);
222         } finally {
223             bindingContext.pop();
224         }
225     }
226 
227     @Override
visitWhileLoop(JCWhileLoop tree)228     public void visitWhileLoop(JCWhileLoop tree) {
229         bindingContext = new BasicBindingContext();
230         try {
231             super.visitWhileLoop(tree);
232             result = bindingContext.decorateStatement(tree);
233         } finally {
234             bindingContext.pop();
235         }
236     }
237 
238     @Override
visitDoLoop(JCDoWhileLoop tree)239     public void visitDoLoop(JCDoWhileLoop tree) {
240         bindingContext = new BasicBindingContext();
241         try {
242             super.visitDoLoop(tree);
243             result = bindingContext.decorateStatement(tree);
244         } finally {
245             bindingContext.pop();
246         }
247     }
248 
249     @Override
visitMethodDef(JCMethodDecl tree)250     public void visitMethodDef(JCMethodDecl tree) {
251         MethodSymbol prevMethodSym = currentMethodSym;
252         try {
253             currentMethodSym = tree.sym;
254             super.visitMethodDef(tree);
255         } finally {
256             currentMethodSym = prevMethodSym;
257         }
258     }
259 
260     @Override
visitIdent(JCIdent tree)261     public void visitIdent(JCIdent tree) {
262         VarSymbol bindingVar = null;
263         if ((tree.sym.flags() & Flags.MATCH_BINDING) != 0) {
264             bindingVar = bindingContext.getBindingFor((BindingSymbol)tree.sym);
265         }
266         if (bindingVar == null) {
267             super.visitIdent(tree);
268         } else {
269             result = make.at(tree.pos).Ident(bindingVar);
270         }
271     }
272 
273     @Override
visitBlock(JCBlock tree)274     public void visitBlock(JCBlock tree) {
275         ListBuffer<JCStatement> statements = new ListBuffer<>();
276         bindingContext = new BindingDeclarationFenceBindingContext() {
277             boolean tryPrepend(BindingSymbol binding, JCVariableDecl var) {
278                 //{
279                 //    if (E instanceof T N) {
280                 //        return ;
281                 //    }
282                 //    //use of N:
283                 //}
284                 //=>
285                 //{
286                 //    T N;
287                 //    if ((let T' N$temp = E; N$temp instanceof T && (N = (T) N$temp == (T) N$temp))) {
288                 //        return ;
289                 //    }
290                 //    //use of N:
291                 //}
292                 hoistedVarMap.put(binding, var.sym);
293                 statements.append(var);
294                 return true;
295             }
296         };
297         try {
298             for (List<JCStatement> l = tree.stats; l.nonEmpty(); l = l.tail) {
299                 statements.append(translate(l.head));
300             }
301 
302             tree.stats = statements.toList();
303             result = tree;
304         } finally {
305             bindingContext.pop();
306         }
307     }
308 
309     @Override
visitLambda(JCLambda tree)310     public void visitLambda(JCLambda tree) {
311         BindingContext prevContent = bindingContext;
312         try {
313             bindingContext = new BindingDeclarationFenceBindingContext();
314             super.visitLambda(tree);
315         } finally {
316             bindingContext = prevContent;
317         }
318     }
319 
translateTopLevelClass(Env<AttrContext> env, JCTree cdef, TreeMaker make)320     public JCTree translateTopLevelClass(Env<AttrContext> env, JCTree cdef, TreeMaker make) {
321         try {
322             this.make = make;
323             translate(cdef);
324         } finally {
325             // note that recursive invocations of this method fail hard
326             this.make = null;
327         }
328 
329         return cdef;
330     }
331 
332     /** Make an instanceof expression.
333      *  @param lhs      The expression.
334      *  @param type     The type to be tested.
335      */
336 
makeTypeTest(JCExpression lhs, JCExpression type)337     JCInstanceOf makeTypeTest(JCExpression lhs, JCExpression type) {
338         JCInstanceOf tree = make.TypeTest(lhs, type);
339         tree.type = syms.booleanType;
340         return tree;
341     }
342 
343     /** Make an attributed binary expression (copied from Lower).
344      *  @param optag    The operators tree tag.
345      *  @param lhs      The operator's left argument.
346      *  @param rhs      The operator's right argument.
347      */
makeBinary(JCTree.Tag optag, JCExpression lhs, JCExpression rhs)348     JCBinary makeBinary(JCTree.Tag optag, JCExpression lhs, JCExpression rhs) {
349         JCBinary tree = make.Binary(optag, lhs, rhs);
350         tree.operator = operators.resolveBinary(tree, optag, lhs.type, rhs.type);
351         tree.type = tree.operator.type.getReturnType();
352         return tree;
353     }
354 
convert(JCExpression expr, Type target)355     JCExpression convert(JCExpression expr, Type target) {
356         JCExpression result = make.at(expr.pos()).TypeCast(make.Type(target), expr);
357         result.type = target;
358         return result;
359     }
360 
361     abstract class BindingContext {
bindingDeclared(BindingSymbol varSymbol)362         abstract VarSymbol bindingDeclared(BindingSymbol varSymbol);
getBindingFor(BindingSymbol varSymbol)363         abstract VarSymbol getBindingFor(BindingSymbol varSymbol);
decorateStatement(JCStatement stat)364         abstract JCStatement decorateStatement(JCStatement stat);
decorateExpression(JCExpression expr)365         abstract JCExpression decorateExpression(JCExpression expr);
pop()366         abstract BindingContext pop();
tryPrepend(BindingSymbol binding, JCVariableDecl var)367         abstract boolean tryPrepend(BindingSymbol binding, JCVariableDecl var);
368     }
369 
370     class BasicBindingContext extends BindingContext {
371         Map<BindingSymbol, VarSymbol> hoistedVarMap;
372         BindingContext parent;
373 
BasicBindingContext()374         public BasicBindingContext() {
375             this.parent = bindingContext;
376             this.hoistedVarMap = new HashMap<>();
377         }
378 
379         @Override
bindingDeclared(BindingSymbol varSymbol)380         VarSymbol bindingDeclared(BindingSymbol varSymbol) {
381             VarSymbol res = parent.bindingDeclared(varSymbol);
382             if (res == null) {
383                 res = new VarSymbol(varSymbol.flags(), varSymbol.name, varSymbol.type, varSymbol.owner);
384                 res.setTypeAttributes(varSymbol.getRawTypeAttributes());
385                 hoistedVarMap.put(varSymbol, res);
386             }
387             return res;
388         }
389 
390         @Override
getBindingFor(BindingSymbol varSymbol)391         VarSymbol getBindingFor(BindingSymbol varSymbol) {
392             VarSymbol res = parent.getBindingFor(varSymbol);
393             if (res != null) {
394                 return res;
395             }
396             return hoistedVarMap.entrySet().stream()
397                     .filter(e -> e.getKey().isAliasFor(varSymbol))
398                     .findFirst()
399                     .map(e -> e.getValue()).orElse(null);
400         }
401 
402         @Override
decorateStatement(JCStatement stat)403         JCStatement decorateStatement(JCStatement stat) {
404             if (hoistedVarMap.isEmpty()) return stat;
405             //if (E instanceof T N) {
406             //     //use N
407             //}
408             //=>
409             //{
410             //    T N;
411             //    if ((let T' N$temp = E; N$temp instanceof T && (N = (T) N$temp == (T) N$temp))) {
412             //        //use N
413             //    }
414             //}
415             ListBuffer<JCStatement> stats = new ListBuffer<>();
416             for (Entry<BindingSymbol, VarSymbol> e : hoistedVarMap.entrySet()) {
417                 JCVariableDecl decl = makeHoistedVarDecl(stat.pos, e.getValue());
418                 if (!e.getKey().isPreserved() ||
419                     !parent.tryPrepend(e.getKey(), decl)) {
420                     stats.add(decl);
421                 }
422             }
423             if (stats.nonEmpty()) {
424                 stats.add(stat);
425                 stat = make.at(stat.pos).Block(0, stats.toList());
426             }
427             return stat;
428         }
429 
430         @Override
decorateExpression(JCExpression expr)431         JCExpression decorateExpression(JCExpression expr) {
432             //E instanceof T N && /*use of N*/
433             //=>
434             //(let T N; (let T' N$temp = E; N$temp instanceof T && (N = (T) N$temp == (T) N$temp)) && /*use of N*/)
435             for (VarSymbol vsym : hoistedVarMap.values()) {
436                 expr = make.at(expr.pos).LetExpr(makeHoistedVarDecl(expr.pos, vsym), expr).setType(expr.type);
437             }
438             return expr;
439         }
440 
441         @Override
pop()442         BindingContext pop() {
443             return bindingContext = parent;
444         }
445 
446         @Override
tryPrepend(BindingSymbol binding, JCVariableDecl var)447         boolean tryPrepend(BindingSymbol binding, JCVariableDecl var) {
448             return false;
449         }
450 
makeHoistedVarDecl(int pos, VarSymbol varSymbol)451         private JCVariableDecl makeHoistedVarDecl(int pos, VarSymbol varSymbol) {
452             return make.at(pos).VarDef(varSymbol, null);
453         }
454     }
455 
456     private class BindingDeclarationFenceBindingContext extends BasicBindingContext {
457 
458         @Override
bindingDeclared(BindingSymbol varSymbol)459         VarSymbol bindingDeclared(BindingSymbol varSymbol) {
460             return null;
461         }
462 
463     }
464 }
465