/* * Copyright (C) 2011-2015 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package lombok.javac; import static lombok.javac.Javac.*; import static lombok.javac.JavacTreeMaker.TypeTag.typeTag; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.Map; import javax.lang.model.type.TypeKind; import lombok.Lombok; import lombok.core.debug.AssertionLogger; import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.code.Symbol.TypeSymbol; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Type.ArrayType; import com.sun.tools.javac.code.Type.CapturedType; import com.sun.tools.javac.code.Type.ClassType; import com.sun.tools.javac.code.Type.WildcardType; import com.sun.tools.javac.code.Types; import com.sun.tools.javac.comp.Attr; import com.sun.tools.javac.comp.AttrContext; import com.sun.tools.javac.comp.Enter; import com.sun.tools.javac.comp.Env; import com.sun.tools.javac.comp.MemberEnter; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; public class JavacResolution { private final Attr attr; private final CompilerMessageSuppressor messageSuppressor; public JavacResolution(Context context) { attr = Attr.instance(context); messageSuppressor = new CompilerMessageSuppressor(context); } /* * We need to dig down to the level of the method or field declaration or (static) initializer block, then attribute that entire method/field/block using * the appropriate environment. So, we start from the top and walk down the node tree until we hit that method/field/block and stop there, recording both * the environment object (`env`) and the exact tree node (`copyAt`) at which to begin the attr process. */ private static final class EnvFinder extends JCTree.Visitor { private Env<AttrContext> env = null; private Enter enter; private MemberEnter memberEnter; private JCTree copyAt = null; EnvFinder(Context context) { this.enter = Enter.instance(context); this.memberEnter = MemberEnter.instance(context); } Env<AttrContext> get() { return env; } JCTree copyAt() { return copyAt; } @Override public void visitTopLevel(JCCompilationUnit tree) { if (copyAt != null) return; env = enter.getTopLevelEnv(tree); } @Override public void visitClassDef(JCClassDecl tree) { if (copyAt != null) return; if (tree.sym != null) env = enter.getClassEnv(tree.sym); } @Override public void visitMethodDef(JCMethodDecl tree) { if (copyAt != null) return; env = memberEnter.getMethodEnv(tree, env); copyAt = tree; } public void visitVarDef(JCVariableDecl tree) { if (copyAt != null) return; env = memberEnter.getInitEnv(tree, env); copyAt = tree; } @Override public void visitBlock(JCBlock tree) { if (copyAt != null) return; copyAt = tree; } @Override public void visitTree(JCTree that) { } } public Map<JCTree, JCTree> resolveMethodMember(JavacNode node) { ArrayDeque<JCTree> stack = new ArrayDeque<JCTree>(); { JavacNode n = node; while (n != null) { stack.push(n.get()); n = n.up(); } } messageSuppressor.disableLoggers(); try { EnvFinder finder = new EnvFinder(node.getContext()); while (!stack.isEmpty()) stack.pop().accept(finder); TreeMirrorMaker mirrorMaker = new TreeMirrorMaker(node.getTreeMaker(), node.getContext()); JCTree copy = mirrorMaker.copy(finder.copyAt()); memberEnterAndAttribute(copy, finder.get(), node.getContext()); return mirrorMaker.getOriginalToCopyMap(); } finally { messageSuppressor.enableLoggers(); } } private static Field memberEnterDotEnv; private static Field getMemberEnterDotEnv() { if (memberEnterDotEnv != null) return memberEnterDotEnv; try { Field f = MemberEnter.class.getDeclaredField("env"); f.setAccessible(true); memberEnterDotEnv = f; } catch (NoSuchFieldException e) { return null; } return memberEnterDotEnv; } @SuppressWarnings("unchecked") private static Env<AttrContext> getEnvOfMemberEnter(MemberEnter memberEnter) { Field f = getMemberEnterDotEnv(); try { return (Env<AttrContext>) f.get(memberEnter); } catch (Exception e) { return null; } } private static void setEnvOfMemberEnter(MemberEnter memberEnter, Env<AttrContext> env) { Field f = getMemberEnterDotEnv(); try { f.set(memberEnter, env); } catch (Exception e) { return; } } private void memberEnterAndAttribute(JCTree copy, Env<AttrContext> env, Context context) { MemberEnter memberEnter = MemberEnter.instance(context); Env<AttrContext> oldEnv = getEnvOfMemberEnter(memberEnter); setEnvOfMemberEnter(memberEnter, env); try { copy.accept(memberEnter); } catch (Exception ignore) { // intentionally ignored; usually even if this step fails, val will work (but not for val in method local inner classes and anonymous inner classes). AssertionLogger.assertLog("member enter failed.", ignore); } finally { setEnvOfMemberEnter(memberEnter, oldEnv); } attrib(copy, env); } public void resolveClassMember(JavacNode node) { ArrayDeque<JCTree> stack = new ArrayDeque<JCTree>(); { JavacNode n = node; while (n != null) { stack.push(n.get()); n = n.up(); } } messageSuppressor.disableLoggers(); try { EnvFinder finder = new EnvFinder(node.getContext()); while (!stack.isEmpty()) stack.pop().accept(finder); attrib(node.get(), finder.get()); } finally { messageSuppressor.enableLoggers(); } } private void attrib(JCTree tree, Env<AttrContext> env) { if (tree instanceof JCBlock) attr.attribStat(tree, env); else if (tree instanceof JCMethodDecl) attr.attribStat(((JCMethodDecl)tree).body, env); else if (tree instanceof JCVariableDecl) attr.attribStat(tree, env); else throw new IllegalStateException("Called with something that isn't a block, method decl, or variable decl"); } public static class TypeNotConvertibleException extends Exception { public TypeNotConvertibleException(String msg) { super(msg); } } private static class ReflectiveAccess { private static Method UPPER_BOUND; static { Method upperBound = null; try { upperBound = Types.class.getMethod("upperBound", Type.class); } catch (Throwable ignore) {} if (upperBound == null) try { upperBound = Types.class.getMethod("wildUpperBound", Type.class); } catch (Throwable ignore) {} UPPER_BOUND = upperBound; } public static Type Types_upperBound(Types types, Type type) { try { return (Type) UPPER_BOUND.invoke(types, type); } catch (InvocationTargetException e) { throw Lombok.sneakyThrow(e.getCause()); } catch (Exception e) { throw Lombok.sneakyThrow(e); } } } public static Type ifTypeIsIterableToComponent(Type type, JavacAST ast) { Types types = Types.instance(ast.getContext()); Symtab syms = Symtab.instance(ast.getContext()); Type boundType = ReflectiveAccess.Types_upperBound(types, type); // Type boundType = types.upperBound(type); Type elemTypeIfArray = types.elemtype(boundType); if (elemTypeIfArray != null) return elemTypeIfArray; Type base = types.asSuper(boundType, syms.iterableType.tsym); if (base == null) return syms.objectType; List<Type> iterableParams = base.allparams(); return iterableParams.isEmpty() ? syms.objectType : ReflectiveAccess.Types_upperBound(types, iterableParams.head); } public static JCExpression typeToJCTree(Type type, JavacAST ast, boolean allowVoid) throws TypeNotConvertibleException { return typeToJCTree(type, ast, false, allowVoid); } public static JCExpression createJavaLangObject(JavacAST ast) { JavacTreeMaker maker = ast.getTreeMaker(); JCExpression out = maker.Ident(ast.toName("java")); out = maker.Select(out, ast.toName("lang")); out = maker.Select(out, ast.toName("Object")); return out; } private static JCExpression typeToJCTree(Type type, JavacAST ast, boolean allowCompound, boolean allowVoid) throws TypeNotConvertibleException { int dims = 0; Type type0 = type; while (type0 instanceof ArrayType) { dims++; type0 = ((ArrayType) type0).elemtype; } JCExpression result = typeToJCTree0(type0, ast, allowCompound, allowVoid); while (dims > 0) { result = ast.getTreeMaker().TypeArray(result); dims--; } return result; } private static JCExpression typeToJCTree0(Type type, JavacAST ast, boolean allowCompound, boolean allowVoid) throws TypeNotConvertibleException { // NB: There's such a thing as maker.Type(type), but this doesn't work very well; it screws up anonymous classes, captures, and adds an extra prefix dot for some reason too. // -- so we write our own take on that here. JavacTreeMaker maker = ast.getTreeMaker(); if (CTC_BOT.equals(typeTag(type))) return createJavaLangObject(ast); if (CTC_VOID.equals(typeTag(type))) return allowVoid ? primitiveToJCTree(type.getKind(), maker) : createJavaLangObject(ast); if (type.isPrimitive()) return primitiveToJCTree(type.getKind(), maker); if (type.isErroneous()) throw new TypeNotConvertibleException("Type cannot be resolved"); TypeSymbol symbol = type.asElement(); List<Type> generics = type.getTypeArguments(); JCExpression replacement = null; if (symbol == null) throw new TypeNotConvertibleException("Null or compound type"); if (symbol.name.length() == 0) { // Anonymous inner class if (type instanceof ClassType) { List<Type> ifaces = ((ClassType) type).interfaces_field; Type supertype = ((ClassType) type).supertype_field; if (ifaces != null && ifaces.length() == 1) { return typeToJCTree(ifaces.get(0), ast, allowCompound, allowVoid); } if (supertype != null) return typeToJCTree(supertype, ast, allowCompound, allowVoid); } throw new TypeNotConvertibleException("Anonymous inner class"); } if (type instanceof CapturedType || type instanceof WildcardType) { Type lower, upper; if (type instanceof WildcardType) { upper = ((WildcardType)type).getExtendsBound(); lower = ((WildcardType)type).getSuperBound(); } else { lower = type.getLowerBound(); upper = type.getUpperBound(); } if (allowCompound) { if (lower == null || CTC_BOT.equals(typeTag(lower))) { if (upper == null || upper.toString().equals("java.lang.Object")) { return maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); } if (upper.getTypeArguments().contains(type)) { return maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); } return maker.Wildcard(maker.TypeBoundKind(BoundKind.EXTENDS), typeToJCTree(upper, ast, false, false)); } else { return maker.Wildcard(maker.TypeBoundKind(BoundKind.SUPER), typeToJCTree(lower, ast, false, false)); } } if (upper != null) { if (upper.getTypeArguments().contains(type)) { return maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); } return typeToJCTree(upper, ast, allowCompound, allowVoid); } return createJavaLangObject(ast); } String qName; if (symbol.isLocal()) { qName = symbol.getSimpleName().toString(); } else if (symbol.type != null && symbol.type.getEnclosingType() != null && typeTag(symbol.type.getEnclosingType()).equals(typeTag("CLASS"))) { replacement = typeToJCTree0(type.getEnclosingType(), ast, false, false); qName = symbol.getSimpleName().toString(); } else { qName = symbol.getQualifiedName().toString(); } if (qName.isEmpty()) throw new TypeNotConvertibleException("unknown type"); if (qName.startsWith("<")) throw new TypeNotConvertibleException(qName); String[] baseNames = qName.split("\\."); int i = 0; if (replacement == null) { replacement = maker.Ident(ast.toName(baseNames[0])); i = 1; } for (; i < baseNames.length; i++) { replacement = maker.Select(replacement, ast.toName(baseNames[i])); } return genericsToJCTreeNodes(generics, ast, replacement); } private static JCExpression genericsToJCTreeNodes(List<Type> generics, JavacAST ast, JCExpression rawTypeNode) throws TypeNotConvertibleException { if (generics != null && !generics.isEmpty()) { ListBuffer<JCExpression> args = new ListBuffer<JCExpression>(); for (Type t : generics) args.append(typeToJCTree(t, ast, true, false)); return ast.getTreeMaker().TypeApply(rawTypeNode, args.toList()); } return rawTypeNode; } private static JCExpression primitiveToJCTree(TypeKind kind, JavacTreeMaker maker) throws TypeNotConvertibleException { switch (kind) { case BYTE: return maker.TypeIdent(CTC_BYTE); case CHAR: return maker.TypeIdent( CTC_CHAR); case SHORT: return maker.TypeIdent(CTC_SHORT); case INT: return maker.TypeIdent(CTC_INT); case LONG: return maker.TypeIdent(CTC_LONG); case FLOAT: return maker.TypeIdent(CTC_FLOAT); case DOUBLE: return maker.TypeIdent(CTC_DOUBLE); case BOOLEAN: return maker.TypeIdent(CTC_BOOLEAN); case VOID: return maker.TypeIdent(CTC_VOID); case NULL: case NONE: case OTHER: default: throw new TypeNotConvertibleException("Nulltype"); } } public static boolean platformHasTargetTyping() { return Javac.getJavaCompilerVersion() >= 8; } }