private Iterable<ModelPropertyExtractionContext> selectProperties(final ModelSchemaExtractionContext<?> context, CandidateMethods candidateMethods) { Map<String, ModelPropertyExtractionContext> propertiesMap = Maps.newTreeMap(); for (Map.Entry<Wrapper<Method>, Collection<Method>> entry : candidateMethods.allMethods().entrySet()) { Method method = entry.getKey().get(); PropertyAccessorType propertyAccessorType = PropertyAccessorType.of(method); Collection<Method> methodsWithEqualSignature = entry.getValue(); if (propertyAccessorType != null) { String propertyName = propertyAccessorType.propertyNameFor(method); ModelPropertyExtractionContext propertyContext = propertiesMap.get(propertyName); if (propertyContext == null) { propertyContext = new ModelPropertyExtractionContext(propertyName); propertiesMap.put(propertyName, propertyContext); } propertyContext.addAccessor(new PropertyAccessorExtractionContext(propertyAccessorType, methodsWithEqualSignature)); } } return Collections2.filter(propertiesMap.values(), new Predicate<ModelPropertyExtractionContext>() { @Override public boolean apply(ModelPropertyExtractionContext property) { return property.isReadable(); } }); }
/** * Marks all methods live that can be reached by calls previously seen. * * <p>This should only be invoked if the given type newly becomes instantiated. In essence, this * method replays all the invokes we have seen so far that could apply to this type and marks * the corresponding methods live. * * <p>Only methods that are visible in this type are considered. That is, only those methods that * are either defined directly on this type or that are defined on a supertype but are not * shadowed by another inherited method. */ private void transitionMethodsForInstantiatedClass(DexType type) { Set<Wrapper<DexMethod>> seen = new HashSet<>(); MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get(); do { DexClass clazz = appInfo.definitionFor(type); if (clazz == null) { reportMissingClass(type); // TODO(herhut): In essence, our subtyping chain is broken here. Handle that case better. break; } SetWithReason<DexEncodedMethod> reachableMethods = reachableVirtualMethods.get(type); if (reachableMethods != null) { for (DexEncodedMethod encodedMethod : reachableMethods.getItems()) { Wrapper<DexMethod> ignoringClass = equivalence.wrap(encodedMethod.method); if (!seen.contains(ignoringClass)) { seen.add(ignoringClass); markVirtualMethodAsLive(encodedMethod, KeepReason.reachableFromLiveType(type)); } } } type = clazz.superType; } while (type != null && !instantiatedTypes.contains(type)); }
private void markMethod(DexEncodedMethod method, Collection<ProguardMemberRule> rules, ProguardConfigurationRule context, Set<Wrapper<DexMethod>> methodsMarked, DexType onlyIfClassKept) { if ((methodsMarked != null) && methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) { return; } for (ProguardMemberRule rule : rules) { if (rule.matches(method, this)) { if (Log.ENABLED) { Log.verbose(getClass(), "Marking method `%s` due to `%s { %s }`.", method, context, rule); } if (methodsMarked != null) { methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.method)); } addItemToSets(method, context, rule, onlyIfClassKept); } } }
private <T extends PresortedComparable<T>, S extends KeyedDexItem<T>> void addNonShadowed( Iterator<S> items, HashMap<Wrapper<T>, S> map, Equivalence<T> equivalence, Set<Wrapper<T>> existing, BiFunction<S, S, S> onConflict) { while (items.hasNext()) { S item = items.next(); if (item == null) { // This item was filtered out by a preprocessing. continue; } Wrapper<T> wrapped = equivalence.wrap(item.getKey()); if (existing.contains(wrapped)) { S resolved = onConflict.apply(map.get(wrapped), item); wrapped = equivalence.wrap(resolved.getKey()); map.put(wrapped, resolved); } else { map.put(wrapped, item); } } }
static Set<Equivalence.Wrapper<ExprNode>> mergePredecessors( Map<Block, Set<Equivalence.Wrapper<ExprNode>>> blockToAccessedExprs, Block current) { Set<Equivalence.Wrapper<ExprNode>> currentBlockSet = null; for (Block predecessor : current.predecessors) { Set<Wrapper<ExprNode>> predecessorBlockSet = blockToAccessedExprs.get(predecessor); if (currentBlockSet == null) { currentBlockSet = new HashSet<>(predecessorBlockSet); } else { currentBlockSet.retainAll(predecessorBlockSet); } } if (currentBlockSet == null) { currentBlockSet = new HashSet<>(); } return currentBlockSet; }
private void addTypeSubstitutions(Map<Wrapper<ExprNode>, SoyType> substitutionsToAdd) { for (Map.Entry<Wrapper<ExprNode>, SoyType> entry : substitutionsToAdd.entrySet()) { ExprNode expr = entry.getKey().get(); // Get the existing type SoyType previousType = expr.getType(); for (TypeSubstitution subst = substitutions; subst != null; subst = subst.parent) { if (ExprEquivalence.get().equivalent(subst.expression, expr)) { previousType = subst.type; break; } } // If the new type is different than the current type, then add a new type substitution. if (!entry.getValue().equals(previousType)) { substitutions = new TypeSubstitution(substitutions, expr, entry.getValue()); } } }
/** * Compute a map which combines the constraints from both the left and right side of an * expression. The result should be a set of constraints which satisfy <strong>either</strong> * sides. * * @param left Constraints from the left side. * @param right Constraints from the right side. * @return The combined constraint. */ private Map<Wrapper<ExprNode>, SoyType> computeConstraintIntersection( Map<Wrapper<ExprNode>, SoyType> left, Map<Wrapper<ExprNode>, SoyType> right) { if (left.isEmpty()) { return left; } if (right.isEmpty()) { return right; } Map<Wrapper<ExprNode>, SoyType> result = Maps.newHashMapWithExpectedSize(left.size()); for (Map.Entry<Wrapper<ExprNode>, SoyType> entry : left.entrySet()) { // A variable must be present in both the left and right sides in order to be // included in the output. if (right.containsKey(entry.getKey())) { // The intersection of two constraints is a *looser* constraint. // Thus "((a instanceof any) OR (a instanceof bool)) == (a instanceof any)" SoyType rightSideType = right.get(entry.getKey()); result.put( entry.getKey(), SoyTypes.computeLowestCommonType(typeRegistry, entry.getValue(), rightSideType)); } } return result; }
private void assertEquivalent(ExprNode left, ExprNode right) { Wrapper<ExprNode> wrappedLeft = ExprEquivalence.get().wrap(left); Wrapper<ExprNode> wrappedRight = ExprEquivalence.get().wrap(right); assertThat(wrappedLeft).isEqualTo(wrappedRight); // Test symmetry assertThat(wrappedRight).isEqualTo(wrappedLeft); assertThat(wrappedLeft.hashCode()).isEqualTo(wrappedRight.hashCode()); // If two expressions are equal, then all subexpressions must also be equal if (left instanceof ParentExprNode) { List<ExprNode> leftChildren = ((ParentExprNode) left).getChildren(); List<ExprNode> rightChildren = ((ParentExprNode) right).getChildren(); for (int i = 0; i < leftChildren.size(); i++) { assertEquivalent(leftChildren.get(i), rightChildren.get(i)); } } }
private static <T> Map<Wrapper<Method>, WeaklyTypeReferencingMethod<?, ?>> collectPublicViewImplMethods(StructSchema<T> publicSchema) { return indexBySignature( Sets.filter( publicSchema.getAllMethods(), new Predicate<WeaklyTypeReferencingMethod<?, ?>>() { @Override public boolean apply(WeaklyTypeReferencingMethod<?, ?> weakMethod) { return !Modifier.isAbstract(weakMethod.getModifiers()); } } ) ); }
private static ImmutableMap<Wrapper<Method>, WeaklyTypeReferencingMethod<?, ?>> indexBySignature(Iterable<WeaklyTypeReferencingMethod<?, ?>> methods) { return Maps.uniqueIndex(methods, new Function<WeaklyTypeReferencingMethod<?, ?>, Wrapper<Method>>() { @Override public Wrapper<Method> apply(WeaklyTypeReferencingMethod<?, ?> weakMethod) { return SIGNATURE_EQUIVALENCE.wrap(weakMethod.getMethod()); } }); }
private static Collection<WeaklyTypeReferencingMethod<?, ?>> collectImplementedMethods(Iterable<StructSchema<?>> implementedSchemas) { Map<Wrapper<Method>, WeaklyTypeReferencingMethod<?, ?>> implementedMethodsBuilder = Maps.newLinkedHashMap(); for (StructSchema<?> implementedSchema : implementedSchemas) { for (WeaklyTypeReferencingMethod<?, ?> viewMethod : implementedSchema.getAllMethods()) { implementedMethodsBuilder.put(DESCRIPTOR_EQUIVALENCE.wrap(viewMethod.getMethod()), viewMethod); } } return implementedMethodsBuilder.values(); }
private Set<Wrapper<DexMethod>> computeMethodsOnMessageType() { DexClass messageClass = appInfo.definitionFor(messageType); if (messageClass == null) { return Collections.emptySet(); } Set<Wrapper<DexMethod>> superMethods = new HashSet<>(); messageClass.forEachMethod(method -> superMethods.add(equivalence.wrap(method.method))); return superMethods; }
private void markMatchingVisibleMethods(DexClass clazz, Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule, DexType onlyIfClassKept) { Set<Wrapper<DexMethod>> methodsMarked = new HashSet<>(); Arrays.stream(clazz.directMethods()).forEach(method -> markMethod(method, memberKeepRules, rule, methodsMarked, onlyIfClassKept)); while (clazz != null) { Arrays.stream(clazz.virtualMethods()).forEach(method -> markMethod(method, memberKeepRules, rule, methodsMarked, onlyIfClassKept)); clazz = application.definitionFor(clazz.superType); } }
private void addProgramMethods(Set<Wrapper<DexMethod>> set, DexMethod method, Equivalence<DexMethod> equivalence) { DexClass definition = appInfo.definitionFor(method.holder); if (definition != null && definition.isProgramClass()) { set.add(equivalence.wrap(method)); } }
private Collection<DexMethod> getInvokes() { if (invokes == null) { // Collect all reachable methods that are not within a library class. Those defined on // library classes are known not to have program classes in their signature. // Also filter methods that only use types from library classes in their signatures. We // know that those won't conflict. Set<Wrapper<DexMethod>> filteredInvokes = new HashSet<>(); Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get(); appInfo.targetedMethods.forEach(m -> addProgramMethods(filteredInvokes, m, equivalence)); invokes = filteredInvokes.stream().map(Wrapper::get).filter(this::removeNonProgram) .collect(Collectors.toList()); } return invokes; }
private <T extends PresortedComparable<T>, S extends KeyedDexItem<T>> Collection<S> mergeItems( Iterator<S> fromItems, S[] toItems, Equivalence<T> equivalence, Set<Wrapper<T>> existing, BiFunction<S, S, S> onConflict) { HashMap<Wrapper<T>, S> methods = new HashMap<>(); // First add everything from the target class. These items are not preprocessed. for (S item : toItems) { methods.put(equivalence.wrap(item.getKey()), item); } // Now add the new methods, resolving shadowing. addNonShadowed(fromItems, methods, equivalence, existing, onConflict); return methods.values(); }
private void addStatesToGlobalMapForMethod( DexEncodedMethod method, Set<NamingState<DexProto>> collectedStates, Map<Wrapper<DexMethod>, Set<NamingState<DexProto>>> globalStateMap, Map<Wrapper<DexMethod>, Set<DexMethod>> sourceMethodsMap, Map<Wrapper<DexMethod>, NamingState<DexProto>> originStates, DexType originInterface) { Wrapper<DexMethod> key = equivalence.wrap(method.method); Set<NamingState<DexProto>> stateSet = globalStateMap.computeIfAbsent(key, k -> new HashSet<>()); stateSet.addAll(collectedStates); sourceMethodsMap.computeIfAbsent(key, k -> new HashSet<>()).add(method.method); originStates.putIfAbsent(key, states.get(originInterface)); }
public static <T> Function<T, Equivalence.Wrapper<T>> wrapFunction( final Equivalence<T> equivalence) { return new Function<T, Equivalence.Wrapper<T>>() { @Override public Equivalence.Wrapper<T> apply(final T item) { return equivalence.wrap(item); } }; }
public static <T> Function<Equivalence.Wrapper<T>, T> unwrapFunction() { return new Function<Equivalence.Wrapper<T>, T>() { @Override public T apply(final Equivalence.Wrapper<T> x) { return x.get(); } }; }
public static <T> Function<Wrapper<T>, String> toStringThroughWrapperFunction() { return new Function<Wrapper<T>, String>() { @Override public String apply(final Wrapper<T> wrapped) { return wrapped.get().toString(); } }; }
/** Returns a set of ExprNodes that have definitely already been resolved. */ Set<ExprNode> getResolvedExpressions() { // To implement we need to walk through the access graph and if we find any path to an // expression that doesn't go through another reference to the 'same expression' // The easiest way to do this is to calculate 'all paths' through the graph and then do a // search. Unfortunately, 'all paths through a DAG' is technically an exponential time // algorithm // But fortunately we can do better. We can do a DFS from every expression to the start node // and if we go through another reference to the same expression we can abort. If we find any // path to 'start' from the expression then we can remove it from the set. // Still, DFS from every node is technically O(N^2 + MN)). This too can be resolved with // dynamic programming to make it O(N + M). To do this we have to limit the number of paths // traversed by accumulating results. // If we do a Topological traversal from the start node then whenever we visit a node we will // have already visited all of its predecessors. The set of variables definitely accessed // prior to a node is simply the intersection of all the accessed variables from its // predecessors. So each node can be processed in time relative to the number of incoming // edges. Set<ExprNode> resolvedExprs = Sets.newIdentityHashSet(); Map<Block, Set<Equivalence.Wrapper<ExprNode>>> blockToAccessedExprs = new IdentityHashMap<>(); for (Block current : getTopologicalOrdering()) { // First calculate the set of exprs that were definitely accessed prior to this node Set<Equivalence.Wrapper<ExprNode>> currentBlockSet = mergePredecessors(blockToAccessedExprs, current); // Then figure out which nodes in this block were _already_ accessed. for (ExprNode expr : current.exprs) { Equivalence.Wrapper<ExprNode> wrapped = ExprEquivalence.get().wrap(expr); if (!currentBlockSet.add(wrapped)) { resolvedExprs.add(expr); } } blockToAccessedExprs.put(current, currentBlockSet); } return resolvedExprs; }
void visitAndImplicitlyCastToBoolean(ExprNode node) { // In places where the expression is implicitly cast to a boolean, treat // a reference to a variable as a comparison of that variable with null. // So for example an expression like {if $var} should be treated as // {if $var != null} but something like {if $var > 0} should not be changed. visit(node); Wrapper<ExprNode> wrapped = ExprEquivalence.get().wrap(node); positiveTypeConstraints.put(wrapped, SoyTypes.tryRemoveNull(node.getType())); // TODO(lukes): The 'negative' type constraint here is not optimal. What we really know is // that the value of the expression is 'falsy' we could use that to inform later checks but // for now we just assume it has its normal type. negativeTypeConstraints.put(wrapped, node.getType()); }
private UsageInfo( SetMultimap<Integer, Wrapper<ASTNode>> idToLastDefinitions, Set<Wrapper<ASTNode>> usedDefinitions, Set<Integer> initializedIdentifiers, boolean reachable) { this.idToLastDefinitions = idToLastDefinitions; this.usedDefinitions = usedDefinitions; this.initializedIdentifiers = initializedIdentifiers; this.reachable = reachable; }
@Override public void visit(FunctionDefStatement node) { Preconditions.checkState(cfi == null); cfi = ControlFlowInfo.entry(); super.visit(node); if (cfi.hasReturnWithValue && (!cfi.returnsAlwaysExplicitly || cfi.hasReturnWithoutValue)) { issues.add( Issue.create( MISSING_RETURN_VALUE_CATEGORY, "some but not all execution paths of '" + node.getIdentifier() + "' return a value." + " If it is intentional, make it explicit using 'return None'." + " If you know these cannot happen," + " add the statement `fail('unreachable')` to them." + " For more details, have a look at the documentation.", node.getLocation())); for (Wrapper<ReturnStatement> returnWrapper : cfi.returnStatementsWithoutValue) { issues.add( Issue.create( MISSING_RETURN_VALUE_CATEGORY, "return value missing (you can `return None` if this is desired)", unwrapReturn(returnWrapper).getLocation())); } } cfi = null; }
private ControlFlowInfo( boolean reachable, boolean hasReturnWithValue, boolean hasReturnWithoutValue, boolean returnsAlwaysExplicitly, LinkedHashSet<Wrapper<ReturnStatement>> returnStatementsWithoutValue) { this.reachable = reachable; this.hasReturnWithValue = hasReturnWithValue; this.hasReturnWithoutValue = hasReturnWithoutValue; this.returnsAlwaysExplicitly = returnsAlwaysExplicitly; this.returnStatementsWithoutValue = returnStatementsWithoutValue; }
private static <T> Set<StructMethodBinding> collectMethodBindings(StructBindingExtractionContext<T> extractionContext, Map<String, Multimap<PropertyAccessorType, StructMethodBinding>> propertyBindings) { Collection<WeaklyTypeReferencingMethod<?, ?>> implementedMethods = collectImplementedMethods(extractionContext.getImplementedSchemas()); Map<Wrapper<Method>, WeaklyTypeReferencingMethod<?, ?>> publicViewImplMethods = collectPublicViewImplMethods(extractionContext.getPublicSchema()); Map<Wrapper<Method>, WeaklyTypeReferencingMethod<?, ?>> delegateMethods = collectDelegateMethods(extractionContext.getDelegateSchema()); ImmutableSet.Builder<StructMethodBinding> methodBindingsBuilder = ImmutableSet.builder(); for (WeaklyTypeReferencingMethod<?, ?> weakImplementedMethod : implementedMethods) { Method implementedMethod = weakImplementedMethod.getMethod(); PropertyAccessorType accessorType = PropertyAccessorType.of(implementedMethod); Wrapper<Method> methodKey = SIGNATURE_EQUIVALENCE.wrap(implementedMethod); WeaklyTypeReferencingMethod<?, ?> weakDelegateImplMethod = delegateMethods.get(methodKey); WeaklyTypeReferencingMethod<?, ?> weakPublicImplMethod = publicViewImplMethods.get(methodKey); if (weakDelegateImplMethod != null && weakPublicImplMethod != null) { extractionContext.add(weakImplementedMethod, String.format("it is both implemented by the view '%s' and the delegate type '%s'", extractionContext.getPublicSchema().getType().getDisplayName(), extractionContext.getDelegateSchema().getType().getDisplayName())); } String propertyName = accessorType == null ? null : accessorType.propertyNameFor(implementedMethod); StructMethodBinding binding; if (!Modifier.isAbstract(implementedMethod.getModifiers())) { binding = new DirectMethodBinding(weakImplementedMethod, accessorType); } else if (weakPublicImplMethod != null) { binding = new BridgeMethodBinding(weakImplementedMethod, weakPublicImplMethod, accessorType); } else if (weakDelegateImplMethod != null) { binding = new DelegateMethodBinding(weakImplementedMethod, weakDelegateImplMethod, accessorType); } else if (propertyName != null) { binding = new ManagedPropertyMethodBinding(weakImplementedMethod, propertyName, accessorType); } else { handleNoMethodImplementation(extractionContext, weakImplementedMethod); continue; } methodBindingsBuilder.add(binding); if (accessorType != null) { Multimap<PropertyAccessorType, StructMethodBinding> accessorBindings = propertyBindings.get(propertyName); if (accessorBindings == null) { accessorBindings = ArrayListMultimap.create(); propertyBindings.put(propertyName, accessorBindings); } accessorBindings.put(accessorType, binding); } } return methodBindingsBuilder.build(); }
private static Map<Wrapper<Method>, WeaklyTypeReferencingMethod<?, ?>> collectDelegateMethods(StructSchema<?> delegateSchema) { return delegateSchema == null ? Collections.<Wrapper<Method>, WeaklyTypeReferencingMethod<?, ?>>emptyMap() : indexBySignature(delegateSchema.getAllMethods()); }
private boolean contains(Wrapper<DexMethod> item) { return items.contains(item) || ((parent != null) && parent.contains(item)); }
boolean addMethod(DexMethod method) { Wrapper<DexMethod> wrapped = METHOD_EQUIVALENCE.wrap(method); return !contains(wrapped) && items.add(wrapped); }
private <T extends KeyedDexItem<S>, S extends PresortedComparable<S>> void addAll( Collection<Wrapper<S>> collection, T[] items, Equivalence<S> equivalence) { for (T item : items) { collection.add(equivalence.wrap(item.getKey())); } }
public void testWrap_get() { String test = "test"; Wrapper<String> wrapper = LENGTH_EQUIVALENCE.wrap(test); assertSame(test, wrapper.get()); }
int getBurnTime(ItemStack stack) { Wrapper<ItemStack> wrapper = FUEL_EQUIVALENCE.wrap(stack); if(overrides.containsKey(wrapper)) return overrides.get(wrapper); else return defaults.getBurnTime(stack); }
private void checkUsed(Integer id) { Set<Wrapper<ASTNode>> unusedDefinitions = new LinkedHashSet<>(idToAllDefinitions.get(id)); unusedDefinitions.removeAll(ui.usedDefinitions); NameInfo nameInfo = env.getNameInfo(id); String name = nameInfo.name; if ("_".equals(name) || nameInfo.kind == Kind.BUILTIN) { return; } if ((nameInfo.kind == Kind.LOCAL || nameInfo.kind == Kind.PARAMETER) && (name.startsWith("_") || name.startsWith("unused_") || name.startsWith("UNUSED_"))) { // local variables starting with an underscore need not be used return; } if ((nameInfo.kind == Kind.GLOBAL || nameInfo.kind == Kind.FUNCTION) && !name.startsWith("_")) { // symbol might be loaded in another file return; } String message = "unused binding of '" + name + "'"; if (nameInfo.kind == Kind.IMPORTED && !nameInfo.name.startsWith("_")) { message += ". If you want to re-export a symbol, use the following pattern:\n" + "\n" + "load(..., _" + name + " = '" + name + "', ...)\n" + name + " = _" + name + "\n" + "\n" + "More details in the documentation."; } else if (nameInfo.kind == Kind.PARAMETER) { message += ". If this is intentional, " + "you can add `_ignore = [<param1>, <param2>, ...]` to the function body."; } else if (nameInfo.kind == Kind.LOCAL) { message += ". If this is intentional, you can use '_' or rename it to '_" + name + "'."; } for (Wrapper<ASTNode> definition : unusedDefinitions) { if (initializationsWithNone.contains(definition) && idToAllDefinitions.get(id).size() > 1) { // initializations with None are OK, cf. visit(AssignmentStatement) above continue; } issues.add( Issue.create(UNUSED_BINDING_CATEGORY, message, unwrapNode(definition).getLocation())); } }
private Wrapper<ASTNode> wrapNode(ASTNode node) { return Equivalence.identity().wrap(node); }
private ASTNode unwrapNode(Wrapper<ASTNode> wrapper) { return wrapper.get(); }