| // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| package com.android.tools.r8.graph; |
| |
| import com.android.tools.r8.dex.IndexedItemCollection; |
| import com.android.tools.r8.dex.MixedSectionCollection; |
| import com.android.tools.r8.utils.ArrayUtils; |
| import com.android.tools.r8.utils.structural.StructuralItem; |
| import com.android.tools.r8.utils.structural.StructuralMapping; |
| import com.android.tools.r8.utils.structural.StructuralSpecification; |
| import java.util.Arrays; |
| import java.util.function.Consumer; |
| import java.util.function.Function; |
| import java.util.function.Predicate; |
| |
| /** |
| * List of parameter annotations. |
| * |
| * <p>Due to a javac bug that went unfixed for multiple Java versions, the JVM specification does |
| * not require that the number of entries in the ParameterAnnotations attribute of a method matches |
| * the number of parameters in the method prototype; the number of ParameterAnnotations entries may |
| * be less than the number of prototype parameters for methods on inner classes. |
| * |
| * <p>There are two ways of accessing the parameter annotations: |
| * |
| * <ul> |
| * <li>Using {@link ParameterAnnotationsList#forEachAnnotation(Consumer)} |
| * <li>Using {@link ParameterAnnotationsList#size()}, {@link |
| * ParameterAnnotationsList#isMissing(int)} and {@link ParameterAnnotationsList#get(int)} |
| * </ul> |
| * |
| * <p>The {@link ParameterAnnotationsList#forEachAnnotation(Consumer)} method visits all the {@link |
| * DexAnnotation}s specified in the ParameterAnnotations attribute. In contrast, the {@link |
| * ParameterAnnotationsList#size()} and {@link ParameterAnnotationsList#get(int)} methods may be |
| * used to access the annotations on individual parameters; these methods automatically shift |
| * parameter annotations up to mitigate the javac bug. The {@link |
| * ParameterAnnotationsList#isMissing(int)} accessor is used to determine whether a given parameter |
| * is missing in the ParameterAnnotations attribute. |
| */ |
| public class ParameterAnnotationsList extends DexItem |
| implements StructuralItem<ParameterAnnotationsList> { |
| |
| private static final ParameterAnnotationsList EMPTY_PARAMETER_ANNOTATIONS_LIST = |
| new ParameterAnnotationsList(); |
| |
| private final DexAnnotationSet[] values; |
| private final int missingParameterAnnotations; |
| |
| private static void specify(StructuralSpecification<ParameterAnnotationsList, ?> spec) { |
| spec.withItemArray(a -> a.values).withInt(a -> a.missingParameterAnnotations); |
| } |
| |
| public static ParameterAnnotationsList empty() { |
| return EMPTY_PARAMETER_ANNOTATIONS_LIST; |
| } |
| |
| private ParameterAnnotationsList() { |
| this.values = DexAnnotationSet.EMPTY_ARRAY; |
| this.missingParameterAnnotations = 0; |
| } |
| |
| private ParameterAnnotationsList(DexAnnotationSet[] values, int missingParameterAnnotations) { |
| assert values != null; |
| assert values.length > 0; |
| assert !isAllEmpty(values); |
| this.values = values; |
| this.missingParameterAnnotations = missingParameterAnnotations; |
| } |
| |
| public static ParameterAnnotationsList create(DexAnnotationSet[] values) { |
| return create(values, 0); |
| } |
| |
| public static ParameterAnnotationsList create( |
| DexAnnotationSet[] values, int missingParameterAnnotations) { |
| return ArrayUtils.isEmpty(values) || isAllEmpty(values) |
| ? empty() |
| : new ParameterAnnotationsList(values, missingParameterAnnotations); |
| } |
| |
| private static boolean isAllEmpty(DexAnnotationSet[] values) { |
| for (int i = 0; i < values.length; i++) { |
| if (!values[i].isEmpty()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public ParameterAnnotationsList self() { |
| return this; |
| } |
| |
| @Override |
| public StructuralMapping<ParameterAnnotationsList> getStructuralMapping() { |
| return ParameterAnnotationsList::specify; |
| } |
| |
| public int getAnnotableParameterCount() { |
| return size(); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Arrays.hashCode(values); |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (this == other) { |
| return true; |
| } |
| if (other instanceof ParameterAnnotationsList) { |
| // TODO(b/172999904): Why does equals not include missingParameterAnnotations? |
| return Arrays.equals(values, ((ParameterAnnotationsList) other).values); |
| } |
| return false; |
| } |
| |
| public void collectIndexedItems(IndexedItemCollection indexedItems) { |
| for (DexAnnotationSet value : values) { |
| value.collectIndexedItems(indexedItems); |
| } |
| } |
| |
| @Override |
| void collectMixedSectionItems(MixedSectionCollection mixedItems) { |
| // Collect values first so that the annotation sets have sorted themselves before adding this. |
| collectAll(mixedItems, values); |
| mixedItems.add(this); |
| } |
| |
| public boolean isEmpty() { |
| return values.length == 0; |
| } |
| |
| /** Iterate over the {@link DexAnnotation}s of all parameters. */ |
| public void forEachAnnotation(Consumer<DexAnnotation> consumer) { |
| for (DexAnnotationSet parameterAnnotations : values) { |
| for (DexAnnotation annotation : parameterAnnotations.annotations) { |
| consumer.accept(annotation); |
| } |
| } |
| } |
| |
| /** |
| * Return the number of parameters in the method prototype, or zero if the method's parameters |
| * have no annotations. |
| */ |
| public int size() { |
| return missingParameterAnnotations + values.length; |
| } |
| |
| /** |
| * Return the number of parameters specified in the ParameterAnnotations attribute, that is, the |
| * number of parameters for which {@link ParameterAnnotationsList#isMissing(int)} returns false. |
| */ |
| public int countNonMissing() { |
| return values.length; |
| } |
| |
| /** |
| * Return true if the ParameterAnnotations attribute is missing an entry for this parameter. This |
| * is sometimes the case for the first parameter in a method on an inner class. |
| * |
| * @param i Index of the parameter in the method prototype. |
| */ |
| public boolean isMissing(int i) { |
| assert i >= 0; |
| return i < missingParameterAnnotations; |
| } |
| |
| /** |
| * Return the annotations on the {@code i}th parameter (indexed according to the method |
| * prototype). If the parameter's annotation list is missing, or {@code i} is not less than the |
| * number of parameters (see {@link ParameterAnnotationsList#isMissing(int)}), {@link |
| * DexAnnotationSet#empty()} is returned. |
| * |
| * @param i Index of the parameter in the method prototype. |
| */ |
| public DexAnnotationSet get(int i) { |
| assert i >= 0; |
| int adjustedIndex = i - missingParameterAnnotations; |
| return (0 <= adjustedIndex && adjustedIndex < values.length) |
| ? values[adjustedIndex] |
| : DexAnnotationSet.empty(); |
| } |
| |
| /** Return a ParameterAnnotationsList extended to the given number of parameters. */ |
| public ParameterAnnotationsList withParameterCount(int parameterCount) { |
| if (this == EMPTY_PARAMETER_ANNOTATIONS_LIST || parameterCount == size()) { |
| return this; |
| } |
| if (parameterCount < size()) { |
| // Generally, it should never be the case that parameterCount < size(). However, it may be |
| // that the input has already been optimized (e.g., by Proguard), and that some optimization |
| // has removed formal parameters without removing the corresponding parameters annotations. |
| // In this case, we remove the excess annotations. |
| DexAnnotationSet[] trimmedValues = new DexAnnotationSet[parameterCount]; |
| System.arraycopy(values, 0, trimmedValues, 0, parameterCount); |
| return new ParameterAnnotationsList(trimmedValues, 0); |
| } |
| return new ParameterAnnotationsList(values, parameterCount - values.length); |
| } |
| |
| public ParameterAnnotationsList withFakeThisParameter() { |
| // If there are no parameter annotations there is no need to add one for the this parameter. |
| if (isEmpty()) { |
| return this; |
| } |
| DexAnnotationSet[] newValues = new DexAnnotationSet[size() + 1]; |
| System.arraycopy(values, 0, newValues, 1, size()); |
| newValues[0] = DexAnnotationSet.empty(); |
| return new ParameterAnnotationsList(newValues, 0); |
| } |
| |
| /** |
| * Return a new ParameterAnnotationsList that keeps only the annotations matched by {@code |
| * filter}. |
| */ |
| public ParameterAnnotationsList keepIf(Predicate<DexAnnotation> filter) { |
| DexAnnotationSet[] filtered = null; |
| boolean allEmpty = true; |
| for (int i = 0; i < values.length; i++) { |
| DexAnnotationSet updated = values[i].keepIf(filter); |
| if (updated != values[i]) { |
| if (filtered == null) { |
| filtered = values.clone(); |
| } |
| filtered[i] = updated; |
| } |
| if (!updated.isEmpty()) { |
| allEmpty = false; |
| } |
| } |
| if (filtered == null) { |
| return this; |
| } |
| if (allEmpty) { |
| return ParameterAnnotationsList.empty(); |
| } |
| return new ParameterAnnotationsList(filtered, missingParameterAnnotations); |
| } |
| |
| public ParameterAnnotationsList rewrite(Function<DexAnnotation, DexAnnotation> mapper) { |
| if (isEmpty()) { |
| return this; |
| } |
| DexAnnotationSet[] rewritten = |
| ArrayUtils.map( |
| values, annotations -> annotations.rewrite(mapper), DexAnnotationSet.EMPTY_ARRAY); |
| return rewritten != values |
| ? ParameterAnnotationsList.create(rewritten, missingParameterAnnotations) |
| : this; |
| } |
| } |