package edu.ucsd.arcum.interpreter.query; import static com.google.common.base.ReferenceType.WEAK; import static edu.ucsd.arcum.ArcumPlugin.DEBUG; import static edu.ucsd.arcum.interpreter.ast.FormalParameter.getIdentifier; import static edu.ucsd.arcum.interpreter.query.EntityTuple.values; import static edu.ucsd.arcum.util.Pair.newPair; import java.lang.reflect.Method; import java.util.*; import java.util.Map.Entry; import org.eclipse.core.resources.IProject; import org.eclipse.jdt.core.dom.*; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.ReferenceMap; import com.google.common.collect.Sets; import edu.ucsd.arcum.exceptions.ArcumError; import edu.ucsd.arcum.interpreter.ast.ASTUtil; import edu.ucsd.arcum.interpreter.ast.FormalParameter; import edu.ucsd.arcum.interpreter.ast.TraitSignature; import edu.ucsd.arcum.interpreter.ast.expressions.BuiltInFunction; import edu.ucsd.arcum.interpreter.ast.expressions.PatternExpression; import edu.ucsd.arcum.interpreter.fragments.*; import edu.ucsd.arcum.interpreter.parser.ASTVisitorAdaptor; import edu.ucsd.arcum.interpreter.satisfier.BindingMap; import edu.ucsd.arcum.interpreter.satisfier.BindingsSet; import edu.ucsd.arcum.interpreter.satisfier.MaskedLookup; import edu.ucsd.arcum.interpreter.satisfier.TypeLookupTable; import edu.ucsd.arcum.util.*; public class EntityDataBase { public static final Map<String, TraitSignature> BUILT_IN_TRAIT_TYPES; public static final String PARENT_VAR_REF = "parent"; public static final String CHILD_VAR_REF = "child"; public static final String SUPER_CLASS_VAR_REF = "superclass"; public static final String SUB_CLASS_VAR_REF = "subclass"; public static final String EXPR_VAR_REF = "expression"; public static final String EXPR2_VAR_REF = "expression2"; public static final String METHOD_VAR_REF = "method"; public static final String DECLARATION_VAR_REF = "declaration"; private static final Set<String> BUILT_IN_TRAIT_NAMES; private static final Set<String> ALL_BUILT_IN_NAMES; private static final DynamicScope<EntityDataBase> currentEDB = DynamicScope .newInstance(); private static final Map<Class<?>, EntityType> entityTypeTable; static { entityTypeTable = Maps.newHashMap(); List<TraitSignature> entries = Lists.newArrayList(); // hasField({Class, Type}, Field) entries.add(TraitSignature.makeBuiltIn("hasField", new FormalParameter( EntityType.TYPE, PARENT_VAR_REF), new FormalParameter(EntityType.FIELD, CHILD_VAR_REF))); // hasMethod({Class, Type}, Method) entries.add(TraitSignature.makeBuiltIn("hasMethod", new FormalParameter( EntityType.TYPE, PARENT_VAR_REF), new FormalParameter(EntityType.METHOD, CHILD_VAR_REF))); // hasAnnotation({Type+,Method,Field,DeclarationElement}, Annotation) entries.add(TraitSignature.makeBuiltIn("hasAnnotation", new FormalParameter( EntityType.ANY, PARENT_VAR_REF), new FormalParameter(EntityType.ANNOTATION, CHILD_VAR_REF))); // invokes({Expr, Method}, Method) entries.add(TraitSignature.makeBuiltIn("invokes", new FormalParameter( EntityType.EXPR, EXPR_VAR_REF), new FormalParameter(EntityType.METHOD, METHOD_VAR_REF))); // hasInvocationTarget(Expr, Expr) entries.add(TraitSignature.makeBuiltIn("hasInvocationTarget", new FormalParameter( EntityType.EXPR, EXPR_VAR_REF), new FormalParameter(EntityType.EXPR, EXPR2_VAR_REF))); // declaredBy(Expr, DeclarationElement) entries.add(TraitSignature.makeBuiltIn("declaredBy", new FormalParameter( EntityType.EXPR, EXPR_VAR_REF), new FormalParameter( EntityType.DECLARATION_ELEMENT, DECLARATION_VAR_REF))); // copiedTo(Expr, DeclarationElement) entries.add(TraitSignature.makeBuiltIn("copiedTo", new FormalParameter( EntityType.EXPR, EXPR_VAR_REF), new FormalParameter( EntityType.DECLARATION_ELEMENT, DECLARATION_VAR_REF))); // superclassOf(Class, Class) entries.add(TraitSignature.makeBuiltIn("superclassOf", new FormalParameter( EntityType.TYPE, SUPER_CLASS_VAR_REF), new FormalParameter(EntityType.TYPE, SUB_CLASS_VAR_REF))); BUILT_IN_TRAIT_TYPES = Maps.newHashMap(); for (TraitSignature entry : entries) { BUILT_IN_TRAIT_TYPES.put(entry.getName(), entry); } BUILT_IN_TRAIT_NAMES = Sets.immutableSet(BUILT_IN_TRAIT_TYPES.keySet()); Set<String> allNames = new HashSet<String>(); allNames.addAll(BUILT_IN_TRAIT_NAMES); allNames.addAll(BuiltInFunction.getBuiltInNames()); ALL_BUILT_IN_NAMES = Sets.immutableSet(allNames); } public static Set<String> getBuiltInTraitAndPredicateNames() { return ALL_BUILT_IN_NAMES; } private static final String PROGRESS_MESSAGE = "Done once. Later analyses are performed incrementally."; private static final EntityType[] TRACKED_TYPES = new EntityType[] { EntityType.ACCESS_SPECIFIER, EntityType.ANNOTATION, EntityType.DECLARATION_ELEMENT, EntityType.EXPR, EntityType.FIELD, EntityType.METHOD, EntityType.MODIFIERS, EntityType.PACKAGE, EntityType.SIGNATURE, EntityType.STATEMENT, EntityType.TYPE, }; private final ASTTraverseTable traverseTable; private final ProjectTraverser projectTraverser; private final Map<EntityType, Collection<ASTNode>> astNodeStorage; private final Map<EntityType, Collection<ITypeBinding>> typeBindingStorage; private final Map<EntityType, Collection<ISynthesizedEntity>> synthesizedStorage; private final Map<ASTNode, ASTNode> desugaredToNearestNode; private final Map<ASTNode, ASTNode> pseudoParentTable; private final MultiDictionary<BindingKeyValue, MethodInvocation> methodInvocations; private final List<Expression> invocationsAndNames; private final List<Assignment> assignments; private final List<Pair<Expression, ? extends IBinding>> initializers; private final List<Expression> argumentsPassed; private final List<Expression> valuesReturned; private final Map<String, AbstractTypeDeclaration> typeDefinitionKeyLookup; private final Map<String, MethodDeclaration> methodBindingKeyLookup; public EntityDataBase(IProject project) { this.traverseTable = new ASTTraverseTable(); this.projectTraverser = new ProjectTraverser(project, PROGRESS_MESSAGE); this.astNodeStorage = newEntityTypeMap(); this.typeBindingStorage = newEntityTypeMap(); this.synthesizedStorage = newEntityTypeMap(); this.desugaredToNearestNode = new ReferenceMap<ASTNode, ASTNode>(WEAK, WEAK); this.pseudoParentTable = new ReferenceMap<ASTNode, ASTNode>(WEAK, WEAK); this.methodInvocations = MultiDictionary.newInstance(); this.invocationsAndNames = Lists.newArrayList(); this.assignments = Lists.newArrayList(); this.initializers = Lists.newArrayList(); this.argumentsPassed = Lists.newArrayList(); this.valuesReturned = Lists.newArrayList(); this.typeDefinitionKeyLookup = Maps.newHashMap(); this.methodBindingKeyLookup = Maps.newHashMap(); // We need to keep only one unique instance of each package found; we will // need to avoid the creation of packages for the moment, because renaming // packages or moving them is not a short to medium term term goal for (EntityType type : TRACKED_TYPES) { astNodeStorage.put(type, new ArrayList<ASTNode>()); typeBindingStorage.put(type, new ArrayList<ITypeBinding>()); synthesizedStorage.put(type, new ArrayList<ISynthesizedEntity>()); } } private static <T> Map<EntityType, T> newEntityTypeMap() { return new EnumMap<EntityType, T>(EntityType.class); } public void populate() { projectTraverser.runTraversal(new ProjectTraverser.ICompilationUnitVisitor() { public @Override void visitCompilationUnit(CompilationUnit compilationUnit) { try { EntityDataBase.pushCurrentDataBase(EntityDataBase.this); IASTVisitor visitor = new EntityDataBaseVisitor(); traverseTable.traverseAST(compilationUnit, visitor); } finally { EntityDataBase.popMostRecentDataBase(); } } }); if (false && DEBUG) { System.out.printf("All types used:%n"); for (ITypeBinding typeBinding : typeBindingStorage.get(EntityType.TYPE)) { System.out.printf("%s%n", Entity.getQualifiedName(typeBinding)); } System.out.printf("All types defined:%n"); for (ASTNode node : astNodeStorage.get(EntityType.TYPE)) { System.out.printf("%s%n", Entity.getDisplayString(node)); } } } public BindingsSet immeditateMatchingBinding(PatternExpression patternExpr, EntityType type, BindingMap in, IEntityLookup lookup, TypeLookupTable types, Object entity) { try { EntityDataBase.pushCurrentDataBase(this); final ProgramFragmentFactory builder; final List<ProgramFragment> fragments; Set<String> vars = patternExpr.getArcumVariableReferences(); IEntityLookup masked = new MaskedLookup(lookup, vars); builder = new ProgramFragmentFactory(patternExpr, type, masked, types, true); fragments = builder.getAbstractProgramFragments(); BindingsSet result = BindingsSet.newEmptySet(); eachFragment: for (ProgramFragment fragment : fragments) { BindingMap theta = fragment.matches(entity); if (theta == null) { continue eachFragment; } boolean allVarsMatch = true; varsMatch: for (String var : vars) { if (var.equals(ArcumDeclarationTable.SPECIAL_ANY_VARIABLE)) { continue varsMatch; } Object startedWith = in.lookupEntity(var); if (startedWith != null) { Object found = theta.lookupEntity(var); if (DEBUG) { System.out.printf("Compare: %s with %s%n", //-> ASTUtil.getDebugString(startedWith), //-> ASTUtil.getDebugString(found)); } if (startedWith != found) { allVarsMatch = false; } } } if (allVarsMatch) { BindingMap merge = theta.consistentMerge(in); if (merge != null) { result.addEntry(merge); } } } return result; } finally { popMostRecentDataBase(); } } public BindingsSet enumerateMatchingBindings(PatternExpression patternExpr, EntityType type, BindingMap in, IEntityLookup lookup, TypeLookupTable types) { try { EntityDataBase.pushCurrentDataBase(this); final ProgramFragmentFactory builder; final List<ProgramFragment> fragments; builder = new ProgramFragmentFactory(patternExpr, type, lookup, types, true); fragments = builder.getAbstractProgramFragments(); BindingsSet literalMatches = immediateLiteralMatches(type, in, lookup, fragments); if (literalMatches != null) { return literalMatches; } if (EntityType.TYPE.isAssignableFrom(type)) { Collection<ITypeBinding> typeBindings = typeBindingStorage.get(type); return entitySearch(typeBindings, fragments, in); } else if (EntityType.SIGNATURE.isAssignableFrom(type)) { Collection<ISynthesizedEntity> sigs = synthesizedStorage.get(type); return entitySearch(sigs, fragments, in); } else if (EntityType.MODIFIERS.isAssignableFrom(type)) { Collection<ISynthesizedEntity> lists = synthesizedStorage.get(type); ArcumError.fatalError("There's a use case for this %s?", lists); return null; } else { Collection<ASTNode> astNodes = astNodeStorage.get(type); return entitySearch(astNodes, fragments, in); } } finally { popMostRecentDataBase(); } } // EXAMPLE: These two push/pop methods are examples of a @StackWinding idiom, // where each block that the push is called must have a finally at the end that // calls the unwind operation. Naturally, this limits how you can use the API, // but that's the point. TODO: statement matching for this kind of thing, and // in the bigger picture we need to think about how several of these would // compose (e.g., for the moment each one used would require a new try/finally, // potentially nested in ones that already exist). public static void pushCurrentDataBase(EntityDataBase entityDataBase) { currentEDB.push(entityDataBase); } public static void popMostRecentDataBase() { currentEDB.pop(); } // If the fragments are already resolved then we don't need to search for them: // we can just return them as is. private BindingsSet immediateLiteralMatches(EntityType type, BindingMap in, IEntityLookup lookup, List<ProgramFragment> fragments) { if (fragments.size() != 1) { return null; } ProgramFragment fragment = fragments.get(0); if (EntityType.TYPE.isAssignableFrom(type)) { if (fragment instanceof ResolvedType) { ResolvedType resolvedType = (ResolvedType)fragment; BindingMap theta; theta = resolvedType.matches(resolvedType.getTypeBinding()); if (theta != null) { BindingMap merge = theta.consistentMerge(in); if (merge != null) { return BindingsSet.newSet(merge); } } } } else if (EntityType.SIGNATURE.isAssignableFrom(type)) { if (fragment instanceof PartialNode) { AST ast = AST.newAST(AST.JLS3); BindingMap node = fragment.generateNode(lookup, ast); MethodDeclaration methodDecl = (MethodDeclaration)node.getResult(); SignatureEntity entity = new SignatureEntity(methodDecl); BindingMap merge = new BindingMap(entity); merge = merge.consistentMerge(in); return BindingsSet.newSet(merge); } } return null; } private <T> BindingsSet entitySearch(Collection<T> entities, Collection<ProgramFragment> fragments, BindingMap in) { final BindingsSet result = BindingsSet.newEmptySet(); entitySearch: for (T entity : entities) { for (ProgramFragment fragment : fragments) { BindingMap theta = fragment.matches(entity); if (theta != null) { BindingMap merge = theta.consistentMerge(in); if (merge != null) { result.addEntry(merge); continue entitySearch; } } } } return result; } // May return null public AbstractTypeDeclaration lookupTypeDeclaration(ITypeBinding givenBinding) { String key = givenBinding.getKey(); AbstractTypeDeclaration typeDecl = typeDefinitionKeyLookup.get(key); return typeDecl; } public static AbstractTypeDeclaration findTypeDeclaration(ITypeBinding givenBinding) { EntityDataBase edb = currentEDB.peek(); return edb.lookupTypeDeclaration(givenBinding); } public ITypeBinding lookupTypeBinding(AbstractTypeDeclaration node) { ITypeBinding result = resolveBinding(node); return result; } // Collect a database of program fragments seen, with some canonicalization // applied to them in order to simplify matching. E.g., // static final int a = 1, b[] = null, c[][]; // gets turned into three entries: // static final int a = 1; // static final int[] b = null; // static final int[][] c; // And, // int a[]; // gets turned into: // int[] a; private class EntityDataBaseVisitor extends ASTVisitorAdaptor { @Override public boolean visitASTNode(ASTNode node, StructuralPropertyDescriptor edge) { if (node == null) { return false; } else if (node instanceof PackageDeclaration) { return handlePackageDeclaration((PackageDeclaration)node); } else if (node instanceof ImportDeclaration) { return handleImportDeclaration((ImportDeclaration)node); } else if (node instanceof Modifier) { return handleModifier((Modifier)node, edge); } else if (node instanceof Annotation) { return handleAnnotation((Annotation)node); } else if (node instanceof AbstractTypeDeclaration) { return handleTypeDeclaration((AbstractTypeDeclaration)node); } else if (node instanceof VariableDeclaration) { return handleVariableDeclaration((VariableDeclaration)node); } else if (node instanceof VariableDeclarationStatement) { return handleVariableDeclarationStatement((VariableDeclarationStatement)node); } else if (node instanceof MethodInvocation) { return handleMethodInvocation((MethodInvocation)node); } else if (node instanceof Expression) { return handleExpression((Expression)node); } else if (node instanceof FieldDeclaration) { return handleFieldDeclaration((FieldDeclaration)node); } else if (node instanceof MethodDeclaration) { return handleMethod((MethodDeclaration)node); } else if (node instanceof Statement) { return handleStatement(node); } else if (node instanceof Initializer) { return handleInitializer((Initializer)node); } return true; } private boolean handleImportDeclaration(ImportDeclaration node) { // We don't handle or match against imports: The idea being that we // desugar over uses of them. return false; } private boolean handleAnnotation(Annotation node) { // ANNOTATION storeASTNode(EntityType.ANNOTATION, node); // MONDAY: Annotations can be embedded in other annotations and we // may not always be matching or generating the top level one. return false; } private boolean handleTypeDeclaration(AbstractTypeDeclaration atd) { // TYPE ITypeBinding binding = atd.resolveBinding(); typeDefinitionKeyLookup.put(binding.getKey(), atd); storeASTNode(EntityType.TYPE, atd); storeTypeBinding(binding); return true; } // Handle a single, local variable private boolean handleVariableDeclaration(VariableDeclaration node) { // DECLARATION_ELEMENT (part 1: formals) int dims = node.getExtraDimensions(); if (dims == 0) { // Just visit initializer expression, if present final Expression initializer; if (node instanceof SingleVariableDeclaration) { SingleVariableDeclaration decl = (SingleVariableDeclaration)node; initializer = decl.getInitializer(); } else if (node instanceof VariableDeclarationFragment) { VariableDeclarationFragment fragment = (VariableDeclarationFragment)node; initializer = fragment.getInitializer(); } else { initializer = null; } if (initializer != null) { reentrantVisit(initializer); } } else { // then we need to desugar the array AST ast = node.getAST(); SimpleName name = node.getName(); if (node instanceof SingleVariableDeclaration) { SingleVariableDeclaration decl = (SingleVariableDeclaration)node; Type baseType = decl.getType(); List modsAndAnnots = decl.modifiers(); SingleVariableDeclaration svd = ast.newSingleVariableDeclaration(); svd.setType(createDerivedType(ast, baseType, dims)); svd.setName(Entity.copySubtree(ast, name)); Expression initializer = decl.getInitializer(); if (initializer != null) { svd.setInitializer(Entity.copySubtree(ast, initializer)); reentrantVisit(initializer); } svd.modifiers().addAll(modsAndAnnots); node = svd; } else if (node instanceof VariableDeclarationFragment) { VariableDeclarationFragment fragment = (VariableDeclarationFragment)node; node = duplicateFragment(ast, fragment); } } // we must get the type binding from the name, in order to pick // up any extra dimensions if they exist storeTypeBindingFromName(node.getName()); for (Annotation annotation : ASTUtil.getAnnotations(node)) { handleAnnotation(annotation); associateNodeToPseudoParent(annotation, node); } associateBindingToDeclarationElement(node.resolveBinding(), node); storeASTNode(EntityType.DECLARATION_ELEMENT, node); handleVariableInitialization(node); return false; } private boolean handleVariableDeclarationStatement(VariableDeclarationStatement varDeclStmt) { // DECLARATION_ELEMENT (part 2: locals) AST ast = varDeclStmt.getAST(); List fragments = varDeclStmt.fragments(); Type baseType = varDeclStmt.getType(); List<Annotation> annotations = ASTUtil.getAnnotations(varDeclStmt); for (Annotation annotation : annotations) { handleAnnotation(annotation); } boolean needToDesugar = true; if (fragments.size() == 1) { needToDesugar = false; for (Annotation annotation : annotations) { associateNodeToPseudoParent(annotation, varDeclStmt); } VariableDeclarationFragment frag = (VariableDeclarationFragment)fragments .get(0); associateBindingToDeclarationElement(frag.resolveBinding(), varDeclStmt); storeASTNode(EntityType.DECLARATION_ELEMENT, varDeclStmt); handleVariableInitialization(varDeclStmt); } // There may be several variable declarations here, but all will // share the same modifiers and base type (but some may be array // types derived from the base type) -- canonicalize by making // a new VariableDeclarationStatement for each for (Object obj : fragments) { VariableDeclarationFragment frag = (VariableDeclarationFragment)obj; storeTypeBindingFromName(frag.getName()); if (needToDesugar) { VariableDeclarationFragment newFrag = duplicateFragment(ast, frag); VariableDeclarationStatement newStmt; newStmt = ast.newVariableDeclarationStatement(newFrag); newStmt.setType(createDerivedType(ast, baseType, frag .getExtraDimensions())); List modifiers = ASTNode.copySubtrees(ast, varDeclStmt.modifiers()); newStmt.modifiers().addAll(modifiers); associateBindingToDeclarationElement(frag.resolveBinding(), newStmt); ASTUtil.recordUpdatedNode(varDeclStmt, newStmt); storeDesugaredASTNode(EntityType.DECLARATION_ELEMENT, newStmt, frag); handleVariableInitialization(frag); for (Annotation annotation : annotations) { associateNodeToPseudoParent(annotation, newStmt); } } else { Expression initializer = frag.getInitializer(); if (initializer != null) { reentrantVisit(initializer); } } } // TUESDAY: Lots of other cases for DECLARATION_ELEMENT, like return types, // typecasts. But what about super classes, super interfaces, // throws clauses, etc...? return false; } private boolean handleMethodInvocation(MethodInvocation invocation) { storeASTNode(EntityType.EXPR, invocation); Expression targetExpr = invocation.getExpression(); if (targetExpr != null) { reentrantVisit(targetExpr); } else { // VERSION2: We need to create a desugared "this" or class name // as the target and add it: however, the entire method invocation // expression must match, so adding this alone won't fix everything // unless we change the method invocation itself: a major change that // could greatly increase (close to double due to method bodies?) // the size of the AST -- an alternative solution is to abstract // away everything we need from the AST: E.g. keep a text copy of // each source file visited and for each entity we need only a // reference to the file's name, the starting position, the length, // and the resolved type of it (if applicable). Ideally we can // represent resolved types in a way lends to easy comparison (like // is-A) without having to keep the type resolutions of any of the // ASTs around. } List arguments = invocation.arguments(); for (Object obj : arguments) { Expression argument = (Expression)obj; reentrantVisit(argument); argumentsPassed.add(argument); } // MONDAY: Also need to check ClassInstanceCreation, SuperMethodInvocation, // and potentially other ways to invoke methods IMethodBinding binding = invocation.resolveMethodBinding(); BindingKeyValue key = BindingKeyValue.newInstance(EntityType.METHOD, binding); methodInvocations.addDefinition(key, invocation); invocationsAndNames.add(invocation); return false; } private boolean handleExpression(Expression node) { // EXPR storeASTNode(EntityType.EXPR, node); if (node instanceof Assignment) { Assignment assignment = (Assignment)node; assignments.add(assignment); } if (node instanceof Name) { Name name = (Name)node; IBinding binding = resolveBindingNullOK(name); if (binding != null) { if (binding.getKind() == IBinding.VARIABLE) { invocationsAndNames.add(name); } } else { if (!ASTUtil.isLabel(name)) { ArcumError.fatalError("Assertion failed: A non-label is a name" + " without a binding (%s in %s)", //-> StringUtil.debugDisplay(name), //-> StringUtil.debugDisplay(name.getParent())); } } } if (node instanceof QualifiedName) { // E.g., The aim is for expressions like: // treeNode.left.left.right.left = null // to be entered as one field assignment and three field accesses. // MACNEIL: These may need to be ensugared as FieldAccess nodes, when // applicable. Right now, they are ensugared at a later point QualifiedName qualifiedName = (QualifiedName)node; Name qualifier = qualifiedName.getQualifier(); if (qualifier instanceof QualifiedName) { IBinding binding = qualifier.resolveBinding(); if (binding.getKind() == IBinding.VARIABLE) { IVariableBinding var = (IVariableBinding)binding; if (var.isField()) { handleExpression(qualifier); } } } return false; } else if (node instanceof FieldAccess) { FieldAccess fieldAccess = (FieldAccess)node; Expression expression = fieldAccess.getExpression(); reentrantVisit(expression); invocationsAndNames.add(fieldAccess); return false; } return true; } private boolean handleFieldDeclaration(final FieldDeclaration fieldDecl) { // FIELD, and DECLARATION_ELEMENT (part 3: fields) AST ast = fieldDecl.getAST(); List fragments = fieldDecl.fragments(); Type baseType = fieldDecl.getType(); boolean needToDesugar = true; if (fragments.size() == 1) { needToDesugar = false; VariableDeclarationFragment frag = (VariableDeclarationFragment)fragments .get(0); associateBindingToDeclarationElement(frag.resolveBinding(), fieldDecl); storeASTNode(EntityType.DECLARATION_ELEMENT, fieldDecl); storeASTNode(EntityType.FIELD, fieldDecl); handleVariableInitialization(fieldDecl); } // As with the VariableDeclarationStatement case, which has similar // code, there may be several declarations here for (Object obj : fragments) { VariableDeclarationFragment frag = (VariableDeclarationFragment)obj; storeTypeBindingFromName(frag.getName()); if (needToDesugar) { VariableDeclarationFragment newFrag = duplicateFragment(ast, frag); FieldDeclaration newFieldDecl; newFieldDecl = ast.newFieldDeclaration(newFrag); int extraDimensions = frag.getExtraDimensions(); Type derivedType = createDerivedType(ast, baseType, extraDimensions); newFieldDecl.setType(derivedType); // we need a fresh copy of the modifiers for each newFieldDecl List modifiers = ASTNode.copySubtrees(ast, fieldDecl.modifiers()); newFieldDecl.modifiers().addAll(modifiers); associateBindingToDeclarationElement(frag.resolveBinding(), newFieldDecl); ASTUtil.recordUpdatedNode(fieldDecl, newFieldDecl); storeDesugaredASTNode(EntityType.DECLARATION_ELEMENT, newFieldDecl, frag); storeDesugaredASTNode(EntityType.FIELD, newFieldDecl, frag); handleVariableInitialization(frag); } else { Expression initializer = frag.getInitializer(); if (initializer != null) { reentrantVisit(initializer); } } } return false; } // METHOD private boolean handleMethod(MethodDeclaration methodDecl) { // TODO: Desugar extra array dimensions storeASTNode(EntityType.METHOD, methodDecl); storeSignatureEntity(EntityType.SIGNATURE, new SignatureEntity(methodDecl)); IMethodBinding methodBinding = methodDecl.resolveBinding(); methodBindingKeyLookup.put(methodBinding.getKey(), methodDecl); Type returnType = methodDecl.getReturnType2(); if (returnType != null) { associateBindingToDeclarationElement(methodDecl.resolveBinding(), returnType); storeASTNode(EntityType.DECLARATION_ELEMENT, returnType); for (Annotation annotation : ASTUtil.getAnnotations(methodDecl)) { associateNodeToPseudoParent(annotation, returnType); } } return true; } // PACKAGE private boolean handlePackageDeclaration(PackageDeclaration node) { // Some packages may be missing if there are no Java source files // directly defined in them (e.g., when it has sub packages). This // is a low priority concern, however PackageDeclaration packageDeclaration = (PackageDeclaration)node; storeASTNode(EntityType.PACKAGE, packageDeclaration); List annotations = packageDeclaration.annotations(); // don't look further down, otherwise the qualified name in the // package name will be treated like an expression, which it isn't // -- instead, grab the annotations directly for (Object obj : annotations) { Annotation annotation = (Annotation)obj; visitASTNode(annotation, PackageDeclaration.ANNOTATIONS_PROPERTY); } return false; } private boolean handleInitializer(Initializer node) { // MACNEIL: WE SKIP THESE FOR NOW! return false; } // STATEMENT private boolean handleStatement(ASTNode node) { storeASTNode(EntityType.STATEMENT, node); if (node instanceof ReturnStatement) { ReturnStatement returnStmt = (ReturnStatement)node; Expression expression = returnStmt.getExpression(); if (expression != null) { valuesReturned.add(expression); } } return true; } // ACCESS_SPECIFIER and MODIFIERS private boolean handleModifier(Modifier node, StructuralPropertyDescriptor edge) { // These are handled on an edge basis as a unit, so we shouldn't // reach here unless there was an edge type that was forgotten ArcumError.fatalError("AND %s IS AN EDGE THAT HOLDS MODIFIERS%n", edge); return false; } @Override public boolean beforeVisitEdge(ASTNode parent, StructuralPropertyDescriptor edge) { if (Entity.isModifiersEdge(edge)) { List modsAndAnnots = (List)parent.getStructuralProperty(edge); EntityList accessSpecifier = EntityList.newModifiersList(); EntityList modifiersList = EntityList.newModifiersList(); for (Object modOrAnnot : modsAndAnnots) { if (modOrAnnot instanceof Annotation) { visitASTNode((Annotation)modOrAnnot, edge); } else if (modOrAnnot instanceof Modifier) { ModifierElement element; element = ModifierElement.lookup((Modifier)modOrAnnot); modifiersList.addEntity(element, null); if (element.isAccessSpecifier()) { accessSpecifier.addEntity(element, null); } // else { // modifiersList.addEntity(element); // modifiersList.addRequiredModifier(element); // hasNonAccessSpecModifier = true; // } } } if (accessSpecifier.isEmpty()) { accessSpecifier.addEntity(ModifierElement.MOD_PACKAGE, null); } storeModifiersList(EntityType.ACCESS_SPECIFIER, accessSpecifier); storeModifiersList(EntityType.MODIFIERS, modifiersList); // store(EntityType.ACCESS_SPECIFIER, accessSpecifier, parent); // if (hasNonAccessSpecModifier) { // store(EntityType.MODIFIERS, modifiersList, parent); // } return false; } else if (edge == TypeDeclaration.NAME_PROPERTY || edge == EnumDeclaration.NAME_PROPERTY || edge == AnnotationTypeDeclaration.NAME_PROPERTY || edge == MethodDeclaration.NAME_PROPERTY) { // don't traverse the name properly of a type or method declaration, // because it's not actually an expression return false; } return super.beforeVisitEdge(parent, edge); } // Given a base type and the array type implied by the given fragment, return // a derived type to be used instead private Type createDerivedType(AST ast, Type baseType, int dimensions) { Type newType = Entity.copySubtree(ast, baseType); for (int i = 0; i < dimensions; ++i) { newType = ast.newArrayType(newType); } return newType; } // Copy the given declaration fragment, but not its array dimensions. Also, // recursively visit the init expression, if present private VariableDeclarationFragment duplicateFragment(AST ast, VariableDeclarationFragment frag) { VariableDeclarationFragment newFrag = ast.newVariableDeclarationFragment(); SimpleName name = frag.getName(); name = ast.newSimpleName(name.getIdentifier()); newFrag.setName(name); Expression initializer = frag.getInitializer(); if (initializer != null) { initializer = Entity.copySubtree(ast, initializer); newFrag.setInitializer(initializer); reentrantVisit(initializer); } return newFrag; } // Fully traverse the given node. E.g., an init expression may have anonymous inner classes complete // with methods and fields and other code private void reentrantVisit(ASTNode node) { traverseTable.traverseAST(node, this); } // TODO: See if this would be useful anywhere else: Is the method typeof // semantics coded anywhere else? // // given a name it returns the type of the name, given a method, returns // // the return type of the method // private ITypeBinding getTypeBinding(IBinding binding) { // if (binding instanceof ITypeBinding) { // return (ITypeBinding)binding; // } // else if (binding instanceof IVariableBinding) { // IVariableBinding variableBinding = (IVariableBinding)binding; // return variableBinding.getType(); // } // else if (binding instanceof IMethodBinding) { // IMethodBinding methodBinding = (IMethodBinding)binding; // return methodBinding.getReturnType(); // } // return null; // } // Note: This may be called multiple times for the same ASTNode but using // different types. The most specific type should be used last. private void storeASTNode(EntityType type, ASTNode node) { entityTypeTable.put(node.getClass(), type); astNodeStorage.get(type).add(node); } private void storeTypeBinding(ITypeBinding typeBinding) { typeBindingStorage.get(EntityType.TYPE).add(typeBinding); } private void storeTypeBindingFromName(SimpleName name) { ITypeBinding typeBinding = Entity.getTypeOf(name); IBinding binding = name.resolveBinding(); if (binding == null && typeBinding == null) { System.err .printf("No information for %s in %s%n", name, name.getParent()); return; } storeTypeBinding(typeBinding); } private void storeDesugaredASTNode(EntityType type, ASTNode entityValue, ASTNode nearestNode) { storeASTNode(type, entityValue); desugaredToNearestNode.put(entityValue, nearestNode); } private void storeModifiersList(EntityType type, EntityList modifiersList) { synthesizedStorage.get(type).add(modifiersList); } private void storeSignatureEntity(EntityType type, SignatureEntity signatureEntity) { synthesizedStorage.get(type).add(signatureEntity); } } // inserts into the given table all trait values for the built-in predicates: // hasField, hasMethod public void insertBuiltInTraitValues(OptionMatchTable table) { try { pushCurrentDataBase(this); insertOwnerRelation(table, EntityType.FIELD, "hasField", astNodeStorage); insertOwnerRelation(table, EntityType.METHOD, "hasMethod", astNodeStorage); insertDirectParentOfRelation(table, EntityType.ANNOTATION, "hasAnnotation", astNodeStorage); insertInvokesRelation(table, methodInvocations); insertHasInvocationTargetRelation(table, methodInvocations); insertDeclaredByRelation(table); insertCopiedToRelation(table); // MACNEIL : If we consider all types in the program, and not all types // defined in the project, we should think of a different strategy, one which // may require a full search of the jars on the path for all matching types insertSuperclassRelation(table, astNodeStorage.get(EntityType.TYPE)); } finally { popMostRecentDataBase(); } } private <T> void insertOwnerRelation(OptionMatchTable table, EntityType entityType, String traitName, Map<EntityType, Collection<T>> lookupTable) { final TraitSignature type; final List<String> names; final Collection<T> nodes; type = BUILT_IN_TRAIT_TYPES.get(traitName); names = Lists.transform(type.getFormals(), FormalParameter.getIdentifier); nodes = lookupTable.get(entityType); table.addBuiltInTrait(type); for (T node : nodes) { ITypeBinding definingType = findDefiningType(node); Map<String, Object> values = values(names, definingType, node); EntityTuple instance = new EntityTuple(type, values, null); table.addTraitInstance(traitName, instance); } } private <T extends ASTNode> void insertDirectParentOfRelation(OptionMatchTable table, EntityType entityType, String traitName, Map<EntityType, Collection<T>> lookupTable) { final TraitSignature type; final List<String> names; final Collection<T> nodes; type = BUILT_IN_TRAIT_TYPES.get(traitName); names = Lists.transform(type.getFormals(), FormalParameter.getIdentifier); nodes = lookupTable.get(entityType); table.addBuiltInTrait(type); for (T node : nodes) { ASTNode parent = lookupPseudoParent(node); Map<String, Object> values = values(names, parent, node); EntityTuple instance = new EntityTuple(type, values, null); table.addTraitInstance(traitName, instance); if (DEBUG) { System.out.printf("hasAnnotation info: %s on %s%n", node, StringUtil .firstLine(parent.toString())); } } } private @ReadWriteAccess(@MethodGroup(type = EntityDataBase.class, names = { "associateBindingToDeclarationElement", "lookupDeclarationElement" })) final Map<String, Object> declarationElementLookup = Maps.newHashMap(); private void associateBindingToDeclarationElement(IBinding binding, @Union("Entity") Object declarationElement) { String key = binding.getKey(); declarationElementLookup.put(key, declarationElement); } private Object lookupDeclarationElement(IBinding binding) { if (binding instanceof IVariableBinding) { // work with the binding in the generic type instead of an instance of // the generic type binding = ((IVariableBinding)binding).getVariableDeclaration(); } String key = binding.getKey(); Object result = declarationElementLookup.get(key); return result; } private void associateNodeToPseudoParent(ASTNode node, ASTNode parent) { pseudoParentTable.put(node, parent); } private ASTNode lookupPseudoParent(ASTNode node) { if (pseudoParentTable.containsKey(node)) { return pseudoParentTable.get(node); } else { return node.getParent(); } } // TODO: Need to worry about method overriding too: The methodKey used should // actually be a set of method keys: That method itself, and all methods that // override it. private void insertInvokesRelation(OptionMatchTable table, MultiDictionary<BindingKeyValue, MethodInvocation> methodInvocations) { final String INVOKES = "invokes"; TraitSignature type = BUILT_IN_TRAIT_TYPES.get(INVOKES); List<String> names = Lists.transform(type.getFormals(), getIdentifier); table.addBuiltInTrait(type); for (Entry<BindingKeyValue, List<MethodInvocation>> keyedDefinitions : methodInvocations) { BindingKeyValue methodKey = keyedDefinitions.getKey(); for (MethodInvocation expr : keyedDefinitions.getValue()) { Map<String, Object> values = values(names, expr, methodKey); EntityTuple instance = new EntityTuple(type, values, null); table.addTraitInstance(INVOKES, instance); IMethodBinding caller = ASTUtil.getDefiningMethod(expr); if (caller != null) { BindingKeyValue callerKey = BindingKeyValue.newInstance( EntityType.METHOD, caller); values = values(names, callerKey, methodKey); instance = new EntityTuple(type, values, null); table.addTraitInstance(INVOKES, instance); } } } } private void insertHasInvocationTargetRelation(OptionMatchTable table, MultiDictionary<BindingKeyValue, MethodInvocation> methodInvocations) { final String HAS_INVOCATION_TARGET = "hasInvocationTarget"; TraitSignature type = BUILT_IN_TRAIT_TYPES.get(HAS_INVOCATION_TARGET); List<String> names = Lists.transform(type.getFormals(), getIdentifier); table.addBuiltInTrait(type); for (Entry<BindingKeyValue, List<MethodInvocation>> keyedDefinitions : methodInvocations) { for (MethodInvocation invocation : keyedDefinitions.getValue()) { Expression target = invocation.getExpression(); if (target == null) { target = invocation.getAST().newThisExpression(); } Map<String, Object> values = values(names, invocation, target); EntityTuple instance = new EntityTuple(type, values, null); table.addTraitInstance(HAS_INVOCATION_TARGET, instance); } } } // Where declarationReferences is a list of method invocation expressions and // all variable reference expressions (locals, parameters, fields). // TODO: implement ArrayAccess as a declaration reference, very similar to // accessing a field. However, fields can have annotations, while the type of // an array cannot. private void insertDeclaredByRelation(OptionMatchTable table) { final String DECLARED_BY = "declaredBy"; TraitSignature type = BUILT_IN_TRAIT_TYPES.get(DECLARED_BY); List<String> names = Lists.transform(type.getFormals(), getIdentifier); table.addBuiltInTrait(type); for (Expression reference : invocationsAndNames) { IBinding declarationBinding = getDeclarationBinding(reference); Object declarationElement = lookupDeclarationElement(declarationBinding); if (declarationElement != null) { Map<String, Object> values = values(names, reference, declarationElement); EntityTuple instance = new EntityTuple(type, values, null); table.addTraitInstance(DECLARED_BY, instance); } } } private void insertCopiedToRelation(OptionMatchTable table) { final String COPIED_TO = "copiedTo"; TraitSignature type = BUILT_IN_TRAIT_TYPES.get(COPIED_TO); List<String> names = Lists.transform(type.getFormals(), getIdentifier); List<Map<String, Object>> listOfValues = Lists.newArrayList(); table.addBuiltInTrait(type); for (Expression valueCopied : argumentsPassed) { MethodInvocation methodCall = (MethodInvocation)valueCopied.getParent(); String methodBindingKey = methodCall.resolveMethodBinding().getKey(); MethodDeclaration methodDecl = methodBindingKeyLookup.get(methodBindingKey); // if (valueCopied.toString().equals("stmtLookup")) { // System.out.printf("copiedTo %s %s%n", valueCopied, methodBindingKey); // for (String str : methodBindingKeyLookup.keySet()) { // System.out.printf("%s%n", str); // } // } if (methodDecl != null) { IBinding declarationBinding = findMatchingParameterDeclaration(valueCopied, methodDecl); Object declElement = lookupDeclarationElement(declarationBinding); listOfValues.add(values(names, valueCopied, declElement)); } } for (Assignment assignment : assignments) { Expression lhs = assignment.getLeftHandSide(); Expression valueCopied = assignment.getRightHandSide(); IBinding declarationBinding = getDeclarationBinding(lhs); Object declElement = lookupDeclarationElement(declarationBinding); if (declElement != null) { listOfValues.add(values(names, valueCopied, declElement)); } } for (Pair<Expression, ? extends IBinding> initializer : initializers) { Expression valueCopied = initializer.getFirst(); IBinding declarationBinding = initializer.getSecond(); Object declElement = lookupDeclarationElement(declarationBinding); listOfValues.add(values(names, valueCopied, declElement)); } for (Expression valueCopied : valuesReturned) { IMethodBinding methodBinding = ASTUtil.getDefiningMethod(valueCopied); Object declElement = lookupDeclarationElement(methodBinding); listOfValues.add(values(names, valueCopied, declElement)); } for (Map<String, Object> values : listOfValues) { EntityTuple instance = new EntityTuple(type, values, null); table.addTraitInstance(COPIED_TO, instance); } } private IBinding findMatchingParameterDeclaration(Expression argument, MethodDeclaration methodDecl) { StructuralPropertyDescriptor spd = argument.getLocationInParent(); ASTNode parent = argument.getParent(); List arguments = (List)parent.getStructuralProperty(spd); int location = 0; for (int i=0; i<arguments.size(); ++i) { if (arguments.get(i) == argument) { location = i; break; } } boolean isVarargs = methodDecl.isVarargs(); List parameters = methodDecl.parameters(); SingleVariableDeclaration parameter = null; boolean isInVarArgList = false; if (location >= parameters.size()) { if (!isVarargs) { ArcumError.fatalError("Funny internal error, should be var args"); } parameter = (SingleVariableDeclaration)parameters.get(parameters.size() - 1); isInVarArgList = true; } else { parameter = (SingleVariableDeclaration)parameters.get(location); } if (isInVarArgList) { // TODO: Need to work with the type 'Foo' directly, not 'Foo...' } return parameter.resolveBinding(); } private IBinding getDeclarationBinding(Expression expr) { IBinding declarationBinding; if (expr instanceof Name) { Name name = (Name)expr; IBinding binding = name.resolveBinding(); if (!(binding instanceof IVariableBinding)) ArcumError .fatalError("Not an instance of variable binding, time to rewrite"); IVariableBinding varBinding = (IVariableBinding)binding; declarationBinding = varBinding.getVariableDeclaration(); } else if (expr instanceof FieldAccess) { FieldAccess fieldAccess = (FieldAccess)expr; declarationBinding = fieldAccess.resolveFieldBinding(); } else if (expr instanceof ArrayAccess) { ArrayAccess arrayAccess = (ArrayAccess)expr; // WEDNESDAY: We un-array it for now, but this is not what we want to do declarationBinding = getDeclarationBinding(arrayAccess.getArray()); } else if (expr instanceof MethodInvocation) { MethodInvocation invocation = (MethodInvocation)expr; declarationBinding = invocation.resolveMethodBinding(); } else { declarationBinding = null; ArcumError.fatalError("Internal error found in getDeclarationBinding." + " What's the declaration of a %s?", StringUtil.debugDisplay(expr)); } return declarationBinding; } private void handleVariableInitialization(ASTNode node) { if (node instanceof SingleVariableDeclaration) { SingleVariableDeclaration decl = (SingleVariableDeclaration)node; Expression initializer = decl.getInitializer(); if (initializer != null) { initializers.add(newPair(initializer, decl.resolveBinding())); } } else if (node instanceof VariableDeclarationFragment) { VariableDeclarationFragment fragment = (VariableDeclarationFragment)node; Expression initializer = fragment.getInitializer(); if (initializer != null) { initializers.add(newPair(initializer, fragment.resolveBinding())); } } else if (node instanceof VariableDeclarationStatement) { VariableDeclarationStatement varDeclStmt = (VariableDeclarationStatement)node; List fragments = varDeclStmt.fragments(); if (fragments.size() != 1) { ArcumError.fatalError("Internal error found in handleVariableInitialization"); } VariableDeclarationFragment frag = (VariableDeclarationFragment)fragments.get(0); handleVariableInitialization(frag); } else if (node instanceof FieldDeclaration) { FieldDeclaration fieldDecl = (FieldDeclaration)node; List fragments = fieldDecl.fragments(); if (fragments.size() != 1) { ArcumError.fatalError("Internal error found in handleVariableInitialization"); } VariableDeclarationFragment frag = (VariableDeclarationFragment)fragments.get(0); handleVariableInitialization(frag); } else { ArcumError.fatalError("Unhandled case: %s", ASTUtil.getDebugString(node)); } } private void insertSuperclassRelation(OptionMatchTable table, Collection<ASTNode> classes) { final TraitSignature type; final List<String> names; type = BUILT_IN_TRAIT_TYPES.get("superclassOf"); names = Lists.transform(type.getFormals(), FormalParameter.getIdentifier); table.addBuiltInTrait(type); for (ASTNode clazz : classes) { ITypeBinding nextParent = lookupTypeBinding((AbstractTypeDeclaration)clazz); for (;;) { ITypeBinding superclass = nextParent.getSuperclass(); if (superclass == null) break; Map<String, Object> values = Maps.newHashMap(); values.put(names.get(0), superclass); values.put(names.get(1), clazz); EntityTuple instance = new EntityTuple(type, values, null); table.addTraitInstance("superclassOf", instance); nextParent = superclass; } } } private ITypeBinding findDefiningType(@Union("Entity") Object entity) { ASTNode node; // if (entity instanceof SignatureEntity) { // node = ((SignatureEntity)entity).getSignatureNode(); // } // else { node = (ASTNode)entity; // } ASTNode parent = node.getParent(); if (parent == null) { parent = desugaredToNearestNode.get(node); } while (!(parent instanceof AbstractTypeDeclaration)) { parent = parent.getParent(); } ITypeBinding typeBinding = lookupTypeBinding((AbstractTypeDeclaration)parent); return typeBinding; } public static ASTNode findASTNode(IBinding binding) { EntityDataBase edb = currentEDB.peek(); String key = binding.getKey(); MethodDeclaration methodDecl = edb.methodBindingKeyLookup.get(key); if (methodDecl != null) { return methodDecl; } AbstractTypeDeclaration typeDecl = edb.typeDefinitionKeyLookup.get(key); if (typeDecl != null) { return typeDecl; } return null; } public static IBinding resolveBindingNullOK(Type node) { ITypeBinding result = node.resolveBinding(); return result; } public static IBinding resolveBindingNullOK(Name node) { IBinding result = node.resolveBinding(); return result; } public static IBinding resolveBindingNullOK(SingleVariableDeclaration node) { IVariableBinding result = node.resolveBinding(); return result; } // EXAMPLE: This code duplication could be removed by either using interfaces // or by using reflection. All three implementations are useful in different // ways. public static ITypeBinding resolveBinding(AbstractTypeDeclaration node) { return (ITypeBinding)doResolveBinding(node, node.resolveBinding()); } public static IBinding resolveBinding(Name node) { return doResolveBinding(node, node.resolveBinding()); } public static IBinding resolveBinding(Type node) { return doResolveBinding(node, node.resolveBinding()); } public static IBinding resolveBinding(VariableDeclaration node) { return doResolveBinding(node, node.resolveBinding()); } private static IBinding doResolveBinding(final ASTNode toResolve, IBinding binding) { if (binding != null) { return binding; } else { ASTNode root = toResolve.getRoot(); EntityDataBase edb = currentEDB.peek(); ASTNode source = edb.desugaredToNearestNode.get(root); final StructuralPropertyDescriptor spdInParent = toResolve .getLocationInParent(); final ASTNode[] result = new ASTNode[1]; edb.traverseTable.traverseAST(source, new ASTVisitorAdaptor() { @Override public boolean visitASTNode(ASTNode sourceNode, StructuralPropertyDescriptor edge) { if (sourceNode == null) { return false; } if (spdInParent == edge) { if (Entity.compareTo(sourceNode, toResolve) == 0) { result[0] = sourceNode; return false; } } return true; } }); ASTNode foundNode = result[0]; if (foundNode == null) { StringBuilder builder = new StringBuilder(); builder.append(String.format("%n")); builder.append(String.format(" From: %s%n", //-> StringUtil.minimizeWhitespace(root))); builder.append(String.format("To resolve: %s%n", toResolve)); builder.append(String.format("Looking in: %s%n", //-> StringUtil.minimizeWhitespace(source))); builder.append(String.format(" found: %s%n", foundNode)); ArcumError.fatalError("Internal error in doResolveBinding: %s", builder.toString()); return null; } else { try { Method method = foundNode.getClass().getMethod("resolveBinding"); Object methodResult = method.invoke(foundNode); return (IBinding)methodResult; } catch (RuntimeException e) { throw e; } catch (Exception e) { ArcumError.fatalError("Something went wrong: %s", e.getMessage()); return null; } } } } public static EntityType getMostSpecificEntityType(Object entity) { if (entity instanceof BindingKeyValue) { BindingKeyValue keyValue = (BindingKeyValue)entity; return keyValue.getType(); } else if (entity instanceof EntityList) { // MACNEIL: This is a bug because it could be more specifically an // access specifier. This will just be ignored for now. return EntityType.MODIFIERS; } else if (entity instanceof SignatureEntity) { return EntityType.SIGNATURE; } Class<?> clazz = entity.getClass(); EntityType result = entityTypeTable.get(clazz); if (result == null) { ArcumError.fatalError("Unhandled case: %s", ASTUtil.getDebugString(entity)); } return result; } public static boolean isBuiltInTrait(String traitName) { return BUILT_IN_TRAIT_NAMES.contains(traitName); } }