Merge commit 'dd48d39a4b294f7acf4b1e1b5b29ac76c4a746ed' into dev-release Change-Id: I609d377ff27589566406bf4abda940567046afe8
diff --git a/d8_r8/test_modules/tests_bootstrap/build.gradle.kts b/d8_r8/test_modules/tests_bootstrap/build.gradle.kts index 0be47ab..5e6ba69 100644 --- a/d8_r8/test_modules/tests_bootstrap/build.gradle.kts +++ b/d8_r8/test_modules/tests_bootstrap/build.gradle.kts
@@ -76,6 +76,8 @@ dependsOn(mainR8RelocatedTask) systemProperty("TEST_DATA_LOCATION", layout.buildDirectory.dir("classes/java/test").get().toString()) + systemProperty("TESTBASE_DATA_LOCATION", + testbaseJavaCompileTask.outputs.files.getAsPath().split(File.pathSeparator)[0]) systemProperty( "BUILD_PROP_KEEPANNO_RUNTIME_PATH", keepAnnoCompileTask.outputs.files.getAsPath().split(File.pathSeparator)[0])
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java index 85d86a6..d3b820e 100644 --- a/src/main/java/com/android/tools/r8/D8Command.java +++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -16,7 +16,9 @@ import com.android.tools.r8.keepanno.annotations.KeepForApi; import com.android.tools.r8.naming.MapConsumer; import com.android.tools.r8.naming.ProguardMapStringConsumer; +import com.android.tools.r8.origin.ArchiveEntryOrigin; import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.origin.PathOrigin; import com.android.tools.r8.profile.art.ArtProfileForRewriting; import com.android.tools.r8.shaking.ProguardConfigurationParser; import com.android.tools.r8.shaking.ProguardConfigurationRule; @@ -24,10 +26,14 @@ import com.android.tools.r8.shaking.ProguardConfigurationSourceFile; import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings; import com.android.tools.r8.startup.StartupProfileProvider; +import com.android.tools.r8.synthesis.GlobalSyntheticsResourceBytes; +import com.android.tools.r8.synthesis.GlobalSyntheticsResourceFile; +import com.android.tools.r8.synthesis.GlobalSyntheticsUtils; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.AssertionConfigurationWithDefault; import com.android.tools.r8.utils.DumpInputFlags; +import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramProvider; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOptions.DesugarState; @@ -38,7 +44,10 @@ import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.ZipUtils; import com.google.common.collect.ImmutableList; +import com.google.common.io.ByteStreams; +import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -90,6 +99,7 @@ public static class Builder extends BaseCompilerCommand.Builder<D8Command, Builder> { private boolean intermediate = false; + private Path globalSyntheticsOutput = null; private GlobalSyntheticsConsumer globalSyntheticsConsumer = null; private final List<GlobalSyntheticsResourceProvider> globalSyntheticsResourceProviders = new ArrayList<>(); @@ -257,12 +267,28 @@ } /** + * Set an output path for receiving the global synthetic content for the given compilation. + * + * <p>Note: this output is ignored if the compilation is not an "intermediate mode" compilation. + * + * <p>Note: setting this will clear out any consumer set by setGlobalSyntheticsConsumer. + */ + public Builder setGlobalSyntheticsOutput(Path globalSyntheticsOutput) { + this.globalSyntheticsConsumer = null; + this.globalSyntheticsOutput = globalSyntheticsOutput; + return self(); + } + + /** * Set a consumer for receiving the global synthetic content for the given compilation. * * <p>Note: this consumer is ignored if the compilation is not an "intermediate mode" * compilation. + * + * <p>Note: setting this will clear out any output path set by setGlobalSyntheticsOutput. */ public Builder setGlobalSyntheticsConsumer(GlobalSyntheticsConsumer globalSyntheticsConsumer) { + this.globalSyntheticsOutput = null; this.globalSyntheticsConsumer = globalSyntheticsConsumer; return self(); } @@ -288,11 +314,30 @@ /** Add global synthetics resource files. */ public Builder addGlobalSyntheticsFiles(Collection<Path> files) { for (Path file : files) { - addGlobalSyntheticsResourceProviders(new GlobalSyntheticsResourceFile(file)); + addGlobalSyntheticsFileOrArchiveOfGlobalSynthetics(file); } return self(); } + private void addGlobalSyntheticsFileOrArchiveOfGlobalSynthetics(Path file) { + if (!FileUtils.isZipFile(file)) { + addGlobalSyntheticsResourceProviders(new GlobalSyntheticsResourceFile(file)); + return; + } + PathOrigin origin = new PathOrigin(file); + try { + ZipUtils.iter( + file, + (entry, input) -> + addGlobalSyntheticsResourceProviders( + new GlobalSyntheticsResourceBytes( + new ArchiveEntryOrigin(entry.getName(), origin), + ByteStreams.toByteArray(input)))); + } catch (IOException e) { + error(origin, e); + } + } + /** * Set a consumer for receiving the keep rules to use when compiling the desugared library for * the program being compiled in this compilation. @@ -495,7 +540,8 @@ return new D8Command(isPrintHelp(), isPrintVersion()); } - intermediate |= getProgramConsumer() instanceof DexFilePerClassFileConsumer; + final ProgramConsumer programConsumer = getProgramConsumer(); + intermediate |= programConsumer instanceof DexFilePerClassFileConsumer; DexItemFactory factory = new DexItemFactory(); DesugaredLibrarySpecification desugaredLibrarySpecification = @@ -511,20 +557,24 @@ // If compiling to CF with --no-desugaring then the target API is B for consistency with R8. int minApiLevel = - getProgramConsumer() instanceof ClassFileConsumer && getDisableDesugaring() + programConsumer instanceof ClassFileConsumer && getDisableDesugaring() ? AndroidApiLevel.B.getLevel() : getMinApiLevel(); + GlobalSyntheticsConsumer globalConsumer = + GlobalSyntheticsUtils.determineGlobalSyntheticsConsumer( + intermediate, globalSyntheticsOutput, globalSyntheticsConsumer, programConsumer); + return new D8Command( getAppBuilder().build(), getMode(), - getProgramConsumer(), + programConsumer, getMainDexListConsumer(), minApiLevel, getReporter(), getDesugaringState(), intermediate, - intermediate ? globalSyntheticsConsumer : null, + globalConsumer, isOptimizeMultidexForLinearAlloc(), getIncludeClassesChecksum(), getDexClassChecksumFilter(), @@ -841,4 +891,5 @@ .setEnableMissingLibraryApiModeling(enableMissingLibraryApiModeling) .build(); } + }
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java index 49360fd..a16189b 100644 --- a/src/main/java/com/android/tools/r8/D8CommandParser.java +++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -30,6 +30,8 @@ private static final Set<String> OPTIONS_WITH_ONE_PARAMETER = ImmutableSet.of( "--output", + "--globals", + "--globals-output", "--lib", "--classpath", "--pg-map", @@ -53,6 +55,8 @@ .add(ParseFlagInfoImpl.getDebug(true)) .add(ParseFlagInfoImpl.getRelease(false)) .add(ParseFlagInfoImpl.getOutput()) + .add(ParseFlagInfoImpl.getGlobals()) + .add(ParseFlagInfoImpl.getGlobalsOutput()) .add(ParseFlagInfoImpl.getLib()) .add(ParseFlagInfoImpl.getClasspath()) .add(ParseFlagInfoImpl.getMinApi()) @@ -211,6 +215,7 @@ private D8Command.Builder parse(String[] args, Origin origin, D8Command.Builder builder) { CompilationMode compilationMode = null; Path outputPath = null; + Path globalsOutputPath = null; OutputMode outputMode = null; boolean hasDefinedApiLevel = false; OrderedClassFileResourceProvider.Builder classpathBuilder = @@ -279,6 +284,21 @@ continue; } outputPath = Paths.get(nextArg); + } else if (arg.equals("--globals")) { + builder.addGlobalSyntheticsFiles(Paths.get(nextArg)); + } else if (arg.equals("--globals-output")) { + if (globalsOutputPath != null) { + builder.error( + new StringDiagnostic( + "Cannot output globals both to '" + + globalsOutputPath.toString() + + "' and '" + + nextArg + + "'", + origin)); + continue; + } + globalsOutputPath = Paths.get(nextArg); } else if (arg.equals("--lib")) { addLibraryArgument(builder, origin, nextArg); } else if (arg.equals("--classpath")) { @@ -373,6 +393,9 @@ if (outputPath == null) { outputPath = Paths.get("."); } + if (globalsOutputPath != null) { + builder.setGlobalSyntheticsOutput(globalsOutputPath); + } return builder.setOutput(outputPath, outputMode); } }
diff --git a/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java b/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java index 3273271..eb1fed3 100644 --- a/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java +++ b/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java
@@ -47,6 +47,23 @@ "<file> must be an existing directory or a zip file."); } + public static ParseFlagInfoImpl getGlobals() { + return flag1( + "--globals", + "<file>", + "Global synthetics <file> from a previous intermediate compilation.", + "The <file> may be either a zip-archive of global synthetics or the", + "global-synthetic files directly."); + } + + public static ParseFlagInfoImpl getGlobalsOutput() { + return flag1( + "--globals-output", + "<file>", + "Output global synthetics in <file>.", + "<file> must be an existing directory or a non-existent zip archive."); + } + public static ParseFlagInfoImpl getLib() { return flag1("--lib", "<file|jdk-home>", "Add <file|jdk-home> as a library resource."); }
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java index fa97f59..d3fafc4 100644 --- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java +++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -32,7 +32,6 @@ import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.SetUtils; import com.android.tools.r8.utils.ThreadUtils; -import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; import java.util.Arrays; import java.util.Collection; @@ -54,7 +53,6 @@ private final Map<DexLibraryClass, Set<DexProgramClass>> referencingContexts = new ConcurrentHashMap<>(); private final Set<DexLibraryClass> libraryClassesToMock = SetUtils.newConcurrentHashSet(); - private final Set<DexType> seenTypes = SetUtils.newConcurrentHashSet(); private final AndroidApiLevelCompute apiLevelCompute; private final ApiReferenceStubberEventConsumer eventConsumer; @@ -164,25 +162,24 @@ if (!type.isClassType() || isJavaType(type, appView.dexItemFactory())) { return; } - WorkList.newIdentityWorkList(type, seenTypes) - .process( - (classType, workList) -> { - DexClass clazz = appView.definitionFor(classType); - if (clazz == null || !clazz.isLibraryClass()) { - return; - } - ComputedApiLevel androidApiLevel = - apiLevelCompute.computeApiLevelForLibraryReference( - clazz.type, ComputedApiLevel.unknown()); - if (androidApiLevel.isGreaterThan(appView.computedMinApiLevel()) - && androidApiLevel.isKnownApiLevel()) { - workList.addIfNotSeen(clazz.allImmediateSupertypes()); - libraryClassesToMock.add(clazz.asLibraryClass()); - referencingContexts - .computeIfAbsent(clazz.asLibraryClass(), ignoreKey(Sets::newConcurrentHashSet)) - .add(context); - } - }); + DexClass clazz = appView.definitionFor(type); + if (clazz == null || !clazz.isLibraryClass()) { + return; + } + DexLibraryClass libraryClass = clazz.asLibraryClass(); + ComputedApiLevel androidApiLevel = + apiLevelCompute.computeApiLevelForLibraryReference( + libraryClass.type, ComputedApiLevel.unknown()); + if (androidApiLevel.isGreaterThan(appView.computedMinApiLevel()) + && androidApiLevel.isKnownApiLevel()) { + libraryClassesToMock.add(libraryClass); + referencingContexts + .computeIfAbsent(libraryClass, ignoreKey(Sets::newConcurrentHashSet)) + .add(context); + } + for (DexType supertype : libraryClass.allImmediateSupertypes()) { + findReferencedLibraryClasses(supertype, context); + } } @SuppressWarnings("ReferenceEquality")
diff --git a/src/main/java/com/android/tools/r8/errors/FinalRClassEntriesWithOptimizedShrinkingDiagnostic.java b/src/main/java/com/android/tools/r8/errors/FinalRClassEntriesWithOptimizedShrinkingDiagnostic.java new file mode 100644 index 0000000..c2f18a5 --- /dev/null +++ b/src/main/java/com/android/tools/r8/errors/FinalRClassEntriesWithOptimizedShrinkingDiagnostic.java
@@ -0,0 +1,41 @@ +// 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.errors; + +import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.keepanno.annotations.KeepForApi; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; + +@KeepForApi +public class FinalRClassEntriesWithOptimizedShrinkingDiagnostic implements Diagnostic { + + private final Origin origin; + private final DexField dexField; + + public FinalRClassEntriesWithOptimizedShrinkingDiagnostic(Origin origin, DexField dexField) { + this.origin = origin; + this.dexField = dexField; + } + + @Override + public Origin getOrigin() { + return origin; + } + + @Override + public Position getPosition() { + return Position.UNKNOWN; + } + + @Override + public String getDiagnosticMessage() { + return "Running optimized resource shrinking with final R class ids is not supported and " + + "can lead to missing resources and code necessary for program execution. " + + "Field " + + dexField + + " is final"; + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java index 01e5f1e..f70748d 100644 --- a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java +++ b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.graph.analysis; import com.android.build.shrinker.r8integration.R8ResourceShrinkerState; +import com.android.tools.r8.errors.FinalRClassEntriesWithOptimizedShrinkingDiagnostic; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; @@ -90,7 +91,6 @@ } } - @SuppressWarnings("ReferenceEquality") private void populateRClassValues(ProgramField field) { // TODO(287398085): Pending discussions with the AAPT2 team, we might need to harden this // to not fail if we wrongly classify an unrelated class as R class in our heuristic.. @@ -100,10 +100,28 @@ if (programClassInitializer != null) { analyzeClassInitializer(rClassValueBuilder, programClassInitializer); } - + warnOnFinalIdFields(field.getHolder()); fieldToValueMapping.put(field.getHolderType(), rClassValueBuilder.build()); } + private void warnOnFinalIdFields(DexProgramClass holder) { + if (!appView.options().isOptimizedResourceShrinking()) { + return; + } + for (DexEncodedField field : holder.fields()) { + if (field.isStatic() + && field.isFinal() + && field.hasExplicitStaticValue() + && field.getType().isIntType()) { + appView + .reporter() + .warning( + new FinalRClassEntriesWithOptimizedShrinkingDiagnostic( + holder.origin, field.getReference())); + } + } + } + private void analyzeClassInitializer( RClassFieldToValueStore.Builder rClassValueBuilder, ProgramMethod programClassInitializer) { IRCode code = programClassInitializer.buildIR(appView, MethodConversionOptions.nonConverting());
diff --git a/src/main/java/com/android/tools/r8/kotlin/ConcreteKotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/ConcreteKotlinPropertyInfo.java new file mode 100644 index 0000000..f826283 --- /dev/null +++ b/src/main/java/com/android/tools/r8/kotlin/ConcreteKotlinPropertyInfo.java
@@ -0,0 +1,257 @@ +// 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.kotlin; + +import static com.android.tools.r8.kotlin.KotlinMetadataUtils.consume; +import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteIfNotNull; +import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteList; +import static com.android.tools.r8.utils.FunctionUtils.forEachApply; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexDefinitionSupplier; +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.utils.ListUtils; +import com.android.tools.r8.utils.Reporter; +import java.util.List; +import java.util.function.Consumer; +import kotlinx.metadata.KmProperty; +import kotlinx.metadata.jvm.JvmExtensionsKt; + +// Holds information about KmProperty +public class ConcreteKotlinPropertyInfo implements KotlinPropertyInfo { + + // Original flags. + private final int flags; + + // Original getter flags. E.g., for property getter. + private final int getterFlags; + + // Original setter flags. E.g., for property setter. + private final int setterFlags; + + // Original property name for (extension) property. Otherwise, null. + private final String name; + + // Original return type information. This should never be NULL (even for setters without field). + private final KotlinTypeInfo returnType; + + private final KotlinTypeInfo receiverParameterType; + + private final KotlinValueParameterInfo setterParameter; + + private final List<KotlinTypeParameterInfo> typeParameters; + + private final KotlinVersionRequirementInfo versionRequirements; + + private final int jvmFlags; + + private final KotlinJvmFieldSignatureInfo fieldSignature; + + private final KotlinJvmMethodSignatureInfo getterSignature; + + private final KotlinJvmMethodSignatureInfo setterSignature; + + private final KotlinJvmMethodSignatureInfo syntheticMethodForAnnotations; + + private final KotlinJvmMethodSignatureInfo syntheticMethodForDelegate; + // Collection of context receiver types + private final List<KotlinTypeInfo> contextReceiverTypes; + + private ConcreteKotlinPropertyInfo( + int flags, + int getterFlags, + int setterFlags, + String name, + KotlinTypeInfo returnType, + KotlinTypeInfo receiverParameterType, + KotlinValueParameterInfo setterParameter, + List<KotlinTypeParameterInfo> typeParameters, + KotlinVersionRequirementInfo versionRequirements, + int jvmFlags, + KotlinJvmFieldSignatureInfo fieldSignature, + KotlinJvmMethodSignatureInfo getterSignature, + KotlinJvmMethodSignatureInfo setterSignature, + KotlinJvmMethodSignatureInfo syntheticMethodForAnnotations, + KotlinJvmMethodSignatureInfo syntheticMethodForDelegate, + List<KotlinTypeInfo> contextReceiverTypes) { + this.flags = flags; + this.getterFlags = getterFlags; + this.setterFlags = setterFlags; + this.name = name; + assert returnType != null; + this.returnType = returnType; + this.receiverParameterType = receiverParameterType; + this.setterParameter = setterParameter; + this.typeParameters = typeParameters; + this.versionRequirements = versionRequirements; + this.jvmFlags = jvmFlags; + this.fieldSignature = fieldSignature; + this.getterSignature = getterSignature; + this.setterSignature = setterSignature; + this.syntheticMethodForAnnotations = syntheticMethodForAnnotations; + this.syntheticMethodForDelegate = syntheticMethodForDelegate; + this.contextReceiverTypes = contextReceiverTypes; + } + + public static ConcreteKotlinPropertyInfo create( + KmProperty kmProperty, DexItemFactory factory, Reporter reporter) { + return new ConcreteKotlinPropertyInfo( + kmProperty.getFlags(), + kmProperty.getGetterFlags(), + kmProperty.getSetterFlags(), + kmProperty.getName(), + KotlinTypeInfo.create(kmProperty.getReturnType(), factory, reporter), + KotlinTypeInfo.create(kmProperty.getReceiverParameterType(), factory, reporter), + KotlinValueParameterInfo.create(kmProperty.getSetterParameter(), factory, reporter), + KotlinTypeParameterInfo.create(kmProperty.getTypeParameters(), factory, reporter), + KotlinVersionRequirementInfo.create(kmProperty.getVersionRequirements()), + JvmExtensionsKt.getJvmFlags(kmProperty), + KotlinJvmFieldSignatureInfo.create(JvmExtensionsKt.getFieldSignature(kmProperty), factory), + KotlinJvmMethodSignatureInfo.create( + JvmExtensionsKt.getGetterSignature(kmProperty), factory), + KotlinJvmMethodSignatureInfo.create( + JvmExtensionsKt.getSetterSignature(kmProperty), factory), + KotlinJvmMethodSignatureInfo.create( + JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty), factory), + KotlinJvmMethodSignatureInfo.create( + JvmExtensionsKt.getSyntheticMethodForDelegate(kmProperty), factory), + ListUtils.map( + kmProperty.getContextReceiverTypes(), + contextRecieverType -> KotlinTypeInfo.create(contextRecieverType, factory, reporter))); + } + + @Override + public KotlinJvmFieldSignatureInfo getFieldSignature() { + return fieldSignature; + } + + @Override + public KotlinJvmMethodSignatureInfo getGetterSignature() { + return getterSignature; + } + + @Override + public KotlinJvmMethodSignatureInfo getSetterSignature() { + return setterSignature; + } + + @Override + public boolean rewriteNoBacking(Consumer<KmProperty> consumer, AppView<?> appView) { + return rewrite(consumer, null, null, null, null, appView); + } + + @Override + public boolean rewrite( + Consumer<KmProperty> consumer, + DexEncodedField field, + DexEncodedMethod getter, + DexEncodedMethod setter, + DexEncodedMethod syntheticMethodForAnnotationsMethod, + AppView<?> appView) { + KmProperty kmProperty = + consume(new KmProperty(flags, name, getterFlags, setterFlags), consumer); + boolean rewritten = + rewriteIfNotNull(appView, returnType, kmProperty::setReturnType, KotlinTypeInfo::rewrite); + assert returnType != null; + rewritten |= + rewriteIfNotNull( + appView, + receiverParameterType, + kmProperty::setReceiverParameterType, + KotlinTypeInfo::rewrite); + rewritten |= + rewriteIfNotNull( + appView, + setterParameter, + kmProperty::setSetterParameter, + KotlinValueParameterInfo::rewrite); + rewritten |= + rewriteList( + appView, + typeParameters, + kmProperty.getTypeParameters(), + KotlinTypeParameterInfo::rewrite); + rewritten |= + rewriteList( + appView, + contextReceiverTypes, + kmProperty.getContextReceiverTypes(), + KotlinTypeInfo::rewrite); + rewritten |= versionRequirements.rewrite(kmProperty.getVersionRequirements()::addAll); + if (fieldSignature != null) { + rewritten |= + fieldSignature.rewrite( + newSignature -> JvmExtensionsKt.setFieldSignature(kmProperty, newSignature), + field, + appView); + } + if (getterSignature != null) { + rewritten |= + getterSignature.rewrite( + newSignature -> JvmExtensionsKt.setGetterSignature(kmProperty, newSignature), + getter, + appView); + } + if (setterSignature != null) { + rewritten |= + setterSignature.rewrite( + newSignature -> JvmExtensionsKt.setSetterSignature(kmProperty, newSignature), + setter, + appView); + } + if (syntheticMethodForAnnotations != null) { + rewritten |= + syntheticMethodForAnnotations.rewrite( + newSignature -> + JvmExtensionsKt.setSyntheticMethodForAnnotations(kmProperty, newSignature), + syntheticMethodForAnnotationsMethod, + appView); + } + JvmExtensionsKt.setJvmFlags(kmProperty, jvmFlags); + rewritten |= + rewriteIfNotNull( + appView, + syntheticMethodForDelegate, + newMethod -> JvmExtensionsKt.setSyntheticMethodForDelegate(kmProperty, newMethod), + KotlinJvmMethodSignatureInfo::rewriteNoBacking); + return rewritten; + } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + assert returnType != null; + returnType.trace(definitionSupplier); + if (receiverParameterType != null) { + receiverParameterType.trace(definitionSupplier); + } + if (setterParameter != null) { + setterParameter.trace(definitionSupplier); + } + forEachApply(typeParameters, param -> param::trace, definitionSupplier); + forEachApply(contextReceiverTypes, type -> type::trace, definitionSupplier); + if (fieldSignature != null) { + fieldSignature.trace(definitionSupplier); + } + if (getterSignature != null) { + getterSignature.trace(definitionSupplier); + } + if (setterSignature != null) { + setterSignature.trace(definitionSupplier); + } + if (syntheticMethodForAnnotations != null) { + syntheticMethodForAnnotations.trace(definitionSupplier); + } + if (syntheticMethodForDelegate != null) { + syntheticMethodForDelegate.trace(definitionSupplier); + } + } + + @Override + public String toString() { + return "KotlinPropertyInfo(" + name + ")"; + } +}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java index e670bb5..7fd23e9 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.kotlin.KotlinMetadataUtils.KmPropertyProcessor; +import com.android.tools.r8.kotlin.KotlinPropertyInfoDelegate.PropertyType; import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import com.android.tools.r8.utils.Reporter; import com.google.common.collect.ImmutableList; @@ -91,8 +92,8 @@ ImmutableList.Builder<KotlinPropertyInfo> notBackedProperties = ImmutableList.builder(); for (KmProperty kmProperty : container.getProperties()) { - KotlinPropertyInfo kotlinPropertyInfo = - KotlinPropertyInfo.create(kmProperty, factory, reporter); + ConcreteKotlinPropertyInfo kotlinPropertyInfo = + ConcreteKotlinPropertyInfo.create(kmProperty, factory, reporter); KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty); boolean hasBacking = false; if (propertyProcessor.fieldSignature() != null) { @@ -110,7 +111,8 @@ if (method != null) { hasBacking = true; keepIfAccessorInline(kmProperty.getGetterFlags(), method, keepByteCode); - method.setKotlinMemberInfo(kotlinPropertyInfo); + method.setKotlinMemberInfo( + new KotlinPropertyInfoDelegate(kotlinPropertyInfo, PropertyType.GETTER)); originalAssignmentTracker.add(method.getReference()); } } @@ -120,7 +122,20 @@ if (method != null) { hasBacking = true; keepIfAccessorInline(kmProperty.getGetterFlags(), method, keepByteCode); - method.setKotlinMemberInfo(kotlinPropertyInfo); + method.setKotlinMemberInfo( + new KotlinPropertyInfoDelegate(kotlinPropertyInfo, PropertyType.SETTER)); + originalAssignmentTracker.add(method.getReference()); + } + } + if (propertyProcessor.syntheticMethodForAnnotationsSignature() != null) { + DexEncodedMethod method = + methodSignatureMap.get( + propertyProcessor.syntheticMethodForAnnotationsSignature().asString()); + if (method != null) { + hasBacking = true; + method.setKotlinMemberInfo( + new KotlinPropertyInfoDelegate( + kotlinPropertyInfo, PropertyType.SYNTHETIC_METHOD_FOR_ANNOTATIONS)); originalAssignmentTracker.add(method.getReference()); } } @@ -207,12 +222,20 @@ } rewrittenMembersWithKotlinInfo.add(method.getReference()); KotlinPropertyGroup kotlinPropertyGroup = - properties.computeIfAbsent(kotlinPropertyInfo, ignored -> new KotlinPropertyGroup()); - if (method.getReference().proto.returnType == appView.dexItemFactory().voidType) { - // This is a setter. - kotlinPropertyGroup.setSetter(method); - } else { - kotlinPropertyGroup.setGetter(method); + properties.computeIfAbsent( + kotlinPropertyInfo.getReference(), ignored -> new KotlinPropertyGroup()); + switch (kotlinPropertyInfo.getPropertyType()) { + case GETTER: + kotlinPropertyGroup.setGetter(method); + break; + case SETTER: + kotlinPropertyGroup.setSetter(method); + break; + case SYNTHETIC_METHOD_FOR_ANNOTATIONS: + kotlinPropertyGroup.setSyntheticMethodForAnnotations(method); + break; + default: + // Do nothing. } } for (KotlinPropertyInfo kotlinPropertyInfo : properties.keySet()) { @@ -223,6 +246,7 @@ kotlinPropertyGroup.backingField, kotlinPropertyGroup.getter, kotlinPropertyGroup.setter, + kotlinPropertyGroup.syntheticMethodForAnnotations, appView); } // Add all not backed functions and properties. @@ -253,6 +277,7 @@ private DexEncodedField backingField = null; private DexEncodedMethod setter = null; private DexEncodedMethod getter = null; + private DexEncodedMethod syntheticMethodForAnnotations = null; void setBackingField(DexEncodedField backingField) { assert this.backingField == null; @@ -268,5 +293,10 @@ assert this.setter == null; this.setter = setter; } + + public void setSyntheticMethodForAnnotations(DexEncodedMethod syntheticMethodForAnnotations) { + assert this.syntheticMethodForAnnotations == null; + this.syntheticMethodForAnnotations = syntheticMethodForAnnotations; + } } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java index 9fb8b43..566d8b6 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java
@@ -36,7 +36,7 @@ ImmutableList.Builder<KotlinPropertyInfo> builder = ImmutableList.builder(); for (KmProperty kmProperty : kmProperties) { KotlinPropertyInfo kotlinPropertyInfo = - KotlinPropertyInfo.create(kmProperty, factory, reporter); + ConcreteKotlinPropertyInfo.create(kmProperty, factory, reporter); // For ordinary properties, we place these on the fields and methods, but these are hooked in, // and do not have any jvm signatures: assert kotlinPropertyInfo.getFieldSignature() == null;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java index b5de2c0..e7ffaec 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -114,6 +114,8 @@ private JvmMethodSignature getterSignature = null; // Custom getter via @set:JvmName("..."). Otherwise, null. private JvmMethodSignature setterSignature = null; + private JvmMethodSignature syntheticMethodForAnnotationsSignature = null; + KmPropertyProcessor(KmProperty kmProperty) { kmProperty.accept( new KmPropertyVisitor() { @@ -137,6 +139,12 @@ assert setterSignature == null : setterSignature.asString(); setterSignature = setterDesc; } + + @Override + public void visitSyntheticMethodForAnnotations(JvmMethodSignature signature) { + assert syntheticMethodForAnnotationsSignature == null : signature.asString(); + syntheticMethodForAnnotationsSignature = signature; + } }; } }); @@ -153,6 +161,10 @@ JvmMethodSignature setterSignature() { return setterSignature; } + + public JvmMethodSignature syntheticMethodForAnnotationsSignature() { + return syntheticMethodForAnnotationsSignature; + } } static boolean isValidMethodDescriptor(String methodDescriptor) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java index fc8e889..2bd70d4 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
@@ -1,255 +1,49 @@ -// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// 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.kotlin; -import static com.android.tools.r8.kotlin.KotlinMetadataUtils.consume; -import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteIfNotNull; -import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteList; -import static com.android.tools.r8.utils.FunctionUtils.forEachApply; - import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexItemFactory; -import com.android.tools.r8.utils.ListUtils; -import com.android.tools.r8.utils.Reporter; -import java.util.List; +import com.android.tools.r8.kotlin.KotlinPropertyInfoDelegate.PropertyType; import java.util.function.Consumer; import kotlinx.metadata.KmProperty; -import kotlinx.metadata.jvm.JvmExtensionsKt; -// Holds information about KmProperty -public class KotlinPropertyInfo implements KotlinFieldLevelInfo, KotlinMethodLevelInfo { +public interface KotlinPropertyInfo extends KotlinFieldLevelInfo, KotlinMethodLevelInfo { - // Original flags. - private final int flags; - - // Original getter flags. E.g., for property getter. - private final int getterFlags; - - // Original setter flags. E.g., for property setter. - private final int setterFlags; - - // Original property name for (extension) property. Otherwise, null. - private final String name; - - // Original return type information. This should never be NULL (even for setters without field). - private final KotlinTypeInfo returnType; - - private final KotlinTypeInfo receiverParameterType; - - private final KotlinValueParameterInfo setterParameter; - - private final List<KotlinTypeParameterInfo> typeParameters; - - private final KotlinVersionRequirementInfo versionRequirements; - - private final int jvmFlags; - - private final KotlinJvmFieldSignatureInfo fieldSignature; - - private final KotlinJvmMethodSignatureInfo getterSignature; - - private final KotlinJvmMethodSignatureInfo setterSignature; - - private final KotlinJvmMethodSignatureInfo syntheticMethodForAnnotations; - - private final KotlinJvmMethodSignatureInfo syntheticMethodForDelegate; - // Collection of context receiver types - private final List<KotlinTypeInfo> contextReceiverTypes; - - private KotlinPropertyInfo( - int flags, - int getterFlags, - int setterFlags, - String name, - KotlinTypeInfo returnType, - KotlinTypeInfo receiverParameterType, - KotlinValueParameterInfo setterParameter, - List<KotlinTypeParameterInfo> typeParameters, - KotlinVersionRequirementInfo versionRequirements, - int jvmFlags, - KotlinJvmFieldSignatureInfo fieldSignature, - KotlinJvmMethodSignatureInfo getterSignature, - KotlinJvmMethodSignatureInfo setterSignature, - KotlinJvmMethodSignatureInfo syntheticMethodForAnnotations, - KotlinJvmMethodSignatureInfo syntheticMethodForDelegate, - List<KotlinTypeInfo> contextReceiverTypes) { - this.flags = flags; - this.getterFlags = getterFlags; - this.setterFlags = setterFlags; - this.name = name; - this.returnType = returnType; - this.receiverParameterType = receiverParameterType; - this.setterParameter = setterParameter; - this.typeParameters = typeParameters; - this.versionRequirements = versionRequirements; - this.jvmFlags = jvmFlags; - this.fieldSignature = fieldSignature; - this.getterSignature = getterSignature; - this.setterSignature = setterSignature; - this.syntheticMethodForAnnotations = syntheticMethodForAnnotations; - this.syntheticMethodForDelegate = syntheticMethodForDelegate; - this.contextReceiverTypes = contextReceiverTypes; - } - - public static KotlinPropertyInfo create( - KmProperty kmProperty, DexItemFactory factory, Reporter reporter) { - return new KotlinPropertyInfo( - kmProperty.getFlags(), - kmProperty.getGetterFlags(), - kmProperty.getSetterFlags(), - kmProperty.getName(), - KotlinTypeInfo.create(kmProperty.getReturnType(), factory, reporter), - KotlinTypeInfo.create(kmProperty.getReceiverParameterType(), factory, reporter), - KotlinValueParameterInfo.create(kmProperty.getSetterParameter(), factory, reporter), - KotlinTypeParameterInfo.create(kmProperty.getTypeParameters(), factory, reporter), - KotlinVersionRequirementInfo.create(kmProperty.getVersionRequirements()), - JvmExtensionsKt.getJvmFlags(kmProperty), - KotlinJvmFieldSignatureInfo.create(JvmExtensionsKt.getFieldSignature(kmProperty), factory), - KotlinJvmMethodSignatureInfo.create( - JvmExtensionsKt.getGetterSignature(kmProperty), factory), - KotlinJvmMethodSignatureInfo.create( - JvmExtensionsKt.getSetterSignature(kmProperty), factory), - KotlinJvmMethodSignatureInfo.create( - JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty), factory), - KotlinJvmMethodSignatureInfo.create( - JvmExtensionsKt.getSyntheticMethodForDelegate(kmProperty), factory), - ListUtils.map( - kmProperty.getContextReceiverTypes(), - contextRecieverType -> KotlinTypeInfo.create(contextRecieverType, factory, reporter))); + default PropertyType getPropertyType() { + return PropertyType.UNKNOWN; } @Override - public boolean isProperty() { + default boolean isProperty() { return true; } @Override - public KotlinPropertyInfo asProperty() { + default KotlinPropertyInfo asProperty() { return this; } - public KotlinJvmFieldSignatureInfo getFieldSignature() { - return fieldSignature; + default KotlinPropertyInfo getReference() { + return this; } - public KotlinJvmMethodSignatureInfo getGetterSignature() { - return getterSignature; - } + KotlinJvmFieldSignatureInfo getFieldSignature(); - public KotlinJvmMethodSignatureInfo getSetterSignature() { - return setterSignature; - } + KotlinJvmMethodSignatureInfo getGetterSignature(); - boolean rewriteNoBacking(Consumer<KmProperty> consumer, AppView<?> appView) { - return rewrite(consumer, null, null, null, appView); - } + KotlinJvmMethodSignatureInfo getSetterSignature(); + + boolean rewriteNoBacking(Consumer<KmProperty> consumer, AppView<?> appView); boolean rewrite( Consumer<KmProperty> consumer, DexEncodedField field, DexEncodedMethod getter, DexEncodedMethod setter, - AppView<?> appView) { - // TODO(b/154348683): Flags again. - KmProperty kmProperty = - consume(new KmProperty(flags, name, getterFlags, setterFlags), consumer); - // TODO(b/154348149): ReturnType could have been merged to a subtype. - boolean rewritten = - rewriteIfNotNull(appView, returnType, kmProperty::setReturnType, KotlinTypeInfo::rewrite); - rewritten |= - rewriteIfNotNull( - appView, - receiverParameterType, - kmProperty::setReceiverParameterType, - KotlinTypeInfo::rewrite); - rewritten |= - rewriteIfNotNull( - appView, - setterParameter, - kmProperty::setSetterParameter, - KotlinValueParameterInfo::rewrite); - rewritten |= - rewriteList( - appView, - typeParameters, - kmProperty.getTypeParameters(), - KotlinTypeParameterInfo::rewrite); - rewritten |= - rewriteList( - appView, - contextReceiverTypes, - kmProperty.getContextReceiverTypes(), - KotlinTypeInfo::rewrite); - rewritten |= versionRequirements.rewrite(kmProperty.getVersionRequirements()::addAll); - if (fieldSignature != null) { - rewritten |= - fieldSignature.rewrite( - newSignature -> JvmExtensionsKt.setFieldSignature(kmProperty, newSignature), - field, - appView); - } - if (getterSignature != null) { - rewritten |= - getterSignature.rewrite( - newSignature -> JvmExtensionsKt.setGetterSignature(kmProperty, newSignature), - getter, - appView); - } - if (setterSignature != null) { - rewritten |= - setterSignature.rewrite( - newSignature -> JvmExtensionsKt.setSetterSignature(kmProperty, newSignature), - setter, - appView); - } - JvmExtensionsKt.setJvmFlags(kmProperty, jvmFlags); - rewritten |= - rewriteIfNotNull( - appView, - syntheticMethodForAnnotations, - newMethod -> JvmExtensionsKt.setSyntheticMethodForAnnotations(kmProperty, newMethod), - KotlinJvmMethodSignatureInfo::rewriteNoBacking); - rewritten |= - rewriteIfNotNull( - appView, - syntheticMethodForDelegate, - newMethod -> JvmExtensionsKt.setSyntheticMethodForDelegate(kmProperty, newMethod), - KotlinJvmMethodSignatureInfo::rewriteNoBacking); - return rewritten; - } - - @Override - public void trace(DexDefinitionSupplier definitionSupplier) { - if (returnType != null) { - returnType.trace(definitionSupplier); - } - if (receiverParameterType != null) { - receiverParameterType.trace(definitionSupplier); - } - if (setterParameter != null) { - setterParameter.trace(definitionSupplier); - } - forEachApply(typeParameters, param -> param::trace, definitionSupplier); - forEachApply(contextReceiverTypes, type -> type::trace, definitionSupplier); - if (fieldSignature != null) { - fieldSignature.trace(definitionSupplier); - } - if (getterSignature != null) { - getterSignature.trace(definitionSupplier); - } - if (setterSignature != null) { - setterSignature.trace(definitionSupplier); - } - if (syntheticMethodForAnnotations != null) { - syntheticMethodForAnnotations.trace(definitionSupplier); - } - if (syntheticMethodForDelegate != null) { - syntheticMethodForDelegate.trace(definitionSupplier); - } - } + DexEncodedMethod syntheticMethodForAnnotationsMethod, + AppView<?> appView); }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfoDelegate.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfoDelegate.java new file mode 100644 index 0000000..d658fa5 --- /dev/null +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfoDelegate.java
@@ -0,0 +1,82 @@ +// 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.kotlin; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexDefinitionSupplier; +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexEncodedMethod; +import java.util.function.Consumer; +import kotlinx.metadata.KmProperty; + +public class KotlinPropertyInfoDelegate implements KotlinPropertyInfo { + + private final ConcreteKotlinPropertyInfo delegate; + private final PropertyType propertyType; + + public KotlinPropertyInfoDelegate(ConcreteKotlinPropertyInfo delegate, PropertyType type) { + this.delegate = delegate; + this.propertyType = type; + } + + @Override + public PropertyType getPropertyType() { + return propertyType; + } + + @Override + public KotlinPropertyInfo getReference() { + return delegate; + } + + public enum PropertyType { + SETTER, + GETTER, + SYNTHETIC_METHOD_FOR_ANNOTATIONS, + UNKNOWN + } + + @Override + public KotlinJvmFieldSignatureInfo getFieldSignature() { + return delegate.getFieldSignature(); + } + + @Override + public KotlinJvmMethodSignatureInfo getGetterSignature() { + return delegate.getGetterSignature(); + } + + @Override + public KotlinJvmMethodSignatureInfo getSetterSignature() { + return delegate.getSetterSignature(); + } + + @Override + public boolean rewriteNoBacking(Consumer<KmProperty> consumer, AppView<?> appView) { + return delegate.rewriteNoBacking(consumer, appView); + } + + @Override + public boolean rewrite( + Consumer<KmProperty> consumer, + DexEncodedField field, + DexEncodedMethod getter, + DexEncodedMethod setter, + DexEncodedMethod syntheticMethodForAnnotationsMethod, + AppView<?> appView) { + return delegate.rewrite( + consumer, field, getter, setter, syntheticMethodForAnnotationsMethod, appView); + } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + delegate.trace(definitionSupplier); + } + + @Override + public String toString() { + return "Del" + delegate.toString(); + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableFieldsAndMethods.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableFieldsAndMethods.java index 315c9f7..27852c8 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableFieldsAndMethods.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableFieldsAndMethods.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.FieldAccessInfo; import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; import com.android.tools.r8.graph.ProgramField; import com.android.tools.r8.graph.ProgramMethod; @@ -90,7 +91,12 @@ private boolean isUnoptimizableField(ProgramField field) { KeepFieldInfo keepInfo = appView.getKeepInfo(field); InternalOptions options = appView.options(); - return !keepInfo.isFieldPropagationAllowed(options); + if (!keepInfo.isFieldPropagationAllowed(options)) { + return true; + } + FieldAccessInfo fieldAccessInfo = + appView.appInfo().getFieldAccessInfoCollection().get(field.getReference()); + return fieldAccessInfo.hasReflectiveWrite() || fieldAccessInfo.isWrittenFromMethodHandle(); } private boolean isUnoptimizableMethod(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java index 0fa843d..bf7c4f8 100644 --- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java +++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -203,7 +203,7 @@ member -> { KotlinMemberLevelInfo kotlinInfo = member.getKotlinInfo(); if (kotlinInfo.isProperty() - && !pinnedKotlinProperties.contains(kotlinInfo.asProperty())) { + && !pinnedKotlinProperties.contains(kotlinInfo.asProperty().getReference())) { member.clearKotlinInfo(); } }); @@ -238,7 +238,7 @@ // Postpone removal of kotlin property info until we have seen all fields, setters and getters. if (member.getKotlinInfo().isProperty() && !memberInfo.isKotlinMetadataRemovalAllowed(clazz, options)) { - pinnedKotlinProperties.add(member.getKotlinInfo().asProperty()); + pinnedKotlinProperties.add(member.getKotlinInfo().asProperty().getReference()); } }
diff --git a/src/main/java/com/android/tools/r8/synthesis/GlobalSyntheticsResourceBytes.java b/src/main/java/com/android/tools/r8/synthesis/GlobalSyntheticsResourceBytes.java new file mode 100644 index 0000000..08ace42 --- /dev/null +++ b/src/main/java/com/android/tools/r8/synthesis/GlobalSyntheticsResourceBytes.java
@@ -0,0 +1,32 @@ +// 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.synthesis; + +import com.android.tools.r8.GlobalSyntheticsResourceProvider; +import com.android.tools.r8.ResourceException; +import com.android.tools.r8.origin.Origin; +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +public class GlobalSyntheticsResourceBytes implements GlobalSyntheticsResourceProvider { + + private final Origin origin; + private final byte[] bytes; + + public GlobalSyntheticsResourceBytes(Origin origin, byte[] bytes) { + this.origin = origin; + this.bytes = bytes; + } + + @Override + public Origin getOrigin() { + return origin; + } + + @Override + public InputStream getByteStream() throws ResourceException { + return new ByteArrayInputStream(bytes); + } +}
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceFile.java b/src/main/java/com/android/tools/r8/synthesis/GlobalSyntheticsResourceFile.java similarity index 80% rename from src/main/java/com/android/tools/r8/GlobalSyntheticsResourceFile.java rename to src/main/java/com/android/tools/r8/synthesis/GlobalSyntheticsResourceFile.java index 598a074..cf5b767 100644 --- a/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceFile.java +++ b/src/main/java/com/android/tools/r8/synthesis/GlobalSyntheticsResourceFile.java
@@ -1,8 +1,10 @@ -// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// 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; +package com.android.tools.r8.synthesis; +import com.android.tools.r8.GlobalSyntheticsResourceProvider; +import com.android.tools.r8.ResourceException; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.origin.PathOrigin; import java.io.IOException;
diff --git a/src/main/java/com/android/tools/r8/synthesis/GlobalSyntheticsUtils.java b/src/main/java/com/android/tools/r8/synthesis/GlobalSyntheticsUtils.java new file mode 100644 index 0000000..e132952 --- /dev/null +++ b/src/main/java/com/android/tools/r8/synthesis/GlobalSyntheticsUtils.java
@@ -0,0 +1,107 @@ +// 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.synthesis; + +import com.android.tools.r8.ByteDataView; +import com.android.tools.r8.ClassFileConsumer; +import com.android.tools.r8.DexFilePerClassFileConsumer; +import com.android.tools.r8.DexIndexedConsumer; +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.GlobalSyntheticsConsumer; +import com.android.tools.r8.ProgramConsumer; +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.references.ClassReference; +import com.android.tools.r8.utils.ArchiveBuilder; +import com.android.tools.r8.utils.DirectoryBuilder; +import com.android.tools.r8.utils.OutputBuilder; +import com.android.tools.r8.utils.StringDiagnostic; +import java.nio.file.Files; +import java.nio.file.Path; + +public final class GlobalSyntheticsUtils { + + private GlobalSyntheticsUtils() {} + + public static GlobalSyntheticsConsumer determineGlobalSyntheticsConsumer( + boolean intermediate, + Path output, + GlobalSyntheticsConsumer consumer, + ProgramConsumer programConsumer) { + assert output == null || consumer == null; + if (!intermediate) { + return null; + } + if (consumer != null) { + return consumer; + } + if (output == null) { + return null; + } + + // Output is non-null, and we must create a consumer compatible with the program consumer. + OutputBuilder builder = + Files.isDirectory(output) ? new DirectoryBuilder(output) : new ArchiveBuilder(output); + builder.open(); + + if (programConsumer instanceof DexIndexedConsumer) { + return new GlobalSyntheticsConsumer() { + boolean written = false; + + @Override + public synchronized void accept( + ByteDataView data, ClassReference context, DiagnosticsHandler handler) { + assert context == null; + if (written) { + String msg = "Attempt to write multiple global-synthetics files in dex-indexed mode."; + handler.error(new StringDiagnostic(msg)); + throw new RuntimeException(msg); + } + builder.addFile("classes.globals", data, handler); + builder.close(handler); + written = true; + } + + @Override + public void finished(DiagnosticsHandler handler) { + // If not global info was written, close the builder with empty content. + if (!written) { + builder.close(handler); + } + } + }; + } + + if (programConsumer instanceof DexFilePerClassFileConsumer) { + return new GlobalSyntheticsConsumer() { + + @Override + public void accept(ByteDataView data, ClassReference context, DiagnosticsHandler handler) { + builder.addFile(context.getBinaryName() + ".globals", data, handler); + } + + @Override + public void finished(DiagnosticsHandler handler) { + builder.close(handler); + } + }; + } + + if (programConsumer instanceof ClassFileConsumer) { + return new GlobalSyntheticsConsumer() { + @Override + public void accept(ByteDataView data, ClassReference context, DiagnosticsHandler handler) { + builder.addFile(context.getBinaryName() + ".globals", data, handler); + } + + @Override + public void finished(DiagnosticsHandler handler) { + builder.close(handler); + } + }; + } + + throw new Unreachable("Unexpected program consumer type"); + } +}
diff --git a/src/test/java/com/android/tools/r8/androidresources/DiagnosticOnFinalIntFieldsTest.java b/src/test/java/com/android/tools/r8/androidresources/DiagnosticOnFinalIntFieldsTest.java new file mode 100644 index 0000000..a096a8f --- /dev/null +++ b/src/test/java/com/android/tools/r8/androidresources/DiagnosticOnFinalIntFieldsTest.java
@@ -0,0 +1,83 @@ +// 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.androidresources; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource; +import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder; +import com.android.tools.r8.errors.FinalRClassEntriesWithOptimizedShrinkingDiagnostic; +import com.google.common.collect.ImmutableList; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +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 DiagnosticOnFinalIntFieldsTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection parameters() { + return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build(); + } + + public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception { + return new AndroidTestResourceBuilder() + .withSimpleManifestAndAppNameString() + .addRClassInitializeWithDefaultValues(R.string.class) + .build(temp); + } + + @Test + public void testR8() throws Exception { + // We test the final id field type by simply passing the standard test resources but ignoring + // the aapt generated R class, instead we pass directly the R class from this file, which + // have no real resource references, but does have a non integer field. + testForR8(parameters.getBackend()) + .setMinApi(parameters) + .addProgramClasses(FooBar.class) + .addAndroidResources( + getTestResources(temp), + temp.newFile("resout.zip").toPath(), + ImmutableList.of(ToolHelper.getClassAsBytes(R.string.class))) + .addKeepMainRule(FooBar.class) + .enableOptimizedShrinking() + .allowDiagnosticWarningMessages() + .compileWithExpectedDiagnostics( + diagnostics -> + diagnostics + .assertOnlyWarnings() + .assertWarningsMatch( + diagnosticType(FinalRClassEntriesWithOptimizedShrinkingDiagnostic.class))); + } + + public static class FooBar { + public static void main(String[] args) { + if (System.currentTimeMillis() == 0) { + System.out.println(R.string.foo); + System.out.println(R.string.bar); + } + if (R.string.nonResource != null) { + System.out.println("bar"); + } + } + } + + public static class R { + public static class string { + private static Object nonResource = new Object(); + public static int foo = 0x7f110004; + public static final int bar = 0x7f110005; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/syntheticmethodforannotations/KotlinMetadataTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/syntheticmethodforannotations/KotlinMetadataTest.java new file mode 100644 index 0000000..b2f93a1 --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/syntheticmethodforannotations/KotlinMetadataTest.java
@@ -0,0 +1,198 @@ +// 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.kotlin.metadata.syntheticmethodforannotations; + +import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion; +import com.android.tools.r8.KotlinTestBase; +import com.android.tools.r8.KotlinTestParameters; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.KmPropertySubject; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ExecutionException; +import kotlinx.metadata.internal.extensions.KmPropertyExtension; +import kotlinx.metadata.jvm.JvmMethodSignature; +import kotlinx.metadata.jvm.internal.JvmPropertyExtension; +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 KotlinMetadataTest extends KotlinTestBase { + + private static final String PACKAGE = + "com.android.tools.r8.kotlin.metadata.syntheticmethodforannotations.kt"; + private static final String MAIN = PACKAGE + ".MetadataKt"; + private static final List<String> EXPECTED_OUTPUT = + ImmutableList.of("start", "All has @Test: true", "All2 has @Test: true", "end"); + private static final List<String> EXPECTED_FALSE_OUTPUT = + ImmutableList.of("start", "All has @Test: false", "All2 has @Test: false", "end"); + + private final TestParameters parameters; + + @Parameters(name = "{0}, {1}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withAllRuntimesAndApiLevels().build(), + getKotlinTestParameters().withAllCompilersLambdaGenerationsAndTargetVersions().build()); + } + + public KotlinMetadataTest(TestParameters parameters, KotlinTestParameters kotlinParameters) { + super(kotlinParameters); + this.parameters = parameters; + } + + private static final KotlinCompileMemoizer compilationResults = + getCompileMemoizer(getKotlinSources()); + + private static Collection<Path> getKotlinSources() { + try { + return getFilesInTestFolderRelativeToClass(KotlinMetadataTest.class, "kt", ".kt"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testJvm() throws ExecutionException, CompilationFailedException, IOException { + parameters.assumeJvmTestParameters(); + testForJvm(parameters) + .addProgramFiles(compilationResults.getForConfiguration(kotlinParameters)) + .addRunClasspathFiles( + buildOnDexRuntime( + parameters, kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar())) + .run(parameters.getRuntime(), MAIN) + .assertSuccessWithOutputLines(EXPECTED_OUTPUT); + } + + @Test + public void testD8() throws ExecutionException, CompilationFailedException, IOException { + parameters.assumeDexRuntime(); + testForD8(parameters.getBackend()) + .addProgramFiles(compilationResults.getForConfiguration(kotlinParameters)) + .addProgramFiles(kotlinc.getKotlinStdlibJar()) + .addProgramFiles(kotlinc.getKotlinReflectJar()) + .addProgramFiles(kotlinc.getKotlinAnnotationJar()) + .setMinApi(parameters) + .addOptionsModification( + options -> { + options.testing.enableD8ResourcesPassThrough = true; + options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer(); + }) + .run(parameters.getRuntime(), MAIN) + .assertSuccessWithOutputLines(EXPECTED_OUTPUT); + } + + @Test + public void testR8() throws Exception { + Assume.assumeFalse( + "Kotlin reflect failure on oldest compiler", + kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72)); + testForR8(parameters.getBackend()) + .addProgramFiles(compilationResults.getForConfiguration(kotlinParameters)) + .addProgramFiles(kotlinc.getKotlinStdlibJar()) + .addProgramFiles(kotlinc.getKotlinReflectJar()) + .addProgramFiles(kotlinc.getKotlinAnnotationJar()) + .addKeepRules( + "-keep class " + + PACKAGE + + ".**\n" + + "-keep,allowobfuscation class " + + PACKAGE + + ".** { *; }") + .addKeepEnumsRule() + .addKeepMainRule(MAIN) + .allowUnusedDontWarnPatterns() + .allowDiagnosticMessages() + .setMinApi(parameters) + .addOptionsModification( + options -> { + options.testing.enableD8ResourcesPassThrough = true; + options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer(); + }) + .compile() + .inspect(this::verifyRewrittenExtension) + .run(parameters.getRuntime(), MAIN) + .assertSuccessWithOutputLines(EXPECTED_OUTPUT); + } + + static JvmMethodSignature toJvmMethodSignature(DexMethod method) { + StringBuilder descBuilder = new StringBuilder(); + descBuilder.append("("); + for (DexType argType : method.proto.parameters.values) { + descBuilder.append(argType.toDescriptorString()); + } + descBuilder.append(")"); + descBuilder.append(method.proto.returnType.toDescriptorString()); + return new JvmMethodSignature(method.name.toString(), descBuilder.toString()); + } + + private void verifyRewrittenExtension(CodeInspector i) { + ClassSubject clazz = i.clazz(PACKAGE + ".Data$Companion"); + List<KmPropertySubject> properties = clazz.getKmClass().getProperties(); + assertEquals(2, properties.size()); + for (int i1 = 0; i1 < 2; i1++) { + KmPropertySubject kmPropertySubject = properties.get(i1); + assertTrue(kmPropertySubject.name().equals("All") || kmPropertySubject.name().equals("All2")); + List<KmPropertyExtension> extensions = + kmPropertySubject.getKmProperty().getExtensions$kotlinx_metadata(); + assertEquals(1, extensions.size()); + JvmMethodSignature syntheticMethodForAnnotations = + ((JvmPropertyExtension) extensions.get(0)).getSyntheticMethodForAnnotations(); + assertTrue( + clazz.allMethods().stream() + .anyMatch( + m -> + toJvmMethodSignature(m.getMethod().getReference()) + .equals(syntheticMethodForAnnotations))); + } + } + + @Test + public void testR8NoKR() throws Exception { + Assume.assumeFalse( + "Kotlin reflect failure on oldest compiler", + kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72)); + testForR8(parameters.getBackend()) + .addProgramFiles(compilationResults.getForConfiguration(kotlinParameters)) + .addProgramFiles(kotlinc.getKotlinStdlibJar()) + .addProgramFiles(kotlinc.getKotlinReflectJar()) + .addProgramFiles(kotlinc.getKotlinAnnotationJar()) + .addKeepRules( + "-keep class " + + PACKAGE + + ".**\n" + + "-keep,allowobfuscation class " + + PACKAGE + + ".** { getAll(); getAll2(); }") + .addKeepEnumsRule() + .addKeepMainRule(MAIN) + .allowUnusedDontWarnPatterns() + .allowDiagnosticMessages() + .setMinApi(parameters) + .addOptionsModification( + options -> { + options.testing.enableD8ResourcesPassThrough = true; + options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer(); + }) + .compile() + .run(parameters.getRuntime(), MAIN) + .assertSuccessWithOutputLines(EXPECTED_FALSE_OUTPUT); + } +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/syntheticmethodforannotations/kt/Metadata.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/syntheticmethodforannotations/kt/Metadata.kt new file mode 100644 index 0000000..74154f1 --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/syntheticmethodforannotations/kt/Metadata.kt
@@ -0,0 +1,27 @@ +// Copyright (c) 2023, 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.kotlin.metadata.syntheticmethodforannotations.kt + +import kotlin.reflect.full.declaredMemberProperties + +annotation class Test + +class Data { + companion object { + @Test + var All: Set<String> = emptySet() + + @property:Test + var All2: Set<String> = emptySet() + } +} + +fun main() { + println("start") + Data.Companion::class.declaredMemberProperties.forEach { + println("${it.name} has @Test: ${it.annotations.filterIsInstance<Test>().any()}") + } + println("end") +}
diff --git a/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticOutputCliTest.java b/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticOutputCliTest.java new file mode 100644 index 0000000..a07ca84 --- /dev/null +++ b/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticOutputCliTest.java
@@ -0,0 +1,320 @@ +// 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.synthesis.globals; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.D8; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestRuntime.CfRuntime; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.ToolHelper.ProcessResult; +import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.ListUtils; +import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class GlobalSyntheticOutputCliTest extends TestBase { + + static final String EXPECTED = + StringUtils.lines("Hello", "all good...", "Hello again", "still good..."); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.N_MR1).build(); + } + + public GlobalSyntheticOutputCliTest(TestParameters parameters) { + this.parameters = parameters; + } + + private static String getAndroidJar() { + return ToolHelper.getAndroidJar(AndroidApiLevel.LATEST).toString(); + } + + private String getApiLevelString() { + return "" + parameters.getApiLevel().getLevel(); + } + + private ProcessResult forkD8(String... args) throws IOException { + return forkD8(Arrays.asList(args)); + } + + private ProcessResult forkD8(List<String> args) throws IOException { + ImmutableList.Builder<String> command = + new ImmutableList.Builder<String>() + .add(CfRuntime.getSystemRuntime().getJavaExecutable().toString()) + .add("-Dcom.android.tools.r8.enableApiOutliningAndStubbing=1") + .add("-cp") + .add(System.getProperty("java.class.path")) + .add(D8.class.getName()) + .add("--min-api", getApiLevelString()) + .add("--lib", getAndroidJar()) + .addAll(args); + ProcessBuilder processBuilder = new ProcessBuilder(command.build()); + ProcessResult result = ToolHelper.runProcess(processBuilder); + assertEquals(result.toString(), 0, result.exitCode); + return result; + } + + @Test + public void testDexIndexedZip() throws Exception { + Path input1 = transformClass(TestClass1.class); + Path input2 = transformClass(TestClass2.class); + Path dexOut = temp.newFolder().toPath().resolve("out.jar"); + Path globalsOut = temp.newFolder().toPath().resolve("out.zip"); + forkD8( + input1.toString(), + input2.toString(), + "--intermediate", + "--output", + dexOut.toString(), + "--globals-output", + globalsOut.toString()); + + assertTrue(Files.exists(dexOut)); + assertTrue(Files.exists(globalsOut)); + + Path finalOut = temp.newFolder().toPath().resolve("out.jar"); + forkD8(dexOut.toString(), "--globals", globalsOut.toString(), "--output", finalOut.toString()); + + testForD8() + .addProgramFiles(finalOut) + .run(parameters.getRuntime(), TestClass1.class) + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testDexIndexedDir() throws Exception { + Path input1 = transformClass(TestClass1.class); + Path input2 = transformClass(TestClass2.class); + Path dexOut = temp.newFolder().toPath().resolve("out.jar"); + Path globalsOut = temp.newFolder("out").toPath(); + forkD8( + input1.toString(), + input2.toString(), + "--intermediate", + "--output", + dexOut.toString(), + "--globals-output", + globalsOut.toString()); + + Path expectedGlobalsFile = globalsOut.resolve("classes.globals"); + assertTrue(Files.exists(dexOut)); + assertTrue(Files.isDirectory(globalsOut)); + assertTrue(Files.exists(expectedGlobalsFile)); + + Path finalOut = temp.newFolder().toPath().resolve("out.jar"); + forkD8( + dexOut.toString(), + "--globals", + expectedGlobalsFile.toString(), + "--output", + finalOut.toString()); + + testForD8() + .addProgramFiles(finalOut) + .run(parameters.getRuntime(), TestClass1.class) + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testDexPerClassZip() throws Exception { + Path input1 = transformClass(TestClass1.class); + Path input2 = transformClass(TestClass2.class); + Path dexOut = temp.newFolder().toPath().resolve("out.jar"); + Path globalsOut = temp.newFolder().toPath().resolve("out.zip"); + forkD8( + input1.toString(), + input2.toString(), + "--file-per-class", + "--output", + dexOut.toString(), + "--globals-output", + globalsOut.toString()); + + assertTrue(Files.exists(dexOut)); + assertTrue(Files.exists(globalsOut)); + + Path finalOut = temp.newFolder().toPath().resolve("out.jar"); + forkD8(dexOut.toString(), "--globals", globalsOut.toString(), "--output", finalOut.toString()); + + testForD8() + .addProgramFiles(finalOut) + .run(parameters.getRuntime(), TestClass1.class) + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testDexPerClassDir() throws Exception { + Path input1 = transformClass(TestClass1.class); + Path input2 = transformClass(TestClass2.class); + Path dexOut = temp.newFolder().toPath().resolve("out.jar"); + Path globalsOut = temp.newFolder("out").toPath(); + forkD8( + input1.toString(), + input2.toString(), + "--file-per-class", + "--output", + dexOut.toString(), + "--globals-output", + globalsOut.toString()); + + assertTrue(Files.exists(dexOut)); + assertTrue(Files.isDirectory(globalsOut)); + + List<Path> globalFiles = + ImmutableList.of( + globalsOut.resolve(binaryName(TestClass1.class) + ".globals"), + globalsOut.resolve(binaryName(TestClass2.class) + ".globals")); + globalFiles.forEach(f -> assertTrue(Files.exists(f))); + + Path finalOut = temp.newFolder().toPath().resolve("out.jar"); + forkD8( + ImmutableList.<String>builder() + .add(dexOut.toString()) + .addAll( + ListUtils.flatMap(globalFiles, f -> ImmutableList.of("--globals", f.toString()))) + .add("--output") + .add(finalOut.toString()) + .build()); + + testForD8() + .addProgramFiles(finalOut) + .run(parameters.getRuntime(), TestClass1.class) + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testDexPerClassFileZip() throws Exception { + Path input1 = transformClass(TestClass1.class); + Path input2 = transformClass(TestClass2.class); + Path dexOut = temp.newFolder().toPath().resolve("out.jar"); + Path globalsOut = temp.newFolder().toPath().resolve("out.zip"); + forkD8( + input1.toString(), + input2.toString(), + "--file-per-class-file", + "--output", + dexOut.toString(), + "--globals-output", + globalsOut.toString()); + + assertTrue(Files.exists(dexOut)); + assertTrue(Files.exists(globalsOut)); + + Path finalOut = temp.newFolder().toPath().resolve("out.jar"); + forkD8(dexOut.toString(), "--globals", globalsOut.toString(), "--output", finalOut.toString()); + + testForD8() + .addProgramFiles(finalOut) + .run(parameters.getRuntime(), TestClass1.class) + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testDexPerClassFileDir() throws Exception { + Path input1 = transformClass(TestClass1.class); + Path input2 = transformClass(TestClass2.class); + Path dexOut = temp.newFolder().toPath().resolve("out.jar"); + Path globalsOut = temp.newFolder("out").toPath(); + forkD8( + input1.toString(), + input2.toString(), + "--file-per-class-file", + "--output", + dexOut.toString(), + "--globals-output", + globalsOut.toString()); + + assertTrue(Files.exists(dexOut)); + assertTrue(Files.isDirectory(globalsOut)); + + List<Path> globalFiles = + ImmutableList.of( + globalsOut.resolve(binaryName(TestClass1.class) + ".globals"), + globalsOut.resolve(binaryName(TestClass2.class) + ".globals")); + globalFiles.forEach(f -> assertTrue(Files.exists(f))); + + Path finalOut = temp.newFolder().toPath().resolve("out.jar"); + forkD8( + ImmutableList.<String>builder() + .add(dexOut.toString()) + .addAll( + ListUtils.flatMap(globalFiles, f -> ImmutableList.of("--globals", f.toString()))) + .add("--output") + .add(finalOut.toString()) + .build()); + + testForD8() + .addProgramFiles(finalOut) + .run(parameters.getRuntime(), TestClass1.class) + .assertSuccessWithOutput(EXPECTED); + } + + // Transform the class such that its handlers use the AuthenticationRequiredException which + // is introduced with API level 25. This triggers the need for a class stub for the exception. + private Path transformClass(Class<?> clazz) throws IOException { + byte[] bytes = + transformer(clazz) + .transformTryCatchBlock( + MethodPredicate.all(), + (start, end, handler, type, visitor) -> { + assertEquals("java/lang/Exception", type); + visitor.visitTryCatchBlock( + start, end, handler, "android/app/AuthenticationRequiredException"); + }) + .transform(); + Path file = temp.newFolder().toPath().resolve("input.class"); + Files.write(file, bytes); + return file; + } + + static class TestClass1 { + + public static void main(String[] args) { + Runnable r = + () -> { + try { + System.out.println("Hello"); + } catch (Exception /* will be AuthenticationRequiredException */ e) { + System.out.println("fail..."); + throw e; + } + System.out.println("all good..."); + }; + r.run(); + TestClass2.main(args); + } + } + + static class TestClass2 { + + public static void main(String[] args) { + try { + System.out.println("Hello again"); + } catch (Exception /* will be AuthenticationRequiredException */ e) { + System.out.println("fail..."); + throw e; + } + System.out.println("still good..."); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticStubContextRegressionTest.java b/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticStubContextRegressionTest.java new file mode 100644 index 0000000..54f5f0c --- /dev/null +++ b/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticStubContextRegressionTest.java
@@ -0,0 +1,110 @@ +// 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.synthesis.globals; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.OutputMode; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import java.io.IOException; +import java.nio.file.Path; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** Regression test for b/335803299. */ +@RunWith(Parameterized.class) +public class GlobalSyntheticStubContextRegressionTest extends TestBase { + + static final String EXPECTED = + StringUtils.lines("Hello", "all good...", "Hello again", "still good..."); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.N).build(); + } + + public GlobalSyntheticStubContextRegressionTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testDexPerClassFileDir() throws Exception { + GlobalSyntheticsTestingConsumer globals = new GlobalSyntheticsTestingConsumer(); + Path dexOut = + testForD8() + .apply(b -> b.getBuilder().setEnableExperimentalMissingLibraryApiModeling(true)) + .addProgramClassFileData( + transformClass(TestClass1.class), transformClass(TestClass2.class)) + .setMinApi(parameters) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST)) + .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals)) + .setOutputMode(OutputMode.DexFilePerClassFile) + .compile() + .writeToZip(); + + assertTrue(globals.hasGlobals()); + assertEquals(2, globals.getProviders().size()); + + testForD8() + .addProgramFiles(dexOut) + .setMinApi(parameters) + .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals.getProviders())) + .run(parameters.getRuntime(), TestClass1.class) + .assertSuccessWithOutput(EXPECTED); + } + + private byte[] transformClass(Class<?> clazz) throws IOException { + return transformer(clazz) + .transformTryCatchBlock( + MethodPredicate.all(), + (start, end, handler, type, visitor) -> { + assertEquals("java/lang/Exception", type); + visitor.visitTryCatchBlock( + start, end, handler, "android/app/AuthenticationRequiredException"); + }) + .transform(); + } + + static class TestClass1 { + + public static void main(String[] args) { + Runnable r = + () -> { + try { + System.out.println("Hello"); + } catch (Exception e) { + System.out.println("fail..."); + throw e; + } + System.out.println("all good..."); + }; + r.run(); + TestClass2.main(args); + } + } + + static class TestClass2 { + + public static void main(String[] args) { + try { + System.out.println("Hello again"); + } catch (Exception e) { + System.out.println("fail..."); + throw e; + } + System.out.println("still good..."); + } + } +}
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java index accf1b0..e2099f2 100644 --- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java +++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
@@ -70,6 +70,11 @@ } @Override + public KmProperty getKmProperty() { + return kmProperty; + } + + @Override public KmTypeSubject returnType() { return new KmTypeSubject(codeInspector, kmProperty.getReturnType()); }
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java index dc3f62e..77a6b40 100644 --- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java +++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
@@ -23,5 +23,9 @@ public abstract JvmMethodSignature setterSignature(); + public KmProperty getKmProperty() { + return null; + } + public abstract KmTypeSubject returnType(); }
diff --git a/tools/linux/host/art-master.tar.gz.sha1 b/tools/linux/host/art-master.tar.gz.sha1 index f79fc13..3c38992 100644 --- a/tools/linux/host/art-master.tar.gz.sha1 +++ b/tools/linux/host/art-master.tar.gz.sha1
@@ -1 +1 @@ -c8f7b9bc4de8b3f019281b656a3bff344341c21f \ No newline at end of file +d50068b3af94990273ee1c4e58c9691269681035 \ No newline at end of file
diff --git a/tools/run_benchmark.py b/tools/run_benchmark.py index 6800813..3a1efbf 100755 --- a/tools/run_benchmark.py +++ b/tools/run_benchmark.py
@@ -134,7 +134,8 @@ cmd.append('-Dcom.android.tools.r8.printtimes=1') if not options.golem: cmd.extend([ - f'-DTEST_DATA_LOCATION={utils.REPO_ROOT}/d8_r8/test_modules/tests_java_8/build/classes/java/test' + f'-DTEST_DATA_LOCATION={utils.REPO_ROOT}/d8_r8/test_modules/tests_java_8/build/classes/java/test', + f'-DTESTBASE_DATA_LOCATION={utils.REPO_ROOT}/d8_r8/test_modules/testbase/build/classes/java/main', ]) cmd.extend(['-cp', ':'.join([r8jar] + testjars)]) cmd.extend([