1 /*
  2  * Copyright (c) 2016, 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 jdk.jshell;
 27 
 28 import java.util.EnumSet;
 29 import java.util.HashMap;
 30 import java.util.HashSet;
 31 import java.util.Map;
 32 import java.util.Set;
 33 import java.util.function.Function;
 34 import javax.lang.model.element.Element;
 35 import javax.lang.model.element.ElementKind;
 36 import javax.lang.model.element.VariableElement;
 37 import com.sun.source.tree.ReturnTree;
 38 import com.sun.source.tree.ClassTree;
 39 import com.sun.source.tree.CompilationUnitTree;
 40 import com.sun.source.tree.ConditionalExpressionTree;
 41 import com.sun.source.tree.ExpressionTree;
 42 import com.sun.source.tree.IdentifierTree;
 43 import com.sun.source.tree.MethodInvocationTree;
 44 import com.sun.source.tree.MethodTree;
 45 import com.sun.source.tree.NewClassTree;
 46 import com.sun.source.tree.Tree;
 47 import com.sun.source.tree.Tree.Kind;
 48 import com.sun.source.tree.VariableTree;
 49 import com.sun.source.util.TreePath;
 50 import com.sun.source.util.TreePathScanner;
 51 import com.sun.source.util.TreeScanner;
 52 import com.sun.tools.javac.code.Flags;
 53 import com.sun.tools.javac.code.Symbol;
 54 import com.sun.tools.javac.code.Symbol.TypeSymbol;
 55 import com.sun.tools.javac.code.Symtab;
 56 import com.sun.tools.javac.code.Type;
 57 import com.sun.tools.javac.code.Types;
 58 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
 59 import com.sun.tools.javac.tree.TreeInfo;
 60 import com.sun.tools.javac.util.List;
 61 import com.sun.tools.javac.util.ListBuffer;
 62 import java.util.function.BinaryOperator;
 63 import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo.AnonymousDescription;
 64 import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo.AnonymousDescription.VariableDesc;
 65 import jdk.jshell.TaskFactory.AnalyzeTask;
 66 import jdk.jshell.TypePrinter.AnonymousTypeKind;
 67 
 68 /**
 69  * Compute information about an expression string, particularly its type name.
 70  */
 71 class ExpressionToTypeInfo {
 72 
 73     private static final String OBJECT_TYPE_NAME = "Object";
 74 
 75     final AnalyzeTask at;
 76     final CompilationUnitTree cu;
 77     final JShell state;
 78     final boolean computeEnhancedInfo;
 79     final boolean enhancedTypesAccessible;
 80     final Symtab syms;
 81     final Types types;
 82     final Map<TypeSymbol, String> anon2Name = new HashMap<>();
 83 
 84     private ExpressionToTypeInfo(AnalyzeTask at, CompilationUnitTree cu, JShell state,
 85                                  boolean computeEnhancedInfo, boolean enhancedTypesAccessible) {
 86         this.at = at;
 87         this.cu = cu;
 88         this.state = state;
 89         this.computeEnhancedInfo = computeEnhancedInfo;
 90         this.enhancedTypesAccessible = enhancedTypesAccessible;
 91         this.syms = Symtab.instance(at.context);
 92         this.types = Types.instance(at.context);
 93     }
 94 
 95     public static class ExpressionInfo {
 96         ExpressionTree tree;
 97         boolean isPrimitiveType;
 98         String typeName;
 99         String accessibleTypeName;
100         /* In result of localVariableTypeForInitializer, the type that should be used
101          * as a declaration type of the field. This does not include intersection types,
102          * but does contain references to anonymous types converted to member types.
103          */
104         String declareTypeName;
105         /* In result of localVariableTypeForInitializer, the apparent/infered type of
106          * the variable. This includes intersection types, and references to anonymous
107          * types converted to member types.
108          */
109         String fullTypeName;
110         /* In result of localVariableTypeForInitializer, the human readable type of
111          * the variable. This includes intersection types, and human readable descriptions
112          * of anonymous types.
113          */
114         String displayTypeName;
115         boolean isNonVoid;
116         /* In result of localVariableTypeForInitializer, description of important anonymous
117          * classes.
118          */
119         List<AnonymousDescription> anonymousClasses = List.nil();
120 
121         /* A description of an anonymous class. */
122         static class AnonymousDescription {
123             /* Parameter types of the invoked super constructor.*/
124             List<String> parameterTypes;
125             /* Type of the base/enclosing expression, if any.*/
126             String enclosingInstanceType;
127             /* The denotable name of the supertype.*/
128             String superTypeName;
129             /* The human-readable name of this class.*/
130             String declareTypeName;
131             /* If the supertype of this anonymous is a class. */
132             boolean isClass;
133             /* Variables captured by this anonymous class*/
134             List<VariableDesc> capturedVariables;
135 
136             static class VariableDesc {
137                 String type;
138                 String name;
139 
140                 public VariableDesc(String type, String name) {
141                     this.type = type;
142                     this.name = name;
143                 }
144 
145             }
146         }
147     }
148 
149     // return mechanism and other general structure from TreePath.getPath()
150     private static class Result extends Error {
151 
152         static final long serialVersionUID = -5942088234594905629L;
153         @SuppressWarnings("serial") // Not statically typed as Serializable
154         final TreePath expressionPath;
155 
156         Result(TreePath path) {
157             this.expressionPath = path;
158         }
159     }
160 
161     private static class PathFinder extends TreePathScanner<TreePath, Boolean> {
162 
163         // Optimize out imports etc
164         @Override
165         public TreePath visitCompilationUnit(CompilationUnitTree node, Boolean isTargetContext) {
166             return scan(node.getTypeDecls(), isTargetContext);
167         }
168 
169         // Only care about members
170         @Override
171         public TreePath visitClass(ClassTree node, Boolean isTargetContext) {
172             return scan(node.getMembers(), isTargetContext);
173         }
174 
175         // Only want the doit method where the code is
176         @Override
177         public TreePath visitMethod(MethodTree node, Boolean isTargetContext) {
178             if (Util.isDoIt(node.getName())) {
179                 return scan(node.getBody(), true);
180             } else {
181                 return null;
182             }
183         }
184 
185         @Override
186         public TreePath visitReturn(ReturnTree node, Boolean isTargetContext) {
187             ExpressionTree tree = node.getExpression();
188             TreePath tp = new TreePath(getCurrentPath(), tree);
189             if (isTargetContext) {
190                 throw new Result(tp);
191             } else {
192                 return null;
193             }
194         }
195 
196         @Override
197         public TreePath visitVariable(VariableTree node, Boolean isTargetContext) {
198             if (isTargetContext) {
199                 throw new Result(getCurrentPath());
200             } else {
201                 return null;
202             }
203         }
204 
205     }
206 
207     private Type pathToType(TreePath tp) {
208         return (Type) at.trees().getTypeMirror(tp);
209     }
210 
211     private Type pathToType(TreePath tp, Tree tree) {
212         if (tree instanceof ConditionalExpressionTree) {
213             // Conditionals always wind up as Object -- this corrects
214             ConditionalExpressionTree cet = (ConditionalExpressionTree) tree;
215             Type tmt = pathToType(new TreePath(tp, cet.getTrueExpression()));
216             Type tmf = pathToType(new TreePath(tp, cet.getFalseExpression()));
217             if (!tmt.isPrimitive() && !tmf.isPrimitive()) {
218                 Type lub = types.lub(tmt, tmf);
219                 // System.err.printf("cond ? %s : %s  --  lub = %s\n",
220                 //             varTypeName(tmt), varTypeName(tmf), varTypeName(lub));
221                 return lub;
222             }
223         }
224         return pathToType(tp);
225     }
226 
227     /**
228      * Entry method: get expression info
229      * @param code the expression as a string
230      * @param state a JShell instance
231      * @return type information
232      */
233     public static ExpressionInfo expressionInfo(String code, JShell state) {
234         if (code == null || code.isEmpty()) {
235             return null;
236         }
237         OuterWrap codeWrap = state.outerMap.wrapInTrialClass(Wrap.methodReturnWrap(code));
238         try {
239             return state.taskFactory.analyze(codeWrap, at -> {
240                 CompilationUnitTree cu = at.firstCuTree();
241                 if (at.hasErrors() || cu == null) {
242                     return null;
243                 }
244                 return new ExpressionToTypeInfo(at, cu, state, false, false).typeOfExpression();
245             });
246         } catch (Exception ex) {
247             return null;
248         }
249     }
250 
251     /**
252      * Entry method: get expression info corresponding to a local variable declaration if its type
253      * has been inferred automatically from the given initializer.
254      * @param code the initializer as a string
255      * @param state a JShell instance
256      * @return type information
257      */
258     public static ExpressionInfo localVariableTypeForInitializer(String code, JShell state, boolean onlyAccessible) {
259         if (code == null || code.isEmpty()) {
260             return null;
261         }
262         try {
263             OuterWrap codeWrap = state.outerMap.wrapInTrialClass(Wrap.methodWrap("var $$$ = " + code));
264             return state.taskFactory.analyze(codeWrap, at -> {
265                 CompilationUnitTree cu = at.firstCuTree();
266                 if (at.hasErrors() || cu == null) {
267                     return null;
268                 }
269                 return new ExpressionToTypeInfo(at, cu, state, true, onlyAccessible)
270                         .typeOfExpression();
271             });
272         } catch (Exception ex) {
273             return null;
274         }
275     }
276 
277     /**List (in a stable order) all NewClassTree instances under {@code from} that should be
278      * converted to member classes
279      *
280      * @param from tree to inspect
281      * @return NewClassTree instances that should be converted to member classes
282      */
283     public static List<NewClassTree> listAnonymousClassesToConvert(Tree from) {
284         ListBuffer<NewClassTree> classes = new ListBuffer<>();
285 
286         new TreeScanner<Void, Void>() {
287             @Override
288             public Void visitNewClass(NewClassTree node, Void p) {
289                 if (node.getClassBody() != null) {
290                     classes.append(node);
291                     return null;
292                 }
293                 return super.visitNewClass(node, p);
294             }
295         }.scan(from, null);
296 
297         return classes.toList();
298     }
299 
300     private ExpressionInfo typeOfExpression() {
301         return treeToInfo(findExpressionPath());
302     }
303 
304     private TreePath findExpressionPath() {
305         try {
306             new PathFinder().scan(new TreePath(cu), false);
307         } catch (Result result) {
308             return result.expressionPath;
309         }
310         return null;
311     }
312 
313     /**
314      * A type is accessible if it is public or if it is package-private and is a
315      * type defined in JShell.  Additionally, all its type arguments must be
316      * accessible
317      *
318      * @param type the type to check for accessibility
319      * @return true if the type name can be referenced
320      */
321     private boolean isAccessible(Type type) {
322         Symbol.TypeSymbol tsym = type.asElement();
323         return ((tsym.flags() & Flags.PUBLIC) != 0 ||
324                 ((tsym.flags() & Flags.PRIVATE) == 0 &&
325                 Util.isInJShellClass(tsym.flatName().toString()))) &&
326                  type.getTypeArguments().stream()
327                         .allMatch(this::isAccessible);
328     }
329 
330     /**
331      * Return the superclass.
332      *
333      * @param type the type
334      * @return the superclass, or Object on error
335      */
336     private Type supertype(Type type) {
337         Type sup = types.supertype(type);
338         if (sup == Type.noType || sup == null) {
339             return syms.objectType;
340         }
341         return sup;
342     }
343 
344     /**
345      * Find an accessible supertype.
346      *
347      * @param type the type
348      * @return the type, if it is accessible, otherwise a superclass or
349      * interface which is
350      */
351     private List<Type> findAccessibleSupertypes(Type type) {
352         List<Type> accessible = List.nil();
353         Type accessibleSuper = syms.objectType;
354         // Iterate up the superclasses, see if any are accessible
355         for (Type sup = type; !types.isSameType(sup, syms.objectType); sup = supertype(sup)) {
356             if (isAccessible(sup)) {
357                 accessible = accessible.prepend(sup);
358                 accessibleSuper = sup;
359                 break;
360             }
361         }
362         // then look through superclasses for accessible interfaces
363         for (Type sup = type; !types.isSameType(sup, accessibleSuper); sup = supertype(sup)) {
364             for (Type itf : types.interfaces(sup)) {
365                 if (isAccessible(itf)) {
366                     accessible = accessible.prepend(itf);
367                 }
368             }
369         }
370         if (accessible.isEmpty()) {
371             // Punt, use Object which is the supertype of everything
372             accessible = accessible.prepend(syms.objectType);
373         }
374 
375         return accessible.reverse();
376     }
377 
378     private ExpressionInfo treeToInfo(TreePath tp) {
379         if (tp != null) {
380             Tree tree = tp.getLeaf();
381             boolean isExpression = tree instanceof ExpressionTree;
382             if (isExpression || tree.getKind() == Kind.VARIABLE) {
383                 ExpressionInfo ei = new ExpressionInfo();
384                 if (isExpression)
385                     ei.tree = (ExpressionTree) tree;
386                 Type type = pathToType(tp, tree);
387                 if (type != null) {
388                     switch (type.getKind()) {
389                         case VOID:
390                         case NONE:
391                         case ERROR:
392                         case OTHER:
393                             break;
394                         case NULL:
395                             ei.isNonVoid = true;
396                             ei.typeName = OBJECT_TYPE_NAME;
397                             ei.accessibleTypeName = OBJECT_TYPE_NAME;
398                             break;
399                         default: {
400                             ei.isNonVoid = true;
401                             ei.isPrimitiveType = type.isPrimitive();
402                             ei.typeName = varTypeName(type, false, AnonymousTypeKind.SUPER);
403                             List<Type> accessibleTypes = findAccessibleSupertypes(type);
404                             ei.accessibleTypeName =
405                                     varTypeName(accessibleTypes.head, false, AnonymousTypeKind.SUPER);
406                             if (computeEnhancedInfo) {
407                                 Type accessibleType = accessibleTypes.size() == 1 ? accessibleTypes.head
408                                             : types.makeIntersectionType(accessibleTypes);
409                                 ei.declareTypeName =
410                                         varTypeName(accessibleType, (full, pkg) -> full, false, AnonymousTypeKind.DECLARE);
411                                 ei.fullTypeName =
412                                         varTypeName(enhancedTypesAccessible ? accessibleType : type, (full, pkg) -> full,
413                                                     true, AnonymousTypeKind.DECLARE);
414                                 ei.displayTypeName =
415                                         varTypeName(type, true, AnonymousTypeKind.DISPLAY);
416                             }
417                             break;
418                         }
419                     }
420                 }
421                 if (tree.getKind() == Tree.Kind.VARIABLE && computeEnhancedInfo) {
422                     Tree init = ((VariableTree) tree).getInitializer();
423                     for (NewClassTree node : listAnonymousClassesToConvert(init)) {
424                         Set<VariableElement> captured = capturedVariables(at,
425                                                                           tp.getCompilationUnit(),
426                                                                           node);
427                         JCClassDecl clazz = (JCClassDecl) node.getClassBody();
428                         MethodInvocationTree superCall =
429                                 clazz.getMembers()
430                                      .stream()
431                                      .map(TreeInfo::firstConstructorCall)
432                                      .findAny()
433                                      .get();
434                         TreePath superCallPath
435                                 = at.trees().
436                                         getPath(tp.getCompilationUnit(), superCall.
437                                                 getMethodSelect());
438                         Type constrType = pathToType(superCallPath);
439                         AnonymousDescription desc = new AnonymousDescription();
440                         desc.parameterTypes = constrType.getParameterTypes().
441                                 stream().
442                                 map(t -> varTypeName(t, false, AnonymousTypeKind.DECLARE)).
443                                 collect(List.collector());
444                         if (node.getEnclosingExpression() != null) {
445                             TreePath enclPath = new TreePath(tp,
446                                                              node.getEnclosingExpression());
447                             desc.enclosingInstanceType = varTypeName(pathToType(enclPath),
448                                                                      false,
449                                                                      AnonymousTypeKind.DECLARE);
450                         }
451                         TreePath currentPath = at.trees()
452                                                  .getPath(tp.getCompilationUnit(),
453                                                           node);
454                         Type nodeType = pathToType(currentPath, node);
455                         desc.superTypeName = varTypeName(nodeType,
456                                                          false,
457                                                          AnonymousTypeKind.SUPER);
458                         desc.declareTypeName = varTypeName(nodeType,
459                                                            true, AnonymousTypeKind.DECLARE);
460                         desc.capturedVariables =
461                                 captured.stream()
462                                         .map(ve -> new VariableDesc(varTypeName((Type) ve.asType(),
463                                                                                 false,
464                                                                                 AnonymousTypeKind.DECLARE),
465                                                                     ve.getSimpleName().toString()))
466                                         .collect(List.collector());
467 
468                         desc.isClass = at.task.getTypes().directSupertypes(nodeType).size() == 1;
469                         ei.anonymousClasses = ei.anonymousClasses.prepend(desc);
470                     }
471                     ei.anonymousClasses = ei.anonymousClasses.reverse();
472                 }
473                 return ei;
474             }
475         }
476         return null;
477     }
478     //where:
479         private static Set<VariableElement> capturedVariables(AnalyzeTask at,
480                                                               CompilationUnitTree topLevel,
481                                                               Tree tree) {
482             Set<VariableElement> capturedVars = new HashSet<>();
483             new TreeScanner<Void, Void>() {
484                 Set<VariableElement> declaredLocalVars = new HashSet<>();
485                 @Override
486                 public Void visitVariable(VariableTree node, Void p) {
487                     TreePath currentPath = at.trees()
488                                              .getPath(topLevel, node);
489                     declaredLocalVars.add((VariableElement) at.trees().getElement(currentPath));
490                     return super.visitVariable(node, p);
491                 }
492 
493                 @Override
494                 public Void visitIdentifier(IdentifierTree node, Void p) {
495                     TreePath currentPath = at.trees()
496                                              .getPath(topLevel, node);
497                     Element el = at.trees().getElement(currentPath);
498                     if (el != null &&
499                         LOCAL_VARIABLES.contains(el.getKind()) &&
500                         !declaredLocalVars.contains(el)) {
501                         capturedVars.add((VariableElement) el);
502                     }
503                     return super.visitIdentifier(node, p);
504                 }
505             }.scan(tree, null);
506 
507             return capturedVars;
508         }
509         private static final Set<ElementKind> LOCAL_VARIABLES =
510                 EnumSet.of(ElementKind.EXCEPTION_PARAMETER, ElementKind.LOCAL_VARIABLE,
511                            ElementKind.PARAMETER, ElementKind.RESOURCE_VARIABLE);
512 
513     private String varTypeName(Type type, boolean printIntersectionTypes, AnonymousTypeKind anonymousTypesKind) {
514         return varTypeName(type, state.maps::fullClassNameAndPackageToClass, printIntersectionTypes, anonymousTypesKind);
515     }
516 
517     private String varTypeName(Type type, BinaryOperator<String> fullClassNameAndPackageToClass, boolean printIntersectionTypes, AnonymousTypeKind anonymousTypesKind) {
518         try {
519             Function<TypeSymbol, String> anonymousClass2DeclareName =
520                     cs -> anon2Name.computeIfAbsent(cs, state.eval::computeDeclareName);
521             TypePrinter tp = new TypePrinter(at.messages(), at.types(),
522                     fullClassNameAndPackageToClass, anonymousClass2DeclareName,
523                     printIntersectionTypes, anonymousTypesKind);
524             List<Type> captures = types.captures(type);
525             String res = tp.toString(types.upward(type, captures));
526 
527             if (res == null)
528                 res = OBJECT_TYPE_NAME;
529 
530             return res;
531         } catch (Exception ex) {
532             return OBJECT_TYPE_NAME;
533         }
534     }
535 
536 }