Merge commit 'd541560cbaab87e2feb1f173bb39ab48f8e09616' into dev-release
Change-Id: I8bb4a071c291d9c639a79225a842f546cedcad6b
diff --git a/scripts/add-android-jar.sh b/scripts/add-android-jar.sh
index e2fe239..3f575b4 100755
--- a/scripts/add-android-jar.sh
+++ b/scripts/add-android-jar.sh
@@ -16,7 +16,7 @@
SDK_HOME=$HOME/Android/Sdk
# Modify these to match the SDK android.jar to add.
-SDK_DIR_NAME=android-VanillaIceCream
+SDK_DIR_NAME=android-35
SDK_VERSION=35
SDK_DIR=$SDK_HOME/platforms/$SDK_DIR_NAME
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
index 47fdb1d..9413ca3 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
@@ -34,6 +34,7 @@
* <ul>
* <li>constant
* <li>classNamePattern
+ * <li>instanceOfPattern
* </ul>
*/
String name() default "";
@@ -48,6 +49,7 @@
* <ul>
* <li>name
* <li>classNamePattern
+ * <li>instanceOfPattern
* </ul>
*/
Class<?> constant() default Object.class;
@@ -60,7 +62,23 @@
* <ul>
* <li>name
* <li>constant
+ * <li>instanceOfPattern
* </ul>
*/
ClassNamePattern classNamePattern() default @ClassNamePattern(unqualifiedName = "");
+
+ /**
+ * Define the instance-of with a pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining type-pattern:
+ *
+ * <ul>
+ * <li>name
+ * <li>constant
+ * <li>classNamePattern
+ * </ul>
+ *
+ * @return The pattern that defines what instance-of the class must be.
+ */
+ InstanceOfPattern instanceOfPattern() default @InstanceOfPattern();
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
index 538c8ca..e0ef6b5 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
@@ -69,7 +69,11 @@
arrayTypePattern -> {
throw parsingContext.error("Invalid use of array type where class type was expected");
},
- classNamePattern -> classNamePattern);
+ classNamePattern -> classNamePattern,
+ instanceOfPattern -> {
+ throw parsingContext.error(
+ "Invalid use of instance of type where class type was expected");
+ });
}
@Override
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
index c1f8c13..2645b35 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -466,6 +466,9 @@
},
clazz -> {
writeClassNamePattern(clazz, TypePattern.classNamePattern, v);
+ },
+ instanceOf -> {
+ writeInstanceOfPattern(instanceOf, v);
}));
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
index a0c0afc..f81610e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.keepanno.asm;
import com.android.tools.r8.keepanno.asm.ClassNameParser.ClassNameProperty;
+import com.android.tools.r8.keepanno.asm.InstanceOfParser.InstanceOfProperties;
import com.android.tools.r8.keepanno.asm.TypeParser.TypeProperty;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.TypePattern;
import com.android.tools.r8.keepanno.ast.KeepTypePattern;
@@ -24,7 +25,8 @@
TYPE_PATTERN,
TYPE_NAME,
TYPE_CONSTANT,
- CLASS_NAME_PATTERN
+ CLASS_NAME_PATTERN,
+ INSTANCE_OF_PATTERN
}
@Override
@@ -56,6 +58,7 @@
typeParser.setProperty(TypePattern.name, TypeProperty.TYPE_NAME);
typeParser.setProperty(TypePattern.constant, TypeProperty.TYPE_CONSTANT);
typeParser.setProperty(TypePattern.classNamePattern, TypeProperty.CLASS_NAME_PATTERN);
+ typeParser.setProperty(TypePattern.instanceOfPattern, TypeProperty.INSTANCE_OF_PATTERN);
return new ParserVisitor(
context,
typeParser,
@@ -70,6 +73,15 @@
descriptor,
value -> setValue.accept(KeepTypePattern.fromClass(value)));
}
+ case INSTANCE_OF_PATTERN:
+ {
+ InstanceOfParser parser = new InstanceOfParser(getParsingContext());
+ return parser.tryPropertyAnnotation(
+ InstanceOfProperties.PATTERN,
+ name,
+ descriptor,
+ value -> setValue.accept(KeepTypePattern.fromInstanceOf(value)));
+ }
default:
return null;
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
index 9b0ae29..77643cd 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
@@ -237,6 +237,7 @@
public static final String name = "name";
public static final String constant = "constant";
public static final String classNamePattern = "classNamePattern";
+ public static final String instanceOfPattern = "instanceOfPattern";
}
public static final class ClassNamePattern {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java
index 6194fb3..5981336 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java
@@ -47,11 +47,14 @@
() -> {
throw new KeepEdgeException("No descriptor exists for 'any primitive' array");
},
- primitive -> primitive.getDescriptor(),
+ KeepPrimitiveTypePattern::getDescriptor,
array -> {
throw new KeepEdgeException("Unexpected nested array");
},
- clazz -> clazz.getExactDescriptor());
+ KeepQualifiedClassNamePattern::getExactDescriptor,
+ instanceOf -> {
+ throw new KeepEdgeException("No descriptor exists for instanceOf array");
+ });
}
@Override
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
index a0edf23..7e3c088 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
@@ -28,6 +28,10 @@
return new ClassType(type);
}
+ public static KeepTypePattern fromInstanceOf(KeepInstanceOfPattern pattern) {
+ return new KeepInstanceOf(pattern);
+ }
+
public static KeepTypePattern fromDescriptor(String typeDescriptor) {
char c = typeDescriptor.charAt(0);
if (c == 'L') {
@@ -56,18 +60,21 @@
Supplier<T> onAny,
Function<KeepPrimitiveTypePattern, T> onPrimitive,
Function<KeepArrayTypePattern, T> onArray,
- Function<KeepQualifiedClassNamePattern, T> onClass);
+ Function<KeepQualifiedClassNamePattern, T> onClass,
+ Function<KeepInstanceOfPattern, T> onInstanceOf);
public final void match(
Runnable onAny,
Consumer<KeepPrimitiveTypePattern> onPrimitive,
Consumer<KeepArrayTypePattern> onArray,
- Consumer<KeepQualifiedClassNamePattern> onClass) {
+ Consumer<KeepQualifiedClassNamePattern> onClass,
+ Consumer<KeepInstanceOfPattern> onInstanceOf) {
apply(
AstUtils.toVoidSupplier(onAny),
AstUtils.toVoidFunction(onPrimitive),
AstUtils.toVoidFunction(onArray),
- AstUtils.toVoidFunction(onClass));
+ AstUtils.toVoidFunction(onClass),
+ AstUtils.toVoidFunction(onInstanceOf));
}
public boolean isAny() {
@@ -87,7 +94,8 @@
Supplier<T> onAny,
Function<KeepPrimitiveTypePattern, T> onPrimitive,
Function<KeepArrayTypePattern, T> onArray,
- Function<KeepQualifiedClassNamePattern, T> onClass) {
+ Function<KeepQualifiedClassNamePattern, T> onClass,
+ Function<KeepInstanceOfPattern, T> onInstanceOf) {
return onAny.get();
}
@@ -152,7 +160,8 @@
Supplier<T> onAny,
Function<KeepPrimitiveTypePattern, T> onPrimitive,
Function<KeepArrayTypePattern, T> onArray,
- Function<KeepQualifiedClassNamePattern, T> onClass) {
+ Function<KeepQualifiedClassNamePattern, T> onClass,
+ Function<KeepInstanceOfPattern, T> onInstanceOf) {
return onPrimitive.apply(type);
}
}
@@ -169,7 +178,8 @@
Supplier<T> onAny,
Function<KeepPrimitiveTypePattern, T> onPrimitive,
Function<KeepArrayTypePattern, T> onArray,
- Function<KeepQualifiedClassNamePattern, T> onClass) {
+ Function<KeepQualifiedClassNamePattern, T> onClass,
+ Function<KeepInstanceOfPattern, T> onInstanceOf) {
return onClass.apply(type);
}
@@ -208,7 +218,8 @@
Supplier<T> onAny,
Function<KeepPrimitiveTypePattern, T> onPrimitive,
Function<KeepArrayTypePattern, T> onArray,
- Function<KeepQualifiedClassNamePattern, T> onClass) {
+ Function<KeepQualifiedClassNamePattern, T> onClass,
+ Function<KeepInstanceOfPattern, T> onInstanceOf) {
return onArray.apply(type);
}
@@ -234,4 +245,22 @@
return type.toString();
}
}
+
+ private static class KeepInstanceOf extends KeepTypePattern {
+ private final KeepInstanceOfPattern instanceOf;
+
+ private KeepInstanceOf(KeepInstanceOfPattern instanceOf) {
+ this.instanceOf = instanceOf;
+ }
+
+ @Override
+ public <T> T apply(
+ Supplier<T> onAny,
+ Function<KeepPrimitiveTypePattern, T> onPrimitive,
+ Function<KeepArrayTypePattern, T> onArray,
+ Function<KeepQualifiedClassNamePattern, T> onClass,
+ Function<KeepInstanceOfPattern, T> onInstanceOf) {
+ return onInstanceOf.apply(instanceOf);
+ }
+ }
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
index 12d2caa..38a768f 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
@@ -221,7 +221,13 @@
printer::appendTripleStar,
primitivePattern -> printPrimitiveType(printer, primitivePattern),
arrayTypePattern -> printArrayType(printer, arrayTypePattern),
- classTypePattern -> printClassName(classTypePattern, printer));
+ classTypePattern -> printClassName(classTypePattern, printer),
+ instanceOfPattern -> printInstanceOf(instanceOfPattern, printer));
+ }
+
+ private static RulePrinter printInstanceOf(
+ KeepInstanceOfPattern instanceOfPattern, RulePrinter printer) {
+ throw new Unimplemented();
}
private static RulePrinter printPrimitiveType(
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
index af5a909..e4222dd 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
@@ -16,16 +16,39 @@
public class AndroidApiLevelDatabaseHelper {
public static Set<String> notModeledTypes() {
- // The below types are known not to be modeled by any api-versions.
+ // The types below are known not to be modeled by any api-versions.
Set<String> notModeledTypes = new HashSet<>();
notModeledTypes.add("androidx.annotation.RecentlyNullable");
notModeledTypes.add("androidx.annotation.RecentlyNonNull");
notModeledTypes.add("android.annotation.Nullable");
notModeledTypes.add("android.annotation.NonNull");
notModeledTypes.add("android.annotation.FlaggedApi");
+ notModeledTypes.add("android.adservices.customaudience.PartialCustomAudience");
+ notModeledTypes.add("android.adservices.customaudience.PartialCustomAudience$Builder");
+ notModeledTypes.add(
+ "android.adservices.customaudience.ScheduleCustomAudienceUpdateRequest$Builder");
+ notModeledTypes.add("android.adservices.customaudience.ScheduleCustomAudienceUpdateRequest");
return notModeledTypes;
}
+ public static Set<String> notModeledFields() {
+ // The fields below are known not to be modeled by any api-versions.
+ Set<String> notModeledFields = new HashSet<>();
+ notModeledFields.add("int android.app.appsearch.AppSearchResult.RESULT_DENIED");
+ notModeledFields.add("int android.app.appsearch.AppSearchResult.RESULT_RATE_LIMITED");
+ return notModeledFields;
+ }
+
+ public static Set<String> notModeledMethods() {
+ // The methods below are known not to be modeled by any api-versions.
+ Set<String> notModelledMethods = new HashSet<>();
+ notModelledMethods.add(
+ "void android.adservices.customaudience.CustomAudienceManager.scheduleCustomAudienceUpdate(android.adservices.customaudience.ScheduleCustomAudienceUpdateRequest,"
+ + " java.util.concurrent.Executor,"
+ + " android.adservices.common.AdServicesOutcomeReceiver)");
+ return notModelledMethods;
+ }
+
static void visitAdditionalKnownApiReferences(
DexItemFactory factory, BiConsumer<DexReference, AndroidApiLevel> apiLevelConsumer) {
addStringBuilderAndBufferMethods(factory, apiLevelConsumer);
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
index a08de1f..fe4d746 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
@@ -15,16 +15,17 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ThrowingCharIterator;
import com.android.tools.r8.utils.ThrowingFunction;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UTFDataFormatException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class AndroidApiLevelHashingDatabaseImpl implements AndroidApiLevelDatabase {
@@ -36,6 +37,7 @@
private static final byte[] NON_EXISTING_DESCRIPTOR = new byte[0];
private final List<DexString> androidApiExtensionPackages;
+ private final Set<DexType> androidApiExtensionClasses;
public static byte[] getNonExistingDescriptor() {
return NON_EXISTING_DESCRIPTOR;
@@ -165,22 +167,35 @@
predefinedApiReference.getReference(),
Optional.of(predefinedApiReference.getApiLevel()));
});
- ImmutableList.Builder<DexString> androidApiExtensionPackagesBuilder =
- new ImmutableList.Builder<>();
- if (options.apiModelingOptions().androidApiExtensionPackages != null) {
- StringUtils.split(options.apiModelingOptions().androidApiExtensionPackages, ',')
- .forEach(
- pkg -> {
- androidApiExtensionPackagesBuilder.add(
- options.itemFactory.createString(
- "L"
- + pkg.replace(
- DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
- DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR)
- + "/"));
- });
+
+ // Register classes in the extension libraries.
+ {
+ ImmutableSet.Builder<DexType> builder = ImmutableSet.builder();
+ options
+ .apiModelingOptions()
+ .forEachAndroidApiExtensionClassDescriptor(
+ descriptor -> builder.add(options.itemFactory.createType(descriptor)));
+ this.androidApiExtensionClasses = builder.build();
}
- this.androidApiExtensionPackages = androidApiExtensionPackagesBuilder.build();
+ // Register packages for extension libraries.
+ // TODO(b/326252366): Remove support for list of extension packages in favour of only
+ // supporting passing extension libraries as JAR files.
+ {
+ ImmutableList.Builder<DexString> builder = ImmutableList.builder();
+ options
+ .apiModelingOptions()
+ .forEachAndroidApiExtensionPackage(
+ pkg ->
+ builder.add(
+ options.itemFactory.createString(
+ "L"
+ + pkg.replace(
+ DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
+ DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR)
+ + "/")));
+ this.androidApiExtensionPackages = builder.build();
+ }
+
assert predefinedApiTypeLookup.stream()
.allMatch(added -> added.getApiLevel().isEqualTo(lookupApiLevel(added.getReference())));
}
@@ -225,6 +240,9 @@
private AndroidApiLevel lookupApiLevel(DexReference reference) {
// TODO(b/326252366): Assigning all extension items the same "fake" API level results in API
// outlines becoming mergable across extensions, which should be prevented.
+ if (androidApiExtensionClasses.contains(reference.getContextType())) {
+ return AndroidApiLevel.EXTENSION;
+ }
for (int i = 0; i < androidApiExtensionPackages.size(); i++) {
DexString descriptor = reference.getContextType().getDescriptor();
DexString extensionPackage = androidApiExtensionPackages.get(i);
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 54dc05d..21b8de8 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -81,14 +81,27 @@
}
public boolean canBeConvertedToAbstractMethod(AppView<AppInfoWithLiveness> appView) {
- return (appView.options().canUseAbstractMethodOnNonAbstractClass()
+ if (!(appView.options().canUseAbstractMethodOnNonAbstractClass()
|| getHolder().isAbstract()
|| getHolder().isInterface())
- && !getAccessFlags().isNative()
- && !getAccessFlags().isPrivate()
- && !getAccessFlags().isStatic()
- && !getDefinition().isInstanceInitializer()
- && !appView.appInfo().isFailedMethodResolutionTarget(getReference());
+ || getAccessFlags().isNative()
+ || getAccessFlags().isPrivate()
+ || getAccessFlags().isStatic()
+ || getDefinition().isInstanceInitializer()) {
+ return false;
+ }
+ // If the method has a failed resolution, then keep the method as non-abstract to preserve
+ // runtime errors, except when interface method desugaring is required.
+ if (appView.appInfo().isFailedMethodResolutionTarget(getReference())) {
+ boolean mustBeConvertedToAbstractMethod =
+ !appView.options().canUseDefaultAndStaticInterfaceMethods()
+ && getHolder().isInterface()
+ && getAccessFlags().belongsToVirtualPool();
+ if (!mustBeConvertedToAbstractMethod) {
+ return false;
+ }
+ }
+ return true;
}
public void convertToAbstractOrThrowNullMethod(AppView<AppInfoWithLiveness> appView) {
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
index 77605d8..fd7a46d 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
@@ -135,11 +136,27 @@
if (previous.hasReboundReference()) {
// Rewrite the rebound reference and then "fixup" the non-rebound reference.
DexField rewrittenReboundReference = previous.getRewrittenReboundReference(fieldMap);
- DexField rewrittenNonReboundReference =
- previous.getReference() == previous.getReboundReference()
- ? rewrittenReboundReference
- : rewrittenReboundReference.withHolder(
- getNextClassType(previous.getReference().getHolderType()), dexItemFactory());
+ DexField rewrittenNonReboundReference;
+ if (previous.getReference().isIdenticalTo(previous.getReboundReference())) {
+ rewrittenNonReboundReference = rewrittenReboundReference;
+ } else {
+ // Compute the new holder by mapping the original symbolic holder through the lens.
+ DexType originalHolder = previous.getReference().getHolderType();
+ DexType rewrittenHolder = getNextClassType(originalHolder);
+ rewrittenNonReboundReference =
+ rewrittenReboundReference.withHolder(rewrittenHolder, dexItemFactory());
+ // When vertical class merging preserve non-rebound method references when we can hit a
+ // collision due to field shadowing (b/348202700).
+ if (isVerticalClassMergerLens() && rewrittenHolder.isNotIdenticalTo(originalHolder)) {
+ DexClassAndField collision = appView.definitionFor(rewrittenNonReboundReference);
+ if (collision != null) {
+ assert !asVerticalClassMergerLens().hasBeenMerged(collision.getHolder().getSuperType());
+ rewrittenNonReboundReference =
+ rewrittenReboundReference.withHolder(
+ collision.getHolder().getSuperType(), dexItemFactory());
+ }
+ }
+ }
return FieldLookupResult.builder(this)
.setReboundReference(rewrittenReboundReference)
.setReference(rewrittenNonReboundReference)
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 48241f6..54e7d5c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.conversion;
+import static com.android.tools.r8.ir.conversion.IRBuilder.EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET;
+import static com.android.tools.r8.ir.conversion.IRBuilder.EXCEPTIONAL_SYNC_EXIT_THROW_OFFSET;
import static it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMaps.emptyMap;
import com.android.tools.r8.cf.code.CfFrame;
@@ -59,12 +61,27 @@
public class CfSourceCode implements SourceCode {
+ private enum GeneratedMethodSynchronizationBlock {
+ METHOD_ENTER,
+ METHOD_EXIT,
+ EXCEPTIONAL_MONITOR_EXIT,
+ EXCEPTIONAL_THROW,
+ NONE;
+
+ static GeneratedMethodSynchronizationBlock fromOffset(int offset) {
+ assert isExceptionalExitForMethodSynchronization(offset);
+ return isExceptionalMonitorExitForMethodSynchronization(offset)
+ ? EXCEPTIONAL_MONITOR_EXIT
+ : EXCEPTIONAL_THROW;
+ }
+ }
+
private BlockInfo currentBlockInfo;
private boolean hasExitingInstruction = false;
- private static final int EXCEPTIONAL_SYNC_EXIT_OFFSET = -2;
private final boolean needsGeneratedMethodSynchronization;
- private boolean currentlyGeneratingMethodSynchronization = false;
+ private GeneratedMethodSynchronizationBlock currentlyGeneratingMethodSynchronization =
+ GeneratedMethodSynchronizationBlock.NONE;
private Monitor monitorEnter = null;
private static class TryHandlerList {
@@ -129,7 +146,7 @@
}
if (needsGeneratedMethodSynchronization && !seenCatchAll) {
guards.add(factory.throwableType);
- offsets.add(EXCEPTIONAL_SYNC_EXIT_OFFSET);
+ offsets.add(EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET);
}
return new TryHandlerList(startOffset, endOffset, guards, offsets);
}
@@ -239,6 +256,18 @@
&& getMethod().isSynchronized();
}
+ void setCurrentlyGeneratingMethodSynchronization(
+ GeneratedMethodSynchronizationBlock currentlyGeneratingMethodSynchronization) {
+ assert this.currentlyGeneratingMethodSynchronization
+ == GeneratedMethodSynchronizationBlock.NONE;
+ this.currentlyGeneratingMethodSynchronization = currentlyGeneratingMethodSynchronization;
+ }
+
+ void unsetCurrentlyGeneratingMethodSynchronization() {
+ assert currentlyGeneratingMethodSynchronization != GeneratedMethodSynchronizationBlock.NONE;
+ currentlyGeneratingMethodSynchronization = GeneratedMethodSynchronizationBlock.NONE;
+ }
+
private DexEncodedMethod getMethod() {
return method.getDefinition();
}
@@ -272,7 +301,6 @@
@Override
public int traceInstruction(int instructionIndex, IRBuilder builder) {
CfInstruction instruction = code.getInstructions().get(instructionIndex);
- AppView<?> appView = builder.appView;
assert appView.options().isGeneratingClassFiles()
== internalOutputMode.isGeneratingClassFiles();
if (instruction.canThrow()) {
@@ -393,16 +421,30 @@
}
private boolean isCurrentlyGeneratingMethodSynchronization() {
- return currentlyGeneratingMethodSynchronization;
+ return currentlyGeneratingMethodSynchronization != GeneratedMethodSynchronizationBlock.NONE;
}
- private boolean isExceptionalExitForMethodSynchronization(int instructionIndex) {
- return instructionIndex == EXCEPTIONAL_SYNC_EXIT_OFFSET;
+ private boolean isCurrentlyGeneratingExceptionalMonitorExitForMethodSynchronization() {
+ return currentlyGeneratingMethodSynchronization
+ == GeneratedMethodSynchronizationBlock.EXCEPTIONAL_MONITOR_EXIT;
+ }
+
+ public static boolean isExceptionalMonitorExitForMethodSynchronization(int instructionIndex) {
+ return instructionIndex == EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET;
+ }
+
+ public static boolean isExceptionalThrowForMethodSynchronization(int instructionIndex) {
+ return instructionIndex == EXCEPTIONAL_SYNC_EXIT_THROW_OFFSET;
+ }
+
+ public static boolean isExceptionalExitForMethodSynchronization(int instructionIndex) {
+ return isExceptionalMonitorExitForMethodSynchronization(instructionIndex)
+ || isExceptionalThrowForMethodSynchronization(instructionIndex);
}
private void buildMethodEnterSynchronization(IRBuilder builder) {
assert needsGeneratedMethodSynchronization;
- currentlyGeneratingMethodSynchronization = true;
+ setCurrentlyGeneratingMethodSynchronization(GeneratedMethodSynchronizationBlock.METHOD_ENTER);
DexType type = method.getHolderType();
int monitorRegister;
if (getMethod().isStatic()) {
@@ -414,24 +456,30 @@
}
// Build the monitor enter and save it for when generating exits later.
monitorEnter = builder.addMonitor(MonitorType.ENTER, monitorRegister);
- currentlyGeneratingMethodSynchronization = false;
+ unsetCurrentlyGeneratingMethodSynchronization();
}
- private void buildExceptionalExitMethodSynchronization(IRBuilder builder) {
+ private void buildExceptionalExitForMethodSynchronization(
+ IRBuilder builder, int instructionIndex) {
assert needsGeneratedMethodSynchronization;
- currentlyGeneratingMethodSynchronization = true;
- state.setPosition(getCanonicalDebugPositionAtOffset(EXCEPTIONAL_SYNC_EXIT_OFFSET));
- builder.add(new Monitor(MonitorType.EXIT, monitorEnter.inValues().get(0)));
- builder.addThrow(getMoveExceptionRegister(0));
- currentlyGeneratingMethodSynchronization = false;
+ setCurrentlyGeneratingMethodSynchronization(
+ GeneratedMethodSynchronizationBlock.fromOffset(instructionIndex));
+ state.setPosition(getCanonicalDebugPositionAtOffset(instructionIndex));
+ if (isExceptionalMonitorExitForMethodSynchronization(instructionIndex)) {
+ builder.add(new Monitor(MonitorType.EXIT, monitorEnter.object()));
+ builder.addGoto(EXCEPTIONAL_SYNC_EXIT_THROW_OFFSET);
+ } else {
+ builder.addThrow(getMoveExceptionRegister(0));
+ }
+ unsetCurrentlyGeneratingMethodSynchronization();
}
@Override
public void buildPostlude(IRBuilder builder) {
if (needsGeneratedMethodSynchronization) {
- currentlyGeneratingMethodSynchronization = true;
- builder.add(new Monitor(MonitorType.EXIT, monitorEnter.inValues().get(0)));
- currentlyGeneratingMethodSynchronization = false;
+ setCurrentlyGeneratingMethodSynchronization(GeneratedMethodSynchronizationBlock.METHOD_EXIT);
+ builder.add(new Monitor(MonitorType.EXIT, monitorEnter.object()));
+ unsetCurrentlyGeneratingMethodSynchronization();
}
}
@@ -484,7 +532,7 @@
public void buildInstruction(
IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
if (isExceptionalExitForMethodSynchronization(instructionIndex)) {
- buildExceptionalExitMethodSynchronization(builder);
+ buildExceptionalExitForMethodSynchronization(builder, instructionIndex);
return;
}
CfInstruction instruction = code.getInstructions().get(instructionIndex);
@@ -507,7 +555,7 @@
if (instruction.canThrow()) {
Snapshot exceptionTransfer =
- state.getSnapshot().exceptionTransfer(builder.appView.dexItemFactory().throwableType);
+ state.getSnapshot().exceptionTransfer(appView.dexItemFactory().throwableType);
for (int target : currentBlockInfo.exceptionalSuccessors) {
recordStateForTarget(target, exceptionTransfer);
}
@@ -820,12 +868,19 @@
if (inPrelude) {
return null;
}
+ TryHandlerList tryHandlers;
if (isCurrentlyGeneratingMethodSynchronization()) {
- return null;
+ if (!isCurrentlyGeneratingExceptionalMonitorExitForMethodSynchronization()) {
+ return null;
+ }
+ // Ensure that the exceptional monitor-exit instruction is guarded by a catch-all handler that
+ // repeats the monitor-exit.
+ tryHandlers =
+ getTryHandlers(EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET, appView.dexItemFactory());
+ } else {
+ tryHandlers =
+ getTryHandlers(instructionOffset(currentInstructionIndex), appView.dexItemFactory());
}
- TryHandlerList tryHandlers =
- getTryHandlers(
- instructionOffset(currentInstructionIndex), builder.appView.dexItemFactory());
if (tryHandlers.isEmpty()) {
return null;
}
@@ -857,7 +912,7 @@
@Override
public Position getCanonicalDebugPositionAtOffset(int offset) {
- if (offset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
+ if (isExceptionalExitForMethodSynchronization(offset)) {
return canonicalPositions.getExceptionalExitPosition(
appView.options().debug,
() ->
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 8d03811..2c84747 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -14,6 +14,9 @@
import static com.android.tools.r8.ir.analysis.type.TypeElement.getNull;
import static com.android.tools.r8.ir.analysis.type.TypeElement.getSingle;
import static com.android.tools.r8.ir.analysis.type.TypeElement.getWide;
+import static com.android.tools.r8.ir.conversion.CfSourceCode.isExceptionalExitForMethodSynchronization;
+import static com.android.tools.r8.ir.conversion.CfSourceCode.isExceptionalMonitorExitForMethodSynchronization;
+import static com.android.tools.r8.ir.conversion.CfSourceCode.isExceptionalThrowForMethodSynchronization;
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.errors.CompilationError;
@@ -156,6 +159,14 @@
public static final int INITIAL_BLOCK_OFFSET = -1;
+ // The (synthetic) offset of the monitor-exit instruction on the exceptional exit, when desugaring
+ // declared synchronized methods.
+ public static final int EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET = -2;
+
+ // The (synthetic) offset of the throw instruction that follows the monitor-exit instruction on
+ // the exceptional exit, when desugaring declared synchronized methods.
+ public static final int EXCEPTIONAL_SYNC_EXIT_THROW_OFFSET = -3;
+
private static TypeElement fromMemberType(MemberType type) {
switch (type) {
case BOOLEAN_OR_BYTE:
@@ -378,6 +389,8 @@
@SuppressWarnings("JdkObsolete")
private final Queue<Integer> traceBlocksWorklist = new LinkedList<>();
+ private boolean processedExceptionalMonitorExitForMethodSynchronization;
+
// Bitmap to ensure we don't process an instruction more than once.
private boolean[] processedInstructions = null;
@@ -631,6 +644,9 @@
traceBlocksWorklist.add(0);
while (!traceBlocksWorklist.isEmpty()) {
int startOfBlockOffset = traceBlocksWorklist.remove();
+ if (handleExceptionalExitForMethodSynchronization(startOfBlockOffset)) {
+ continue;
+ }
int startOfBlockIndex = source.instructionIndex(startOfBlockOffset);
// Check that the block has not been processed after being added.
if (isIndexProcessed(startOfBlockIndex)) {
@@ -2470,14 +2486,24 @@
// Ensure there is a block starting at offset and add it to the work-list if it needs processing.
private BlockInfo ensureBlock(int offset) {
// We don't enqueue negative targets (these are special blocks, eg, an argument prelude).
- if (offset >= 0 && !isOffsetProcessed(offset)) {
+ if (!isOffsetProcessed(offset)) {
traceBlocksWorklist.add(offset);
}
return ensureBlockWithoutEnqueuing(offset);
}
private boolean isOffsetProcessed(int offset) {
- return isIndexProcessed(source.instructionIndex(offset));
+ if (offset >= 0) {
+ return isIndexProcessed(source.instructionIndex(offset));
+ }
+ if (isExceptionalMonitorExitForMethodSynchronization(offset)) {
+ return isExceptionalMonitorExitForMethodSynchronizationProcessed();
+ } else {
+ // We never need to process this block since it has no successors and its predecessor is set
+ // up when processing the monitor-exit block.
+ assert isExceptionalThrowForMethodSynchronization(offset);
+ return true;
+ }
}
private boolean isIndexProcessed(int index) {
@@ -2498,6 +2524,34 @@
processedSubroutineInstructions.add(index);
}
+ private boolean handleExceptionalExitForMethodSynchronization(int startOfBlockOffset) {
+ if (!isExceptionalExitForMethodSynchronization(startOfBlockOffset)) {
+ return false;
+ }
+ // We never need to process the throw block. We therefore treat it as always processed, meaning
+ // it should never be traced here.
+ assert isExceptionalMonitorExitForMethodSynchronization(startOfBlockOffset);
+ if (markExceptionalMonitorExitForMethodSynchronizationProcessed()) {
+ ensureNormalSuccessorBlock(
+ EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET, EXCEPTIONAL_SYNC_EXIT_THROW_OFFSET);
+ ensureExceptionalSuccessorBlock(
+ EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET, EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET);
+ }
+ return true;
+ }
+
+ private boolean isExceptionalMonitorExitForMethodSynchronizationProcessed() {
+ return processedExceptionalMonitorExitForMethodSynchronization;
+ }
+
+ private boolean markExceptionalMonitorExitForMethodSynchronizationProcessed() {
+ if (isExceptionalMonitorExitForMethodSynchronizationProcessed()) {
+ return false;
+ }
+ processedExceptionalMonitorExitForMethodSynchronization = true;
+ return true;
+ }
+
private void ensureSubroutineProcessedInstructions() {
if (processedSubroutineInstructions == null) {
processedSubroutineInstructions = new HashSet<>();
@@ -2732,4 +2786,3 @@
return builder.toString();
}
}
-
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java
index dea79c9..052c9ad 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java
@@ -408,10 +408,11 @@
},
fieldPattern ->
holder.forEachProgramFieldMatching(
- f -> predicates.matchesField(f, fieldPattern), continueWithMember),
+ f -> predicates.matchesField(f, fieldPattern, appInfo), continueWithMember),
methodPattern ->
holder.forEachProgramMethodMatching(
- m -> predicates.matchesMethod(m, methodPattern), continueWithMember));
+ m -> predicates.matchesMethod(m, methodPattern, appInfo),
+ continueWithMember));
if (didContinue.isFalse()) {
// No match for the member pattern existed, continue with next class.
continueWithNoClassClearingMembers(
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java
index 0982747..dfe3f51 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -84,7 +85,16 @@
}
private boolean matchesInstanceOfPattern(
- DexProgramClass clazz, KeepInstanceOfPattern pattern, AppInfoWithClassHierarchy appInfo) {
+ DexType type, KeepInstanceOfPattern pattern, AppInfoWithClassHierarchy appInfo) {
+ DexClass dexClass = appInfo.definitionFor(type);
+ if (dexClass != null) {
+ return matchesInstanceOfPattern(dexClass.asProgramClass(), pattern, appInfo);
+ }
+ return false;
+ }
+
+ private boolean matchesInstanceOfPattern(
+ DexClass clazz, KeepInstanceOfPattern pattern, AppInfoWithClassHierarchy appInfo) {
if (pattern.isAny()) {
return true;
}
@@ -115,23 +125,25 @@
&& matchesGeneralMemberAccess(member.getAccessFlags(), pattern.getAccessPattern());
}
- public boolean matchesMethod(DexEncodedMethod method, KeepMethodPattern pattern) {
+ public boolean matchesMethod(
+ DexEncodedMethod method, KeepMethodPattern pattern, AppInfoWithClassHierarchy appInfo) {
if (pattern.isAnyMethod()) {
return true;
}
return matchesString(method.getName(), pattern.getNamePattern().asStringPattern())
- && matchesReturnType(method.getReturnType(), pattern.getReturnTypePattern())
- && matchesParameters(method.getParameters(), pattern.getParametersPattern())
+ && matchesReturnType(method.getReturnType(), pattern.getReturnTypePattern(), appInfo)
+ && matchesParameters(method.getParameters(), pattern.getParametersPattern(), appInfo)
&& matchesAnnotatedBy(method.annotations(), pattern.getAnnotatedByPattern())
&& matchesMethodAccess(method.getAccessFlags(), pattern.getAccessPattern());
}
- public boolean matchesField(DexEncodedField field, KeepFieldPattern pattern) {
+ public boolean matchesField(
+ DexEncodedField field, KeepFieldPattern pattern, AppInfoWithClassHierarchy appInfo) {
if (pattern.isAnyField()) {
return true;
}
return matchesString(field.getName(), pattern.getNamePattern().asStringPattern())
- && matchesType(field.getType(), pattern.getTypePattern().asType())
+ && matchesType(field.getType(), pattern.getTypePattern().asType(), appInfo)
&& matchesAnnotatedBy(field.annotations(), pattern.getAnnotatedByPattern())
&& matchesFieldAccess(field.getAccessFlags(), pattern.getAccessPattern());
}
@@ -211,7 +223,10 @@
return false;
}
- public boolean matchesParameters(DexTypeList parameters, KeepMethodParametersPattern pattern) {
+ public boolean matchesParameters(
+ DexTypeList parameters,
+ KeepMethodParametersPattern pattern,
+ AppInfoWithClassHierarchy appInfo) {
if (pattern.isAny()) {
return true;
}
@@ -221,7 +236,7 @@
}
int size = parameters.size();
for (int i = 0; i < size; i++) {
- if (!matchesType(parameters.get(i), patternList.get(i))) {
+ if (!matchesType(parameters.get(i), patternList.get(i), appInfo)) {
return false;
}
}
@@ -263,29 +278,34 @@
}
public boolean matchesReturnType(
- DexType returnType, KeepMethodReturnTypePattern returnTypePattern) {
+ DexType returnType,
+ KeepMethodReturnTypePattern returnTypePattern,
+ AppInfoWithClassHierarchy appInfo) {
if (returnTypePattern.isAny()) {
return true;
}
if (returnTypePattern.isVoid()) {
return returnType.isVoidType();
}
- return matchesType(returnType, returnTypePattern.asType());
+ return matchesType(returnType, returnTypePattern.asType(), appInfo);
}
- public boolean matchesType(DexType type, KeepTypePattern pattern) {
+ public boolean matchesType(
+ DexType type, KeepTypePattern pattern, AppInfoWithClassHierarchy appInfo) {
return pattern.apply(
() -> true,
p -> matchesPrimitiveType(type, p),
- p -> matchesArrayType(type, p),
- p -> matchesClassType(type, p));
+ p -> matchesArrayType(type, p, appInfo),
+ p -> matchesClassType(type, p),
+ p -> matchesInstanceOfPattern(type, p, appInfo));
}
public boolean matchesClassType(DexType type, KeepQualifiedClassNamePattern pattern) {
return type.isClassType() && matchesClassName(type, pattern);
}
- public boolean matchesArrayType(DexType type, KeepArrayTypePattern pattern) {
+ public boolean matchesArrayType(
+ DexType type, KeepArrayTypePattern pattern, AppInfoWithClassHierarchy appInfo) {
if (!type.isArrayType()) {
return false;
}
@@ -296,7 +316,9 @@
return false;
}
return matchesType(
- type.toArrayElementAfterDimension(pattern.getDimensions(), factory), pattern.getBaseType());
+ type.toArrayElementAfterDimension(pattern.getDimensions(), factory),
+ pattern.getBaseType(),
+ appInfo);
}
public boolean matchesPrimitiveType(DexType type, KeepPrimitiveTypePattern pattern) {
diff --git a/src/main/java/com/android/tools/r8/utils/CfUtils.java b/src/main/java/com/android/tools/r8/utils/CfUtils.java
new file mode 100644
index 0000000..52911b4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/CfUtils.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2024, 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.utils;
+
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
+
+import java.io.IOException;
+import java.io.InputStream;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+
+public class CfUtils {
+ private static class ClassNameExtractor extends ClassVisitor {
+ private String className;
+
+ private ClassNameExtractor() {
+ super(ASM_VERSION);
+ }
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ className = name;
+ }
+
+ String getClassInternalType() {
+ return className;
+ }
+ }
+
+ public static String extractClassName(byte[] ccc) {
+ return DescriptorUtils.getJavaTypeFromBinaryName(
+ extractClassInternalType(new ClassReader(ccc)));
+ }
+
+ public static String extractClassDescriptor(byte[] ccc) {
+ return "L" + extractClassInternalType(new ClassReader(ccc)) + ";";
+ }
+
+ public static String extractClassDescriptor(InputStream input) throws IOException {
+ return DescriptorUtils.getDescriptorFromClassBinaryName(
+ extractClassInternalType(new ClassReader(input)));
+ }
+
+ private static String extractClassInternalType(ClassReader reader) {
+ ClassNameExtractor extractor = new ClassNameExtractor();
+ reader.accept(
+ extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+ return extractor.getClassInternalType();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 91fbada..b3d395b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils;
import static com.android.tools.r8.utils.AndroidApiLevel.B;
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
import com.android.tools.r8.AndroidResourceConsumer;
@@ -2025,6 +2026,31 @@
public boolean enableLibraryApiModeling =
System.getProperty("com.android.tools.r8.disableApiModeling") == null;
+ // Flag to specify Android extension libraries (also known as OEM-implemented shared libraries
+ // or sidecars). All APIs within these libraries are handled as having an API level
+ // higher than any existing API level as these APIs might not exist on any device independent
+ // of API level (the nature of an extension API).
+ public String androidApiExtensionLibraries =
+ System.getProperty("com.android.tools.r8.androidApiExtensionLibraries");
+
+ public void forEachAndroidApiExtensionClassDescriptor(Consumer<String> consumer) {
+ if (androidApiExtensionLibraries != null) {
+ StringUtils.split(androidApiExtensionLibraries, ',')
+ .forEach(
+ lib -> {
+ try {
+ ZipUtils.iter(
+ Paths.get(lib),
+ (entry, input) -> consumer.accept(extractClassDescriptor(input)));
+ } catch (IOException e) {
+ throw new CompilationError("Failed to read extension library " + lib, e);
+ }
+ });
+ }
+ }
+
+ // TODO(b/326252366): Remove support for list of extension packages in favour of only
+ // supporting passing extension libraries as JAR files.
// Flag to specify packages for Android extension APIs (also known as OEM-implemented
// shared libraries or sidecars). The packages are specified as java package names
// separated by commas. All APIs within these packages are handled as having an API level
@@ -2035,6 +2061,12 @@
public String androidApiExtensionPackages =
System.getProperty("com.android.tools.r8.androidApiExtensionPackages");
+ public void forEachAndroidApiExtensionPackage(Consumer<String> consumer) {
+ if (androidApiExtensionPackages != null) {
+ StringUtils.split(androidApiExtensionPackages, ',').forEach(consumer);
+ }
+ }
+
// The flag enableApiCallerIdentification controls if we can inline or merge targets with
// different api levels. It is also the flag that specifies if we assign api levels to
// references.
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
index e1fea1f..f6583d2 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
@@ -95,6 +95,10 @@
this.staticizedMethods = staticizedMethods;
}
+ public boolean hasBeenMerged(DexType type) {
+ return mergedClasses.hasBeenMergedIntoSubtype(type);
+ }
+
public boolean hasInterfaceBeenMergedIntoClass(DexType type) {
return mergedClasses.hasInterfaceBeenMergedIntoClass(type);
}
diff --git a/src/test/examplesJava11/nesthostexample/NestOnProgramAndClasspathAndLibraryTest.java b/src/test/examplesJava11/nesthostexample/NestOnProgramAndClasspathAndLibraryTest.java
new file mode 100644
index 0000000..767b7df
--- /dev/null
+++ b/src/test/examplesJava11/nesthostexample/NestOnProgramAndClasspathAndLibraryTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2024, 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 nesthostexample;
+
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NestOnProgramAndClasspathAndLibraryTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean alsoOnClasspath;
+
+ @Parameters(name = "{0}, alsoOnClasspath: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+ }
+
+ @Test
+ public void testD8MethodBridges() {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ // 1 inner class.
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ compileClassesWithD8ProgramClasses(
+ BasicNestHostWithInnerClassMethods.BasicNestedClass.class,
+ true,
+ BasicNestHostWithInnerClassMethods.class));
+ // Outer class.
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ compileClassesWithD8ProgramClasses(
+ BasicNestHostWithInnerClassMethods.class,
+ false,
+ BasicNestHostWithInnerClassMethods.class));
+ // 2 inner classes.
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ compileClassesWithD8ProgramClasses(
+ NestHostExample.StaticNestMemberInner.class, true, NestHostExample.class));
+ }
+
+ @Test
+ public void testD8ConstructorBridges() {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ compileClassesWithD8ProgramClasses(
+ BasicNestHostWithInnerClassConstructors.BasicNestedClass.class,
+ true,
+ BasicNestHostWithInnerClassConstructors.class));
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ compileClassesWithD8ProgramClasses(
+ BasicNestHostWithInnerClassConstructors.class,
+ false,
+ BasicNestHostWithInnerClassConstructors.class));
+ }
+
+ private void compileClassesWithD8ProgramClasses(
+ Class<?> clazz, boolean withInnerClasses, Class<?> outer) throws Exception {
+ Path nestZip = buildNestZip(outer);
+ testForD8()
+ .setMinApi(parameters)
+ .addProgramClasses(clazz)
+ .applyIf(withInnerClasses, b -> b.addInnerClasses(clazz))
+ .applyIf(alsoOnClasspath, b -> b.addClasspathFiles(nestZip))
+ .addLibraryFiles(nestZip)
+ .compile();
+ Files.delete(nestZip);
+ }
+
+ private Path buildNestZip(Class<?> outer) throws IOException {
+ List<Path> nest = new ArrayList<>();
+ nest.add(ToolHelper.getClassFileForTestClass(outer));
+ nest.addAll(ToolHelper.getClassFilesForInnerClasses(outer));
+ Path zip = temp.getRoot().toPath().resolve("nest.zip");
+ ZipBuilder zp = ZipBuilder.builder(zip);
+ nest.forEach(
+ f -> {
+ try {
+ zp.addFile(f.getFileName().toString(), f);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ return zp.build();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
index 4bdba09..58a8ac6 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
@@ -48,6 +48,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.stream.Collectors;
public class AndroidApiHashingDatabaseBuilderGenerator extends TestBase {
@@ -199,7 +200,31 @@
expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/NfcB;"));
expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/Ndef;"));
expectedMissingMembers.add(factory.createType("Landroid/webkit/CookieSyncManager;"));
- assertEquals(expectedMissingMembers, missingMemberInformation.keySet());
+ expectedMissingMembers.add(
+ factory.createType("Landroid/adservices/customaudience/CustomAudienceManager;"));
+ expectedMissingMembers.add(
+ factory.createType("Landroid/adservices/customaudience/PartialCustomAudience$Builder;"));
+ expectedMissingMembers.add(
+ factory.createType("Landroid/adservices/customaudience/PartialCustomAudience;"));
+ expectedMissingMembers.add(
+ factory.createType(
+ "Landroid/adservices/customaudience/ScheduleCustomAudienceUpdateRequest$Builder;"));
+ expectedMissingMembers.add(
+ factory.createType(
+ "Landroid/adservices/customaudience/ScheduleCustomAudienceUpdateRequest;"));
+ expectedMissingMembers.add(factory.createType("Landroid/app/appsearch/AppSearchResult;"));
+ assertEquals(
+ expectedMissingMembers.stream()
+ .map(DexType::toDescriptorString)
+ .sorted()
+ .collect(Collectors.joining("\n"))
+ + "\n---\n"
+ + missingMemberInformation.keySet().stream()
+ .map(DexType::toDescriptorString)
+ .sorted()
+ .collect(Collectors.joining("\n")),
+ expectedMissingMembers,
+ missingMemberInformation.keySet());
return true;
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
index 1244c0f..0521f40 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.apimodel;
+import static com.android.tools.r8.androidapi.AndroidApiLevelDatabaseHelper.notModeledFields;
+import static com.android.tools.r8.androidapi.AndroidApiLevelDatabaseHelper.notModeledMethods;
import static com.android.tools.r8.androidapi.AndroidApiLevelDatabaseHelper.notModeledTypes;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.junit.Assert.assertEquals;
@@ -110,9 +112,9 @@
methodReferences.forEach(field -> numberOfMethods.increment())));
});
// These numbers will change when updating api-versions.xml
- assertEquals(5971, parsedApiClasses.size());
+ assertEquals(5972, parsedApiClasses.size());
assertEquals(30341, numberOfFields.get());
- assertEquals(46572, numberOfMethods.get());
+ assertEquals(46576, numberOfMethods.get());
}
@Test
@@ -134,6 +136,8 @@
private static void ensureAllPublicMethodsAreMapped(
AppView<AppInfoWithClassHierarchy> appView, AndroidApiLevelCompute apiLevelCompute) {
Set<String> notModeledTypes = notModeledTypes();
+ Set<String> notModeledFields = notModeledFields();
+ Set<String> notModeledMethods = notModeledMethods();
for (DexLibraryClass clazz : appView.app().asDirect().libraryClasses()) {
if (notModeledTypes.contains(clazz.getClassReference().getTypeName())) {
continue;
@@ -144,7 +148,9 @@
.isKnownApiLevel());
clazz.forEachClassField(
field -> {
- if (field.getAccessFlags().isPublic() && !field.toSourceString().contains("this$0")) {
+ if (field.getAccessFlags().isPublic()
+ && !field.toSourceString().contains("this$0")
+ && !notModeledFields.contains(field.toSourceString())) {
assertTrue(
apiLevelCompute
.computeApiLevelForLibraryReference(field.getReference())
@@ -153,7 +159,8 @@
});
clazz.forEachClassMethod(
method -> {
- if (method.getAccessFlags().isPublic()) {
+ if (method.getAccessFlags().isPublic()
+ && !notModeledMethods.contains(method.toSourceString())) {
assertTrue(
apiLevelCompute
.computeApiLevelForLibraryReference(method.getReference())
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
index 7617ca1..e87664f 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -53,13 +53,11 @@
private Set<String> getDeletedTypesMissingRemovedAttribute() {
Set<String> removedTypeNames = new HashSet<>();
if (maxApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.U)) {
- removedTypeNames.add("com.android.internal.util.Predicate");
+ if (maxApiLevel.isLessThan(AndroidApiLevel.V)) {
+ removedTypeNames.add("com.android.internal.util.Predicate");
+ }
removedTypeNames.add("android.adservices.AdServicesVersion");
}
- if (maxApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.V)) {
- removedTypeNames.add("android.media.MediaDrm$HdcpLevel");
- removedTypeNames.add("android.media.MediaDrm$SecurityLevel");
- }
return removedTypeNames;
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiMultipleExtensionPackagesTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiMultipleExtensionPackagesTest.java
index eebab11..ce3b3d2 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiMultipleExtensionPackagesTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiMultipleExtensionPackagesTest.java
@@ -13,11 +13,15 @@
import com.android.tools.r8.TestCompileResult;
import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.apimodel.another_extension.AnotherExtensionApiLibraryClass;
import com.android.tools.r8.apimodel.extension.ExtensionApiLibraryClass;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -32,29 +36,50 @@
"ExtensionApiLibraryClass::extensionApi",
"AnotherExtensionApiLibraryClass::extensionApi");
- @Parameter public TestParameters parameters;
+ @Parameter(0)
+ public TestParameters parameters;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ @Parameter(1)
+ public boolean useExtensionJar;
+
+ @Parameters(name = "{0}, useExtensionJar = {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ private static final List<Class<?>> extensionLibraryClasses =
+ ImmutableList.of(ExtensionApiLibraryClass.class, AnotherExtensionApiLibraryClass.class);
+ private static Path extensionLibraryJar;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ extensionLibraryJar =
+ zipWithTestClasses(
+ getStaticTemp().newFolder().toPath().resolve("extension.jar"), extensionLibraryClasses);
}
private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
testBuilder
- .addLibraryClasses(ExtensionApiLibraryClass.class, AnotherExtensionApiLibraryClass.class)
+ .addLibraryFiles(extensionLibraryJar)
.addDefaultRuntimeLibrary(parameters)
.addInnerClasses(getClass())
.setMinApi(parameters)
.addOptionsModification(
options -> {
- String typeName = ExtensionApiLibraryClass.class.getTypeName();
- String anotherTypeName = AnotherExtensionApiLibraryClass.class.getTypeName();
- options.apiModelingOptions().androidApiExtensionPackages =
- typeName.substring(0, typeName.lastIndexOf('.'))
- + ','
- + anotherTypeName.substring(0, anotherTypeName.lastIndexOf('.'))
- + ','
- + typeName.substring(0, typeName.lastIndexOf('.'));
+ if (useExtensionJar) {
+ options.apiModelingOptions().androidApiExtensionLibraries =
+ extensionLibraryJar.toString();
+ } else {
+ String typeName = ExtensionApiLibraryClass.class.getTypeName();
+ String anotherTypeName = AnotherExtensionApiLibraryClass.class.getTypeName();
+ options.apiModelingOptions().androidApiExtensionPackages =
+ typeName.substring(0, typeName.lastIndexOf('.'))
+ + ','
+ + anotherTypeName.substring(0, anotherTypeName.lastIndexOf('.'))
+ + ','
+ + typeName.substring(0, typeName.lastIndexOf('.'));
+ }
})
// TODO(b/213552119): Remove when enabled by default.
.apply(ApiModelingTestHelper::enableApiCallerIdentification)
@@ -63,8 +88,7 @@
}
private void populateBootClasspath(TestCompileResult<?, ?> compilerResult) throws Exception {
- compilerResult.addBootClasspathClasses(
- ExtensionApiLibraryClass.class, AnotherExtensionApiLibraryClass.class);
+ compilerResult.addBootClasspathClasses(extensionLibraryClasses);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiSubpackageTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiSubpackageTest.java
index d40af72..9fbdd53 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiSubpackageTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiSubpackageTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.TestBase;
@@ -18,7 +19,9 @@
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
import java.util.List;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -42,36 +45,75 @@
@Parameter(2)
public boolean subpackageIsExtension;
- @Parameters(name = "{0}, packageIsExtension = {1}, subpackageIsExtension = {2}")
+ @Parameter(3)
+ public boolean useExtensionJar;
+
+ @Parameters(
+ name = "{0}, packageIsExtension = {1}, subpackageIsExtension = {2}, useExtensionJar = {3}")
public static List<Object[]> data() {
return buildParameters(
getTestParameters().withAllRuntimesAndApiLevels().build(),
BooleanUtils.values(),
+ BooleanUtils.values(),
BooleanUtils.values());
}
+ private static Path extensionLibraryJar;
+ private static Path subPackagExtensionLibraryJar;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ extensionLibraryJar =
+ zipWithTestClasses(
+ getStaticTemp().newFolder().toPath().resolve("extension.jar"),
+ ExtensionApiLibraryClass.class);
+ subPackagExtensionLibraryJar =
+ zipWithTestClasses(
+ getStaticTemp().newFolder().toPath().resolve("sub_package_extension.jar"),
+ SubpackageExtensionApiLibraryClass.class);
+ }
+
private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
testBuilder
- .addLibraryClasses(ExtensionApiLibraryClass.class, SubpackageExtensionApiLibraryClass.class)
+ .addLibraryFiles(extensionLibraryJar, subPackagExtensionLibraryJar)
.addDefaultRuntimeLibrary(parameters)
.addInnerClasses(getClass())
.setMinApi(parameters)
.addOptionsModification(
options -> {
- StringBuilder androidApiExtensionPackages = new StringBuilder();
- if (packageIsExtension) {
- String typeName = ExtensionApiLibraryClass.class.getTypeName();
- androidApiExtensionPackages.append(typeName, 0, typeName.lastIndexOf('.'));
- }
- if (subpackageIsExtension) {
- String typeName = SubpackageExtensionApiLibraryClass.class.getTypeName();
+ if (useExtensionJar) {
+ StringBuilder androidApiExtensionLibraries = new StringBuilder();
if (packageIsExtension) {
- androidApiExtensionPackages.append(",");
+ androidApiExtensionLibraries.append(extensionLibraryJar);
}
- androidApiExtensionPackages.append(typeName, 0, typeName.lastIndexOf('.'));
+ if (subpackageIsExtension) {
+ if (packageIsExtension) {
+ androidApiExtensionLibraries.append(",");
+ }
+ androidApiExtensionLibraries.append(subPackagExtensionLibraryJar);
+ }
+ if (packageIsExtension || subpackageIsExtension) {
+ options.apiModelingOptions().androidApiExtensionLibraries =
+ androidApiExtensionLibraries.toString();
+ } else {
+ assertTrue(androidApiExtensionLibraries.toString().isEmpty());
+ }
+ } else {
+ StringBuilder androidApiExtensionPackages = new StringBuilder();
+ if (packageIsExtension) {
+ String typeName = ExtensionApiLibraryClass.class.getTypeName();
+ androidApiExtensionPackages.append(typeName, 0, typeName.lastIndexOf('.'));
+ }
+ if (subpackageIsExtension) {
+ String typeName = SubpackageExtensionApiLibraryClass.class.getTypeName();
+ if (packageIsExtension) {
+ androidApiExtensionPackages.append(",");
+ }
+ androidApiExtensionPackages.append(typeName, 0, typeName.lastIndexOf('.'));
+ }
+ options.apiModelingOptions().androidApiExtensionPackages =
+ androidApiExtensionPackages.toString();
}
- options.apiModelingOptions().androidApiExtensionPackages =
- androidApiExtensionPackages.toString();
})
// TODO(b/213552119): Remove when enabled by default.
.apply(ApiModelingTestHelper::enableApiCallerIdentification)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiTest.java
index 6057fab..8feeec5 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiTest.java
@@ -14,13 +14,17 @@
import com.android.tools.r8.TestCompileResult;
import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.apimodel.extension.ExtensionApiLibraryClass;
import com.android.tools.r8.apimodel.extension.ExtensionApiLibraryInterface;
import com.android.tools.r8.apimodel.extension.ExtensionApiLibraryInterfaceImpl;
import com.android.tools.r8.apimodel.extension.ExtensionApiLibraryInterfaceProvider;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -35,27 +39,48 @@
"ExtensionApiLibraryClass::extensionApi",
"ExtensionApiLibraryInterfaceImpl::extensionApi");
- @Parameter public TestParameters parameters;
+ @Parameter(0)
+ public TestParameters parameters;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ @Parameter(1)
+ public boolean useExtensionJar;
+
+ @Parameters(name = "{0}, useExtensionJar = {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ private static final List<Class<?>> extensionLibraryClasses =
+ ImmutableList.of(
+ ExtensionApiLibraryClass.class,
+ ExtensionApiLibraryInterface.class,
+ ExtensionApiLibraryInterfaceProvider.class);
+ private static Path extensionLibraryJar;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ extensionLibraryJar =
+ zipWithTestClasses(
+ getStaticTemp().newFolder().toPath().resolve("extension.jar"), extensionLibraryClasses);
}
private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
testBuilder
- .addLibraryClasses(
- ExtensionApiLibraryClass.class,
- ExtensionApiLibraryInterface.class,
- ExtensionApiLibraryInterfaceProvider.class)
+ .addLibraryFiles(extensionLibraryJar)
.addDefaultRuntimeLibrary(parameters)
.addInnerClasses(getClass())
.setMinApi(parameters)
.addOptionsModification(
options -> {
- String typeName = ExtensionApiLibraryClass.class.getTypeName();
- options.apiModelingOptions().androidApiExtensionPackages =
- typeName.substring(0, typeName.lastIndexOf('.'));
+ if (useExtensionJar) {
+ options.apiModelingOptions().androidApiExtensionLibraries =
+ extensionLibraryJar.toString();
+ } else {
+ String typeName = ExtensionApiLibraryClass.class.getTypeName();
+ options.apiModelingOptions().androidApiExtensionPackages =
+ typeName.substring(0, typeName.lastIndexOf('.'));
+ }
})
// TODO(b/213552119): Remove when enabled by default.
.apply(ApiModelingTestHelper::enableApiCallerIdentification)
@@ -64,11 +89,9 @@
}
private void populateBootClasspath(TestCompileResult<?, ?> compilerResult) throws Exception {
- compilerResult.addBootClasspathClasses(
- ExtensionApiLibraryClass.class,
- ExtensionApiLibraryInterface.class,
- ExtensionApiLibraryInterfaceImpl.class,
- ExtensionApiLibraryInterfaceProvider.class);
+ compilerResult
+ .addBootClasspathClasses(extensionLibraryClasses)
+ .addBootClasspathClasses(ExtensionApiLibraryInterfaceImpl.class);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldReferenceCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldReferenceCollisionTest.java
new file mode 100644
index 0000000..2bb3e18
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldReferenceCollisionTest.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2024, 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NonReboundFieldReferenceCollisionTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertMergedInto(C.class, B.class).assertNoOtherClassesMerged())
+ // The minifier may assign different names to A.f and B.f, so disable obfuscation so that we
+ // can check that the two fields have the same name through out the compilation.
+ .addDontObfuscate()
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+
+ FieldSubject shadowedFieldSubject = aClassSubject.uniqueFieldWithOriginalName("f");
+ assertThat(shadowedFieldSubject, isPresent());
+
+ ClassSubject bClassSubject = inspector.clazz(B.class);
+ assertThat(bClassSubject, isPresent());
+
+ FieldSubject shadowingFieldSubject = bClassSubject.uniqueFieldWithOriginalName("f");
+ assertThat(shadowingFieldSubject, isPresent());
+ assertEquals(
+ shadowingFieldSubject.getFinalName(), shadowedFieldSubject.getFinalName());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("1", "2", "3", "4");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ B b = System.currentTimeMillis() > 0 ? new B(one(), two()) : new B(three(), four());
+ System.out.println(b.f());
+ System.out.println(b.f);
+ C c = System.currentTimeMillis() > 0 ? new C(three(), four()) : new C(one(), two());
+ System.out.println(c.f);
+ System.out.println(c.g);
+ }
+
+ static int one() {
+ return 1;
+ }
+
+ static int two() {
+ return 2;
+ }
+
+ static int three() {
+ return 3;
+ }
+
+ static int four() {
+ return 4;
+ }
+ }
+
+ @NoVerticalClassMerging
+ abstract static class A {
+
+ int f;
+
+ @NeverInline
+ A(int f) {
+ this.f = f;
+ }
+
+ int f() {
+ return f;
+ }
+ }
+
+ @NeverClassInline
+ static class B extends A {
+
+ int f;
+
+ @NeverInline
+ B(int f, int g) {
+ super(f);
+ this.f = g;
+ }
+ }
+
+ @NeverClassInline
+ static class C extends A {
+
+ int g;
+
+ @NeverInline
+ C(int f, int g) {
+ super(f);
+ this.g = g;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java
index f22226d..93c2734 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java
@@ -48,11 +48,13 @@
}
private void inspectVerticallyMergedClasses(VerticallyMergedClassesInspector inspector) {
- inspector.assertMergedIntoSubtype(
- GenericInterface.class,
- GenericAbstractClass.class,
- Outer.SuperClass.class,
- SuperClass.class);
+ inspector
+ .assertMergedIntoSubtype(
+ GenericInterface.class,
+ GenericAbstractClass.class,
+ Outer.SuperClass.class,
+ SuperClass.class)
+ .assertNoOtherClassesMerged();
}
private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ShadowedNonReboundFieldVerticalClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ShadowedNonReboundFieldVerticalClassMergingTest.java
new file mode 100644
index 0000000..27e03c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ShadowedNonReboundFieldVerticalClassMergingTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2024, 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.classmerging.vertical;
+
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ShadowedNonReboundFieldVerticalClassMergingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("1", "2", "2");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(B.class).assertNoOtherClassesMerged())
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("1", "2", "2");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ C c = System.currentTimeMillis() > 0 ? new C(1, 2) : new C(3, 4);
+ System.out.println(c.f);
+ System.out.println(c.a());
+ System.out.println(c.b());
+ }
+ }
+
+ @NoVerticalClassMerging
+ static class A {
+
+ int f;
+
+ A(int f) {
+ this.f = f;
+ }
+
+ int a() {
+ return f;
+ }
+ }
+
+ static class B extends A {
+
+ B(int f) {
+ super(f);
+ }
+
+ int b() {
+ return f;
+ }
+ }
+
+ static class C extends B {
+
+ int f;
+
+ C(int f, int g) {
+ super(g);
+ this.f = f;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java b/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java
index 460bdd7..fadd1f6 100644
--- a/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.debug;
+import static com.android.tools.r8.utils.CfUtils.extractClassName;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.ByteDataView;
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 7fcd792..4c2c44a 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.utils.CfUtils.extractClassName;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static java.util.stream.Collectors.toList;
import static org.hamcrest.CoreMatchers.containsString;
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClasspathAndLibraryPathTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClasspathAndLibraryPathTest.java
deleted file mode 100644
index c9cf2e3..0000000
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClasspathAndLibraryPathTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2024, 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.desugar.nestaccesscontrol;
-
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASSES_PATH;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASS_NAMES;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.JAR;
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
-import static java.util.stream.Collectors.toList;
-import static org.hamcrest.core.StringContains.containsString;
-import static org.hamcrest.core.StringEndsWith.endsWith;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import java.nio.file.Path;
-import java.util.List;
-import org.hamcrest.Matcher;
-import org.junit.Assume;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class NestOnProgramAndClasspathAndLibraryPathTest extends TestBase {
-
- public NestOnProgramAndClasspathAndLibraryPathTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- private final TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withDexRuntimes().withAllApiLevels().build();
- }
-
- @Test
- public void testD8MethodBridges() {
- Assume.assumeTrue(parameters.isDexRuntime());
- // 1 inner class.
- assertThrows(
- CompilationFailedException.class,
- () ->
- compileClassesWithD8ProgramClassesMatching(
- containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass")));
- // Outer class.
- assertThrows(
- CompilationFailedException.class,
- () ->
- compileClassesWithD8ProgramClassesMatching(
- endsWith("BasicNestHostWithInnerClassMethods")));
- // 2 inner classes.
- assertThrows(
- CompilationFailedException.class,
- () ->
- compileClassesWithD8ProgramClassesMatching(
- containsString("NestHostExample$StaticNestMemberInner")));
- }
-
- @Test
- public void testD8ConstructorBridges() {
- Assume.assumeTrue(parameters.isDexRuntime());
- assertThrows(
- CompilationFailedException.class,
- () ->
- compileClassesWithD8ProgramClassesMatching(
- containsString("BasicNestHostWithInnerClassConstructors$BasicNestedClass")));
- assertThrows(
- CompilationFailedException.class,
- () ->
- compileClassesWithD8ProgramClassesMatching(
- endsWith("BasicNestHostWithInnerClassConstructors")));
- }
-
- private D8TestCompileResult compileClassesWithD8ProgramClassesMatching(Matcher<String> matcher)
- throws Exception {
- List<Path> matchingClasses =
- CLASS_NAMES.stream()
- .filter(matcher::matches)
- .map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION))
- .collect(toList());
- return testForD8()
- .setMinApi(parameters)
- .addProgramFiles(matchingClasses)
- .addClasspathFiles(JAR)
- .addLibraryFiles(JAR)
- .compile();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndLibraryPathTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndLibraryPathTest.java
deleted file mode 100644
index 74c344e..0000000
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndLibraryPathTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2024, 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.desugar.nestaccesscontrol;
-
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASSES_PATH;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASS_NAMES;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.JAR;
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
-import static java.util.stream.Collectors.toList;
-import static org.hamcrest.core.StringContains.containsString;
-import static org.hamcrest.core.StringEndsWith.endsWith;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import java.nio.file.Path;
-import java.util.List;
-import org.hamcrest.Matcher;
-import org.junit.Assume;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class NestOnProgramAndLibraryPathTest extends TestBase {
-
- public NestOnProgramAndLibraryPathTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- private final TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withDexRuntimes().withAllApiLevels().build();
- }
-
- @Test
- public void testD8MethodBridges() {
- Assume.assumeTrue(parameters.isDexRuntime());
- // 1 inner class.
- assertThrows(
- CompilationFailedException.class,
- () ->
- compileClassesWithD8ProgramClassesMatching(
- containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass")));
- // Outer class.
- assertThrows(
- CompilationFailedException.class,
- () ->
- compileClassesWithD8ProgramClassesMatching(
- endsWith("BasicNestHostWithInnerClassMethods")));
- // 2 inner classes.
- assertThrows(
- CompilationFailedException.class,
- () ->
- compileClassesWithD8ProgramClassesMatching(
- containsString("NestHostExample$StaticNestMemberInner")));
- }
-
- @Test
- public void testD8ConstructorBridges() {
- Assume.assumeTrue(parameters.isDexRuntime());
- assertThrows(
- CompilationFailedException.class,
- () ->
- compileClassesWithD8ProgramClassesMatching(
- containsString("BasicNestHostWithInnerClassConstructors$BasicNestedClass")));
- assertThrows(
- CompilationFailedException.class,
- () ->
- compileClassesWithD8ProgramClassesMatching(
- endsWith("BasicNestHostWithInnerClassConstructors")));
- }
-
- private D8TestCompileResult compileClassesWithD8ProgramClassesMatching(Matcher<String> matcher)
- throws Exception {
- List<Path> matchingClasses =
- CLASS_NAMES.stream()
- .filter(matcher::matches)
- .map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION))
- .collect(toList());
- return testForD8()
- .setMinApi(parameters)
- .addProgramFiles(matchingClasses)
- .addLibraryFiles(JAR)
- .compile();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToImmediateInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToImmediateInterfaceTest.java
index 455e037..d55815d 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToImmediateInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToImmediateInterfaceTest.java
@@ -9,10 +9,12 @@
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -63,13 +65,26 @@
.addProgramClasses(I.class, Main.class)
.addProgramClassFileData(getClassWithTransformedInvoked())
.addKeepMainRule(Main.class)
+ // Illegal invoke-super to interface method.
+ .applyIf(
+ parameters.isDexRuntime()
+ && parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.K),
+ R8TestBuilder::allowDiagnosticWarningMessages)
.setMinApi(parameters)
.run(parameters.getRuntime(), Main.class)
// TODO(b/313065227): Should succeed.
.applyIf(
parameters.isCfRuntime(),
runResult -> runResult.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
- runResult -> runResult.assertFailureWithErrorThatThrows(NullPointerException.class));
+ parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik(),
+ runResult -> runResult.assertFailureWithErrorThatThrows(VerifyError.class),
+ parameters.canUseDefaultAndStaticInterfaceMethods(),
+ runResult -> runResult.assertFailureWithErrorThatThrows(NullPointerException.class),
+ parameters.isDexRuntime()
+ && parameters.getDexRuntimeVersion().isEqualToOneOf(Version.V5_1_1, Version.V6_0_1),
+ runResult ->
+ runResult.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class),
+ runResult -> runResult.assertFailureWithErrorThatThrows(AbstractMethodError.class));
}
private byte[] getClassWithTransformedInvoked() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java
index 5b9b506..df1101d 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java
@@ -3,12 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.internal;
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -120,7 +120,7 @@
String entryString = entry.toString();
if (entryString.endsWith(".class") && !entryString.startsWith("j$")) {
byte[] bytes = ByteStreams.toByteArray(inputStream);
- consumer.accept(ByteDataView.of(bytes), TestBase.extractClassDescriptor(bytes), null);
+ consumer.accept(ByteDataView.of(bytes), extractClassDescriptor(bytes), null);
}
});
consumer.finished(null);
diff --git a/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java b/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java
index dd2700f..3a58eeb 100644
--- a/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java
+++ b/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java
@@ -3,12 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.jacoco;
+import static com.android.tools.r8.utils.CfUtils.extractClassName;
import static com.android.tools.r8.utils.DescriptorUtils.JAVA_PACKAGE_SEPARATOR;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.ZipUtils;
@@ -42,7 +42,7 @@
// Write the class to a .class file with package sub-directories.
Path original = dir.resolve("original");
for (byte[] clazz : classes) {
- String typeName = TestBase.extractClassName(clazz);
+ String typeName = extractClassName(clazz);
int lastDotIndex = typeName.lastIndexOf('.');
String pkg = typeName.substring(0, lastDotIndex);
String baseFileName = typeName.substring(lastDotIndex + 1) + CLASS_EXTENSION;
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
index e3622d4..27b74ee 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.keepanno;
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
+
import com.android.tools.r8.ExternalR8TestBuilder;
import com.android.tools.r8.ProguardTestBuilder;
import com.android.tools.r8.R8FullTestBuilder;
@@ -189,8 +191,8 @@
// This enables native interpretation of all keep annotations.
builder.addOptionsModification(
o -> {
- o.testing.enableExtractedKeepAnnotations = true;
- o.testing.enableEmbeddedKeepAnnotations = true;
+ o.testing.enableExtractedKeepAnnotations = isNormalizeEdges();
+ o.testing.enableEmbeddedKeepAnnotations = !isNormalizeEdges();
});
// This disables all reading of annotations in the command reader.
builder.getBuilder().setEnableExperimentalKeepAnnotations(false);
@@ -257,8 +259,7 @@
List<KeepDeclaration> declarations = KeepEdgeReader.readKeepEdges(classFileData);
if (!declarations.isEmpty()) {
String binaryName =
- DescriptorUtils.getBinaryNameFromDescriptor(
- TestBase.extractClassDescriptor(classFileData));
+ DescriptorUtils.getBinaryNameFromDescriptor(extractClassDescriptor(classFileData));
String synthesizingTarget = binaryName + "$$ExtractedKeepEdges";
ClassWriter classWriter = new ClassWriter(InternalOptions.ASM_VERSION);
classWriter.visit(
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
index b6be4dc..e92c4d8 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -581,7 +581,8 @@
.addMember(
new GroupMember("classNamePattern")
.setDocTitle("Classes matching the class-name pattern.")
- .defaultValue(CLASS_NAME_PATTERN, DEFAULT_INVALID_CLASS_NAME_PATTERN));
+ .defaultValue(CLASS_NAME_PATTERN, DEFAULT_INVALID_CLASS_NAME_PATTERN))
+ .addMember(instanceOfPattern());
// TODO(b/248408342): Add more injections on type pattern variants.
// /** Exact type name as a string to match any array with that type as member. */
// String arrayOf() default "";
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
index d905d4c..8e0df4f 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
@@ -41,7 +41,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
-import java.util.stream.Collectors;
import kotlin.text.Charsets;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -287,16 +286,6 @@
runAndCheckOutput(targetClasses, sourceClasses, format, expected, null);
}
- private Path zipWithTestClasses(Path zipFile, List<Class<?>> targetClasses) throws IOException {
- return ZipBuilder.builder(zipFile)
- .addFilesRelative(
- ToolHelper.getClassPathForTests(),
- targetClasses.stream()
- .map(ToolHelper::getClassFileForTestClass)
- .collect(Collectors.toList()))
- .build();
- }
-
public void runAndCheckOutput(
List<Class<?>> targetClasses,
List<Class<?>> sourceClasses,
diff --git a/src/test/java/com/android/tools/r8/utils/DaggerUtils.java b/src/test/java/com/android/tools/r8/utils/DaggerUtils.java
index de4e55f..e10cd49 100644
--- a/src/test/java/com/android/tools/r8/utils/DaggerUtils.java
+++ b/src/test/java/com/android/tools/r8/utils/DaggerUtils.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import static com.android.tools.r8.utils.CfUtils.extractClassName;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
@@ -73,7 +74,7 @@
String entryString = entry.toString();
if (FileUtils.isClassFile(entryString)) {
byte[] bytes = ByteStreams.toByteArray(inputStream);
- classNames.add(TestBase.extractClassName(bytes));
+ classNames.add(extractClassName(bytes));
}
});
}
diff --git a/src/test/java/com/android/tools/r8/workaround/ExpectedToBeWithinCatchAllAfterInliningWithSoftVerificationErrorTest.java b/src/test/java/com/android/tools/r8/workaround/ExpectedToBeWithinCatchAllAfterInliningWithSoftVerificationErrorTest.java
new file mode 100644
index 0000000..316af73
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/workaround/ExpectedToBeWithinCatchAllAfterInliningWithSoftVerificationErrorTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2024, 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.workaround;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Regression test for b/348785664. */
+@RunWith(Parameterized.class)
+public class ExpectedToBeWithinCatchAllAfterInliningWithSoftVerificationErrorTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .applyIf(!canUseJavaUtilObjects(parameters), builder -> builder.addDontWarn(Objects.class))
+ // Disable desugaring to prevent backporting of Objects.isNull.
+ .disableDesugaring()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ try {
+ // After inlining of greet(), if the catch handler responsible for exiting the monitor is
+ // not itself guarded by a catch-all, then verification fails due to the presence of the
+ // non-catch-all handler.
+ greet();
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+
+ // Verification error only happens when the method has a soft verification error.
+ // We therefore call Objects.isNull, which is only defined from API level 24.
+ if (args.length > 0) {
+ System.out.println(Objects.isNull(args));
+ }
+ }
+
+ static synchronized void greet() {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/RelocatorTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/RelocatorTestBuilder.java
index 2fea242..149ce0e 100644
--- a/src/test/testbase/java/com/android/tools/r8/RelocatorTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/RelocatorTestBuilder.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
-import static com.android.tools.r8.TestBase.extractClassDescriptor;
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBase.java b/src/test/testbase/java/com/android/tools/r8/TestBase.java
index 2a812ee..350bb0f 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBase.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBase.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.TestBuilder.getTestingAnnotations;
import static com.android.tools.r8.ToolHelper.R8_TEST_BUCKET;
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
import static com.google.common.collect.Lists.cartesianProduct;
import static org.hamcrest.CoreMatchers.containsString;
@@ -87,6 +88,7 @@
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -1395,45 +1397,6 @@
}
}
- public static String extractClassName(byte[] ccc) {
- return DescriptorUtils.descriptorToJavaType(extractClassDescriptor(ccc));
- }
-
- public static String extractClassDescriptor(byte[] ccc) {
- return "L" + extractClassInternalType(ccc) + ";";
- }
-
- private static String extractClassInternalType(byte[] ccc) {
- class ClassNameExtractor extends ClassVisitor {
- private String className;
-
- private ClassNameExtractor() {
- super(ASM_VERSION);
- }
-
- @Override
- public void visit(
- int version,
- int access,
- String name,
- String signature,
- String superName,
- String[] interfaces) {
- className = name;
- }
-
- String getClassInternalType() {
- return className;
- }
- }
-
- ClassReader reader = new ClassReader(ccc);
- ClassNameExtractor extractor = new ClassNameExtractor();
- reader.accept(
- extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
- return extractor.getClassInternalType();
- }
-
protected static void writeClassesToJar(Path output, Collection<Class<?>> classes)
throws IOException {
ClassFileConsumer consumer = new ArchiveConsumer(output);
@@ -2157,6 +2120,19 @@
}
}
+ protected static Path zipWithTestClasses(Path zipFile, Class<?>... classes) throws IOException {
+ return zipWithTestClasses(zipFile, Arrays.asList(classes));
+ }
+
+ protected static Path zipWithTestClasses(Path zipFile, List<Class<?>> classes)
+ throws IOException {
+ return ZipBuilder.builder(zipFile)
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ classes.stream().map(ToolHelper::getClassFileForTestClass).collect(Collectors.toList()))
+ .build();
+ }
+
private static class GenericSignatureReader extends ClassVisitor {
public final Set<String> signatures = new HashSet<>();
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
index 201d674..a7c2cd8 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
import static com.android.tools.r8.TestBase.descriptor;
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
import com.android.tools.r8.TestBase.Backend;
@@ -241,7 +242,7 @@
}
ArchiveConsumer consumer = new ArchiveConsumer(out);
for (byte[] bytes : classes) {
- consumer.accept(ByteDataView.of(bytes), TestBase.extractClassDescriptor(bytes), null);
+ consumer.accept(ByteDataView.of(bytes), extractClassDescriptor(bytes), null);
}
consumer.finished(null);
return outputConsumer.apply(out);
diff --git a/src/test/testbase/java/com/android/tools/r8/TestCompileResult.java b/src/test/testbase/java/com/android/tools/r8/TestCompileResult.java
index 545a36e..8f32eae 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestCompileResult.java
@@ -6,7 +6,7 @@
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.android.tools.r8.TestBase.Backend.DEX;
import static com.android.tools.r8.TestBase.testForD8;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
@@ -218,7 +218,8 @@
}
}
}
- assertThat("Did you forget a keep rule for the main method?", mainClassSubject, Matchers.isPresent());
+ assertThat(
+ "Did you forget a keep rule for the main method?", mainClassSubject, Matchers.isPresent());
if (runtime.isDex()) {
return runArt(runtime, mainClassSubject.getFinalName(), args);
}
@@ -295,7 +296,7 @@
AndroidApp.Builder appBuilder = AndroidApp.builder();
for (byte[] clazz : classes) {
appBuilder.addClassProgramData(
- clazz, Origin.unknown(), ImmutableSet.of(TestBase.extractClassDescriptor(clazz)));
+ clazz, Origin.unknown(), ImmutableSet.of(extractClassDescriptor(clazz)));
}
Path path = state.getNewTempFolder().resolve("runtime-classes.jar");
appBuilder.build().writeToZipForTesting(path, OutputMode.ClassFile);
diff --git a/third_party/android_jar/lib-v35.tar.gz.sha1 b/third_party/android_jar/lib-v35.tar.gz.sha1
index 3543c83..0db22a6 100644
--- a/third_party/android_jar/lib-v35.tar.gz.sha1
+++ b/third_party/android_jar/lib-v35.tar.gz.sha1
@@ -1 +1 @@
-9ac3858ee6859c45500fa7c5254592516c5e73b7
\ No newline at end of file
+661a1420aa7b08e8d8f2e670a808d064ef3edd65
\ No newline at end of file
diff --git a/third_party/api_database/api_database.tar.gz.sha1 b/third_party/api_database/api_database.tar.gz.sha1
index 0d2c06c..966e156c 100644
--- a/third_party/api_database/api_database.tar.gz.sha1
+++ b/third_party/api_database/api_database.tar.gz.sha1
@@ -1 +1 @@
-b043b19332f3f7cd2578874b6d051462b55ac56a
\ No newline at end of file
+0aab829fe216f39035a282024966bbd26bea3a26
\ No newline at end of file