Merge commit '753744fdb2eadce122036738e2d5cad6f41c5386' into dev-release
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 9c6d6c3..3eab5e6 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -52,6 +52,7 @@
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
+import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.NestReducer;
import com.android.tools.r8.ir.optimize.SwitchMapCollector;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxingCfMethods;
@@ -621,8 +622,7 @@
new BridgeHoisting(appViewWithLiveness).run();
- // TODO(b/130721661): Enable this assert.
- // assert Inliner.verifyNoMethodsInlinedDueToSingleCallSite(appView);
+ assert Inliner.verifyAllSingleCallerMethodsHaveBeenPruned(appViewWithLiveness);
assert appView.allMergedClasses().verifyAllSourcesPruned(appViewWithLiveness);
assert appView.validateUnboxedEnumsHaveBeenPruned();
@@ -778,7 +778,7 @@
// Overwrite SourceFile if specified. This step should be done after IR conversion.
timing.begin("Rename SourceFile");
- new SourceFileRewriter(appView, appView.appInfo().app()).run();
+ new SourceFileRewriter(appView).run();
timing.end();
// If a method filter is present don't produce output since the application is likely partial.
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 5441a9e..dab9181 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -321,7 +321,7 @@
}
}
- private SourceFileEnvironment createSourceFileEnvironment(ProguardMapId proguardMapId) {
+ public static SourceFileEnvironment createSourceFileEnvironment(ProguardMapId proguardMapId) {
if (proguardMapId == null) {
return new SourceFileEnvironment() {
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 492a4ec..0e165f7 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.naming.SeedMapper;
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepClassInfo;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.shaking.KeepMethodInfo;
import com.android.tools.r8.shaking.LibraryModeledPredicate;
@@ -537,6 +538,10 @@
return keepInfo;
}
+ public KeepClassInfo getKeepInfo(DexProgramClass clazz) {
+ return getKeepInfo().getClassInfo(clazz);
+ }
+
public KeepMethodInfo getKeepInfo(ProgramMethod method) {
return getKeepInfo().getMethodInfo(method);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 450bc51..209e51c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -733,6 +733,11 @@
setCode(builder.build(), appView);
}
+ public void unsetCode() {
+ checkIfObsolete();
+ code = null;
+ }
+
public boolean keepLocals(InternalOptions options) {
if (options.testing.noLocalsTableOnInput) {
return false;
@@ -908,24 +913,6 @@
return getReference().toSourceString();
}
- public DexEncodedMethod toAbstractMethod() {
- checkIfObsolete();
- // 'final' wants this to be *not* overridden, while 'abstract' wants this to be implemented in
- // a subtype, i.e., self contradict.
- assert !accessFlags.isFinal();
- // static abstract is an invalid access combination and we should never create that.
- assert !accessFlags.isStatic();
- return builder(this)
- .modifyAccessFlags(MethodAccessFlags::setAbstract)
- .setIsLibraryMethodOverrideIf(
- isNonPrivateVirtualMethod() && !isLibraryMethodOverride().isUnknown(),
- isLibraryMethodOverride())
- .unsetCode()
- .addBuildConsumer(
- method -> OptimizationFeedbackSimple.getInstance().unsetBridgeInfo(method))
- .build();
- }
-
/**
* Generates a {@link DexCode} object for the given instructions.
*/
@@ -1369,6 +1356,10 @@
return apiLevelForCode;
}
+ public void clearApiLevelForCode(AppView<?> appView) {
+ this.apiLevelForCode = AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView);
+ }
+
public void setApiLevelForCode(AndroidApiLevel apiLevel) {
assert apiLevel != null;
this.apiLevelForCode = apiLevel;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index fdee405..22eaec8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -643,6 +643,10 @@
public final DexProto deserializeLambdaMethodProto =
createProto(objectType, serializedLambdaType);
+ public final String defaultSourceFileAttributeString = "SourceFile";
+ public final DexString defaultSourceFileAttribute =
+ createString(defaultSourceFileAttributeString);
+
// Dex system annotations.
// See https://source.android.com/devices/tech/dalvik/dex-format.html#system-annotation
public final DexType annotationDefault =
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index 23bd9f7..cd5e8de 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -127,6 +127,10 @@
set(Constants.ACC_SYNCHRONIZED);
}
+ public void demoteFromSynchronized() {
+ demote(Constants.ACC_SYNCHRONIZED);
+ }
+
public void unsetSynchronized() {
unset(Constants.ACC_SYNCHRONIZED);
}
@@ -179,6 +183,14 @@
set(Constants.ACC_ABSTRACT);
}
+ public void demoteFromAbstract() {
+ demote(Constants.ACC_ABSTRACT);
+ }
+
+ public void promoteToAbstract() {
+ promote(Constants.ACC_ABSTRACT);
+ }
+
public void unsetAbstract() {
unset(Constants.ACC_ABSTRACT);
}
@@ -191,6 +203,10 @@
set(Constants.ACC_STRICT);
}
+ public void demoteFromStrict() {
+ demote(Constants.ACC_STRICT);
+ }
+
public void unsetStrict() {
unset(Constants.ACC_STRICT);
}
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 c03a7b8..98c8bd2 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -3,16 +3,21 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback;
+
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
/** Type representing a method definition in the programs compilation unit and its holder. */
public final class ProgramMethod extends DexClassAndMethod
@@ -66,6 +71,47 @@
definition.parameterAnnotationsList.collectIndexedItems(indexedItems);
}
+ public boolean canBeConvertedToAbstractMethod(AppView<AppInfoWithLiveness> appView) {
+ return (appView.options().canUseAbstractMethodOnNonAbstractClass()
+ || getHolder().isAbstract()
+ || getHolder().isInterface())
+ && !getAccessFlags().isNative()
+ && !getAccessFlags().isPrivate()
+ && !getAccessFlags().isStatic()
+ && !appView.appInfo().isFailedResolutionTarget(getReference());
+ }
+
+ public void convertToAbstractOrThrowNullMethod(AppView<AppInfoWithLiveness> appView) {
+ if (!convertToAbstractMethodIfPossible(appView)) {
+ convertToThrowNullMethod(appView);
+ }
+ }
+
+ private boolean convertToAbstractMethodIfPossible(AppView<AppInfoWithLiveness> appView) {
+ boolean canBeAbstract = canBeConvertedToAbstractMethod(appView);
+ if (canBeAbstract) {
+ MethodAccessFlags accessFlags = getAccessFlags();
+ accessFlags.demoteFromFinal();
+ accessFlags.demoteFromStrict();
+ accessFlags.demoteFromSynchronized();
+ accessFlags.promoteToAbstract();
+ getDefinition().clearApiLevelForCode(appView);
+ getDefinition().unsetCode();
+ getSimpleFeedback().unsetOptimizationInfoForAbstractMethod(this);
+ }
+ return canBeAbstract;
+ }
+
+ public void convertToThrowNullMethod(AppView<?> appView) {
+ MethodAccessFlags accessFlags = getAccessFlags();
+ accessFlags.demoteFromAbstract();
+ Code emptyThrowingCode = getDefinition().buildEmptyThrowingCode(appView.options());
+ getDefinition().setApiLevelForCode(AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView));
+ getDefinition().setCode(emptyThrowingCode, appView);
+ getSimpleFeedback().markProcessed(getDefinition(), ConstraintWithTarget.ALWAYS);
+ getSimpleFeedback().unsetOptimizationInfoForThrowNullMethod(this);
+ }
+
public void registerCodeReferences(UseRegistry<?> registry) {
Code code = getDefinition().getCode();
if (code != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
index 92a127d..4899a50 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
@@ -82,6 +82,12 @@
continue;
}
+ if (method.getDefinition().isDefaultInitializer()
+ && appView.hasProguardCompatibilityActions()
+ && appView.getProguardCompatibilityActions().isCompatInstantiated(method.getHolder())) {
+ continue;
+ }
+
int numberOfCallSites = node.getNumberOfCallSites();
if (numberOfCallSites == 1) {
singleCallSite.add(reference);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 1d1a698..4fde21a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -270,7 +270,7 @@
: null;
this.enumUnboxer = EnumUnboxer.create(appViewWithLiveness);
this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, enumUnboxer);
- this.inliner = new Inliner(appViewWithLiveness, lensCodeRewriter);
+ this.inliner = new Inliner(appViewWithLiveness, this, lensCodeRewriter);
this.outliner = Outliner.create(appViewWithLiveness);
this.memberValuePropagation =
options.enableValuePropagation ? new MemberValuePropagation(appViewWithLiveness) : null;
@@ -360,8 +360,7 @@
public void convert(AppView<AppInfo> appView, ExecutorService executor)
throws ExecutionException {
LambdaDeserializationMethodRemover.run(appView);
- workaroundAbstractMethodOnNonAbstractClassVerificationBug(
- executor, OptimizationFeedbackIgnore.getInstance());
+ workaroundAbstractMethodOnNonAbstractClassVerificationBug(executor);
DexApplication application = appView.appInfo().app();
D8MethodProcessor methodProcessor = new D8MethodProcessor(this, executor);
InterfaceProcessor interfaceProcessor =
@@ -603,7 +602,7 @@
}
private void workaroundAbstractMethodOnNonAbstractClassVerificationBug(
- ExecutorService executorService, OptimizationFeedback feedback) throws ExecutionException {
+ ExecutorService executorService) throws ExecutionException {
if (!options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()) {
return;
}
@@ -612,13 +611,8 @@
appView.appInfo().classes(),
clazz -> {
if (!clazz.isAbstract()) {
- clazz.forEachMethod(
- method -> {
- if (method.isAbstract()) {
- method.accessFlags.unsetAbstract();
- finalizeEmptyThrowingCode(method, feedback);
- }
- });
+ clazz.forEachProgramMethodMatching(
+ DexEncodedMethod::isAbstract, method -> method.convertToThrowNullMethod(appView));
}
},
executorService);
@@ -633,8 +627,7 @@
DexApplication application = appView.appInfo().app();
computeReachabilitySensitivity(application);
- workaroundAbstractMethodOnNonAbstractClassVerificationBug(
- executorService, simpleOptimizationFeedback);
+ workaroundAbstractMethodOnNonAbstractClassVerificationBug(executorService);
// The process is in two phases in general.
// 1) Subject all DexEncodedMethods to optimization, except some optimizations that require
@@ -975,16 +968,6 @@
processMethodsConcurrently(methods, executorService);
}
- public void optimizeSynthesizedClasses(
- Collection<DexProgramClass> classes, ExecutorService executorService)
- throws ExecutionException {
- SortedProgramMethodSet methods = SortedProgramMethodSet.create();
- for (DexProgramClass clazz : classes) {
- clazz.forEachProgramMethod(methods::add);
- }
- processMethodsConcurrently(methods, executorService);
- }
-
public void optimizeSynthesizedMethod(ProgramMethod synthesizedMethod) {
if (!synthesizedMethod.getDefinition().isProcessed()) {
// Process the generated method, but don't apply any outlining.
@@ -1188,8 +1171,8 @@
+ ExceptionUtils.getMainStackTrace();
assert !method.isProcessed()
|| !appView.enableWholeProgramOptimizations()
- || !appView.appInfo().withLiveness().isNeverReprocessMethod(method.getReference())
- : "Illegal reprocessing due to -neverreprocess rule: " + context.toSourceString();
+ || !appView.appInfo().withLiveness().isNeverReprocessMethod(context)
+ : "Unexpected reprocessing of method: " + context.toSourceString();
if (typeChecker != null && !typeChecker.check(code)) {
assert appView.enableWholeProgramOptimizations();
@@ -1200,14 +1183,14 @@
+ method.toSourceString()
+ "` does not type check and will be assumed to be unreachable.");
options.reporter.warning(warning);
- finalizeEmptyThrowingCode(method, feedback);
+ context.convertToThrowNullMethod(appView);
return timing;
}
// This is the first point in time where we can assert that the types are sound. If this
// assert fails, then the types that we have inferred are unsound, or the method does not type
// check. In the latter case, the type checker should be extended to detect the issue such that
- // we will return with finalizeEmptyThrowingCode() above.
+ // we will return with a throw-null method above.
assert code.verifyTypes(appView);
assert code.isConsistentSSA();
@@ -1653,13 +1636,6 @@
}
}
- private void finalizeEmptyThrowingCode(DexEncodedMethod method, OptimizationFeedback feedback) {
- assert options.isGeneratingClassFiles() || options.isGeneratingDex();
- Code emptyThrowingCode = method.buildEmptyThrowingCode(options);
- method.setCode(emptyThrowingCode, appView);
- feedback.markProcessed(method, ConstraintWithTarget.ALWAYS);
- }
-
private void finalizeToCf(
IRCode code, OptimizationFeedback feedback, MethodConversionOptions conversionOptions) {
DexEncodedMethod method = code.method();
@@ -1984,6 +1960,7 @@
public void pruneMethod(ProgramMethod method) {
assert appView.enableWholeProgramOptimizations();
assert method.getHolder().lookupMethod(method.getReference()) == null;
+ appView.withArgumentPropagator(argumentPropagator -> argumentPropagator.pruneMethod(method));
if (inliner != null) {
inliner.pruneMethod(method);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 80b9cbd..6cf694a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BitSetUtils;
import java.util.BitSet;
import java.util.Set;
@@ -37,8 +38,6 @@
void methodReturnsAbstractValue(
DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue);
- void unsetAbstractReturnValue(ProgramMethod method);
-
void methodReturnsObjectWithUpperBoundType(
DexEncodedMethod method, AppView<?> appView, TypeElement type);
@@ -48,7 +47,7 @@
void methodReturnValueOnlyDependsOnArguments(DexEncodedMethod method);
- void methodNeverReturnsNormally(DexEncodedMethod method);
+ void methodNeverReturnsNormally(ProgramMethod method);
void markProcessed(DexEncodedMethod method, ConstraintWithTarget state);
@@ -66,8 +65,6 @@
void setEnumUnboxerMethodClassification(
ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification);
- void unsetEnumUnboxerMethodClassification(ProgramMethod method);
-
void setInstanceInitializerInfoCollection(
DexEncodedMethod method, InstanceInitializerInfoCollection instanceInitializerInfoCollection);
@@ -82,4 +79,89 @@
void classInitializerMayBePostponed(DexEncodedMethod method);
void setUnusedArguments(ProgramMethod method, BitSet unusedArguments);
+
+ // Unset methods.
+
+ void unsetAbstractReturnValue(ProgramMethod method);
+
+ void unsetBridgeInfo(DexEncodedMethod method);
+
+ void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method);
+
+ void unsetClassInitializerMayBePostponed(ProgramMethod method);
+
+ void unsetClassInlinerMethodConstraint(ProgramMethod method);
+
+ void unsetDynamicLowerBoundReturnType(ProgramMethod method);
+
+ void unsetDynamicUpperBoundReturnType(ProgramMethod method);
+
+ void unsetEnumUnboxerMethodClassification(ProgramMethod method);
+
+ void unsetForceInline(ProgramMethod method);
+
+ void unsetInitializedClassesOnNormalExit(ProgramMethod method);
+
+ void unsetInitializerEnablingJavaVmAssertions(ProgramMethod method);
+
+ void unsetInlinedIntoSingleCallSite(ProgramMethod method);
+
+ void unsetInstanceInitializerInfoCollection(ProgramMethod method);
+
+ void unsetMayNotHaveSideEffects(ProgramMethod method);
+
+ void unsetNeverReturnsNormally(ProgramMethod method);
+
+ void unsetNonNullParamOnNormalExits(ProgramMethod method);
+
+ void unsetNonNullParamOrThrow(ProgramMethod method);
+
+ void unsetReachabilitySensitive(ProgramMethod method);
+
+ void unsetReturnedArgument(ProgramMethod method);
+
+ void unsetReturnValueOnlyDependsOnArguments(ProgramMethod method);
+
+ void unsetSimpleInliningConstraint(ProgramMethod method);
+
+ void unsetTriggerClassInitBeforeAnySideEffect(ProgramMethod method);
+
+ void unsetUnusedArguments(ProgramMethod method);
+
+ default void unsetOptimizationInfoForAbstractMethod(ProgramMethod method) {
+ if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
+ unsetAbstractReturnValue(method);
+ unsetBridgeInfo(method.getDefinition());
+ unsetCheckNullReceiverBeforeAnySideEffect(method);
+ unsetClassInitializerMayBePostponed(method);
+ unsetClassInlinerMethodConstraint(method);
+ unsetDynamicLowerBoundReturnType(method);
+ unsetDynamicUpperBoundReturnType(method);
+ unsetEnumUnboxerMethodClassification(method);
+ unsetForceInline(method);
+ unsetInitializedClassesOnNormalExit(method);
+ unsetInitializerEnablingJavaVmAssertions(method);
+ unsetInstanceInitializerInfoCollection(method);
+ unsetMayNotHaveSideEffects(method);
+ unsetNeverReturnsNormally(method);
+ unsetNonNullParamOnNormalExits(method);
+ unsetNonNullParamOrThrow(method);
+ unsetReachabilitySensitive(method);
+ unsetReturnedArgument(method);
+ unsetReturnValueOnlyDependsOnArguments(method);
+ unsetSimpleInliningConstraint(method);
+ unsetTriggerClassInitBeforeAnySideEffect(method);
+ unsetUnusedArguments(method);
+ }
+ }
+
+ default void unsetOptimizationInfoForThrowNullMethod(ProgramMethod method) {
+ unsetOptimizationInfoForAbstractMethod(method);
+ methodNeverReturnsNormally(method);
+ setUnusedArguments(
+ method, BitSetUtils.createFilled(true, method.getDefinition().getNumberOfArguments()));
+ if (method.getDefinition().isInstance()) {
+ markCheckNullReceiverBeforeAnySideEffect(method.getDefinition(), true);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index a54b717..6435f37 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -82,10 +82,6 @@
methodsToRevisit.forEach(this::add);
}
- public void put(PostOptimization postOptimization) {
- put(postOptimization.methodsToRevisit());
- }
-
// Some optimizations may change methods, creating new instances of the encoded methods with a
// new signature. The compiler needs to update the set of methods that must be reprocessed
// according to the graph lens.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java b/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
deleted file mode 100644
index 30c873d..0000000
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2019, 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.ir.conversion;
-
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
-
-/**
- * An abstraction of optimizations that require post processing of methods.
- */
-public interface PostOptimization {
-
- /** @return a set of methods that need post processing. */
- ProgramMethodSet methodsToRevisit();
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
index 84e3558..326b3c9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
@@ -57,6 +57,7 @@
private final Map<DexType, DexType> backportCoreLibraryMember;
private final Map<DexType, DexType> customConversions;
private final List<Pair<DexType, DexString>> dontRewriteInvocation;
+ private final Set<DexType> dontRetargetLibMember;
private final List<String> extraKeepRules;
private final Set<DexType> wrapperConversions;
private final PrefixRewritingMapper prefixRewritingMapper;
@@ -81,6 +82,7 @@
ImmutableMap.of(),
ImmutableSet.of(),
ImmutableList.of(),
+ ImmutableSet.of(),
ImmutableList.of(),
new DesugarPrefixRewritingMapper(prefix, options.itemFactory, true));
}
@@ -100,6 +102,7 @@
ImmutableMap.of(),
ImmutableSet.of(),
ImmutableList.of(),
+ ImmutableSet.of(),
ImmutableList.of(),
PrefixRewritingMapper.empty()) {
@@ -129,6 +132,7 @@
Map<DexType, DexType> customConversions,
Set<DexType> wrapperConversions,
List<Pair<DexType, DexString>> dontRewriteInvocation,
+ Set<DexType> dontRetargetLibMember,
List<String> extraKeepRules,
PrefixRewritingMapper prefixRewritingMapper) {
this.requiredCompilationAPILevel = requiredCompilationAPILevel;
@@ -144,6 +148,7 @@
this.customConversions = customConversions;
this.wrapperConversions = wrapperConversions;
this.dontRewriteInvocation = dontRewriteInvocation;
+ this.dontRetargetLibMember = dontRetargetLibMember;
this.extraKeepRules = extraKeepRules;
this.prefixRewritingMapper = prefixRewritingMapper;
}
@@ -233,6 +238,10 @@
return dontRewriteInvocation;
}
+ public Set<DexType> getDontRetargetLibMember() {
+ return dontRetargetLibMember;
+ }
+
public List<String> getExtraKeepRules() {
return extraKeepRules;
}
@@ -262,6 +271,8 @@
private Map<DexType, DexType> customConversions = new IdentityHashMap<>();
private Set<DexType> wrapperConversions = Sets.newIdentityHashSet();
private List<Pair<DexType, DexString>> dontRewriteInvocation = new ArrayList<>();
+ private Set<DexType> dontRetargetLibMember = Sets.newIdentityHashSet();
+ ;
private List<String> extraKeepRules = Collections.emptyList();
private boolean supportAllCallbacksFromLibrary = FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY;
@@ -395,6 +406,11 @@
return this;
}
+ public Builder addDontRetargetLibMember(String dontRetargetLibMember) {
+ this.dontRetargetLibMember.add(stringClassToDexType(dontRetargetLibMember));
+ return this;
+ }
+
private int sharpIndex(String typeAndSelector, String descr) {
int index = typeAndSelector.lastIndexOf('#');
if (index <= 0 || index >= typeAndSelector.length() - 1) {
@@ -428,6 +444,7 @@
ImmutableMap.copyOf(customConversions),
ImmutableSet.copyOf(wrapperConversions),
ImmutableList.copyOf(dontRewriteInvocation),
+ ImmutableSet.copyOf(dontRetargetLibMember),
ImmutableList.copyOf(extraKeepRules),
rewritePrefix.isEmpty()
? PrefixRewritingMapper.empty()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfigurationParser.java
index 9b0862e..f8b69f6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfigurationParser.java
@@ -45,6 +45,7 @@
static final String RETARGET_LIB_MEMBER_KEY = "retarget_lib_member";
static final String EMULATE_INTERFACE_KEY = "emulate_interface";
static final String DONT_REWRITE_KEY = "dont_rewrite";
+ static final String DONT_RETARGET_LIB_MEMBER_KEY = "dont_retarget_lib_member";
static final String BACKPORT_KEY = "backport";
static final String SHRINKER_CONFIG_KEY = "shrinker_config";
static final String SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY = "support_all_callbacks_from_library";
@@ -222,5 +223,11 @@
configurationBuilder.addDontRewriteInvocation(rewrite.getAsString());
}
}
+ if (jsonFlagSet.has(DONT_RETARGET_LIB_MEMBER_KEY)) {
+ JsonArray dontRetarget = jsonFlagSet.get(DONT_RETARGET_LIB_MEMBER_KEY).getAsJsonArray();
+ for (JsonElement rewrite : dontRetarget) {
+ configurationBuilder.addDontRetargetLibMember(rewrite.getAsString());
+ }
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
index c268bfd..1a2adfb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
@@ -121,6 +121,13 @@
// The class has already been desugared.
continue;
}
+ if (appView
+ .options()
+ .desugaredLibraryConfiguration
+ .getDontRetargetLibMember()
+ .contains(clazz.getType())) {
+ continue;
+ }
clazz.addExtraInterfaces(
Collections.singletonList(new ClassTypeSignature(newInterface.type)));
eventConsumer.acceptInterfaceInjection(clazz, newInterface);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java
new file mode 100644
index 0000000..76c468a6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2021, 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.ir.desugar.records;
+
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeDynamicOnRecord;
+
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
+import com.android.tools.r8.naming.NamingLens;
+import java.util.ArrayList;
+
+/** Used to shrink records in Cf to Cf compilations */
+public class RecordCfToCfRewriter {
+
+ private final AppView<?> appView;
+
+ public static RecordCfToCfRewriter create(AppView<?> appView) {
+ if (appView.enableWholeProgramOptimizations()
+ && appView.options().isGeneratingClassFiles()
+ && appView.options().testing.enableRecordModeling) {
+ return new RecordCfToCfRewriter(appView);
+ }
+ return null;
+ }
+
+ private RecordCfToCfRewriter(AppView<?> appView) {
+ this.appView = appView;
+ }
+
+ // Called after final tree shaking, prune and minify field names and field values.
+ public CfInvokeDynamic rewriteRecordInvokeDynamic(
+ CfInvokeDynamic invokeDynamic, ProgramMethod context, NamingLens namingLens) {
+ if (!isInvokeDynamicOnRecord(invokeDynamic, appView, context)) {
+ return invokeDynamic;
+ }
+ RecordInvokeDynamic recordInvokeDynamic =
+ parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
+ DexString newFieldNames =
+ recordInvokeDynamic
+ .computeRecordFieldNamesComputationInfo()
+ .internalComputeNameFor(
+ recordInvokeDynamic.getRecordClass().type,
+ appView,
+ appView.graphLens(),
+ namingLens);
+ DexField[] newFields = computePresentFields(appView.graphLens(), recordInvokeDynamic);
+ return writeRecordInvokeDynamic(
+ recordInvokeDynamic.withFieldNamesAndFields(newFieldNames, newFields));
+ }
+
+ private DexField[] computePresentFields(
+ GraphLens graphLens, RecordInvokeDynamic recordInvokeDynamic) {
+ ArrayList<DexField> finalFields = new ArrayList<>();
+ for (DexField field : recordInvokeDynamic.getFields()) {
+ DexEncodedField dexEncodedField =
+ recordInvokeDynamic
+ .getRecordClass()
+ .lookupInstanceField(graphLens.getRenamedFieldSignature(field));
+ if (dexEncodedField != null) {
+ finalFields.add(field);
+ }
+ }
+ DexField[] newFields = new DexField[finalFields.size()];
+ for (int i = 0; i < finalFields.size(); i++) {
+ newFields[i] = finalFields.get(i);
+ }
+ return newFields;
+ }
+
+ private CfInvokeDynamic writeRecordInvokeDynamic(RecordInvokeDynamic recordInvokeDynamic) {
+ DexItemFactory factory = appView.dexItemFactory();
+ DexMethodHandle bootstrapMethod =
+ new DexMethodHandle(
+ MethodHandleType.INVOKE_STATIC, factory.objectMethodsMembers.bootstrap, false, null);
+ ArrayList<DexValue> bootstrapArgs = new ArrayList<>();
+ bootstrapArgs.add(new DexValueType(recordInvokeDynamic.getRecordClass().type));
+ bootstrapArgs.add(new DexValueString(recordInvokeDynamic.getFieldNames()));
+ for (DexField field : recordInvokeDynamic.getFields()) {
+ bootstrapArgs.add(
+ new DexValueMethodHandle(
+ new DexMethodHandle(MethodHandleType.INSTANCE_GET, field, false, null)));
+ }
+ return new CfInvokeDynamic(
+ factory.createCallSite(
+ recordInvokeDynamic.getMethodName(),
+ recordInvokeDynamic.getMethodProto(),
+ bootstrapMethod,
+ bootstrapArgs));
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
index 0c1ee6e..3829aa0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
@@ -6,9 +6,12 @@
import static com.android.tools.r8.cf.code.CfStackInstruction.Opcode.Dup;
import static com.android.tools.r8.cf.code.CfStackInstruction.Opcode.Swap;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeDynamicOnRecord;
import com.android.tools.r8.cf.code.CfConstClass;
import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
import com.android.tools.r8.cf.code.CfFieldInstruction;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
@@ -21,20 +24,14 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring;
@@ -48,6 +45,7 @@
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.android.tools.r8.ir.desugar.ProgramAdditions;
import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
import com.android.tools.r8.ir.synthetic.CallObjectInitCfCodeProvider;
import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordEqualsCfCodeProvider;
import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordGetFieldsAsObjectsCfCodeProvider;
@@ -100,14 +98,15 @@
CfCode cfCode = method.getDefinition().getCode().asCfCode();
for (CfInstruction instruction : cfCode.getInstructions()) {
if (instruction.isInvokeDynamic() && needsDesugaring(instruction, method)) {
- prepareInvokeDynamicOnRecord(instruction.asInvokeDynamic(), method, programAdditions);
+ prepareInvokeDynamicOnRecord(instruction.asInvokeDynamic(), programAdditions, method);
}
}
}
private void prepareInvokeDynamicOnRecord(
- CfInvokeDynamic invokeDynamic, ProgramMethod context, ProgramAdditions programAdditions) {
- RecordInvokeDynamic recordInvokeDynamic = parseInvokeDynamicOnRecord(invokeDynamic, context);
+ CfInvokeDynamic invokeDynamic, ProgramAdditions programAdditions, ProgramMethod context) {
+ RecordInvokeDynamic recordInvokeDynamic =
+ parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName
|| recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions);
@@ -178,8 +177,8 @@
return desugarInvokeDynamicOnRecord(
instruction.asInvokeDynamic(),
localStackAllocator,
- context,
eventConsumer,
+ context,
methodProcessingContext);
}
assert instruction.isInvoke();
@@ -191,71 +190,17 @@
new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface()));
}
- static class RecordInvokeDynamic {
-
- private final DexString methodName;
- private final DexString fieldNames;
- private final DexField[] fields;
- private final DexProgramClass recordClass;
-
- private RecordInvokeDynamic(
- DexString methodName,
- DexString fieldNames,
- DexField[] fields,
- DexProgramClass recordClass) {
- this.methodName = methodName;
- this.fieldNames = fieldNames;
- this.fields = fields;
- this.recordClass = recordClass;
- }
-
- DexField[] getFields() {
- return fields;
- }
-
- DexProgramClass getRecordClass() {
- return recordClass;
- }
-
- DexString getFieldNames() {
- return fieldNames;
- }
-
- DexString getMethodName() {
- return methodName;
- }
- }
-
- private RecordInvokeDynamic parseInvokeDynamicOnRecord(
- CfInvokeDynamic invokeDynamic, ProgramMethod context) {
- assert needsDesugaring(invokeDynamic, context);
- DexCallSite callSite = invokeDynamic.getCallSite();
- DexValueType recordValueType = callSite.bootstrapArgs.get(0).asDexValueType();
- DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
- DexString fieldNames = valueString.getValue();
- DexField[] fields = new DexField[callSite.bootstrapArgs.size() - 2];
- for (int i = 2; i < callSite.bootstrapArgs.size(); i++) {
- DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
- fields[i - 2] = handle.value.member.asDexField();
- }
- DexProgramClass recordClass =
- appView.definitionFor(recordValueType.getValue()).asProgramClass();
- return new RecordInvokeDynamic(callSite.methodName, fieldNames, fields, recordClass);
- }
-
private List<CfInstruction> desugarInvokeDynamicOnRecord(
CfInvokeDynamic invokeDynamic,
LocalStackAllocator localStackAllocator,
- ProgramMethod context,
CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
MethodProcessingContext methodProcessingContext) {
- RecordInvokeDynamic recordInvokeDynamic = parseInvokeDynamicOnRecord(invokeDynamic, context);
+ RecordInvokeDynamic recordInvokeDynamic =
+ parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName) {
return desugarInvokeRecordToString(
- recordInvokeDynamic,
- localStackAllocator,
- eventConsumer,
- methodProcessingContext);
+ recordInvokeDynamic, localStackAllocator, eventConsumer, methodProcessingContext);
}
if (recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
return desugarInvokeRecordHashCode(
@@ -392,7 +337,15 @@
ArrayList<CfInstruction> instructions = new ArrayList<>();
instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false));
instructions.add(new CfConstClass(recordInvokeDynamic.getRecordClass().type, true));
- instructions.add(new CfConstString(recordInvokeDynamic.getFieldNames()));
+ if (appView.options().testing.enableRecordModeling
+ && appView.enableWholeProgramOptimizations()) {
+ instructions.add(
+ new CfDexItemBasedConstString(
+ recordInvokeDynamic.getRecordClass().type,
+ recordInvokeDynamic.computeRecordFieldNamesComputationInfo()));
+ } else {
+ instructions.add(new CfConstString(recordInvokeDynamic.getFieldNames()));
+ }
ProgramMethod programMethod =
synthesizeRecordHelper(
recordToStringHelperProto,
@@ -426,9 +379,7 @@
appView,
builder -> {
DexEncodedMethod init = synthesizeRecordInitMethod();
- builder
- .setAbstract()
- .setDirectMethods(ImmutableList.of(init));
+ builder.setAbstract().setDirectMethods(ImmutableList.of(init));
},
eventConsumer::acceptRecordClass);
}
@@ -484,75 +435,7 @@
}
private boolean needsDesugaring(CfInvokeDynamic invokeDynamic, ProgramMethod context) {
- DexCallSite callSite = invokeDynamic.getCallSite();
- // 1. Validates this is an invoke-static to ObjectMethods#bootstrap.
- DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
- if (!bootstrapMethod.type.isInvokeStatic()) {
- return false;
- }
- if (bootstrapMethod.member != factory.objectMethodsMembers.bootstrap) {
- return false;
- }
- // From there on we assume in the assertions that the invoke to the library method is
- // well-formed. If the invoke is not well formed assertions will fail but the execution is
- // correct.
- if (bootstrapMethod.isInterface) {
- assert false
- : "Invoke-dynamic invoking non interface method ObjectMethods#bootstrap as an interface"
- + " method.";
- return false;
- }
- // 2. Validate the bootstrapArgs include the record type, the instance field names and
- // the corresponding instance getters.
- if (callSite.bootstrapArgs.size() < 2) {
- assert false
- : "Invoke-dynamic invoking method ObjectMethods#bootstrap with less than 2 parameters.";
- return false;
- }
- DexValueType recordType = callSite.bootstrapArgs.get(0).asDexValueType();
- if (recordType == null) {
- assert false : "Invoke-dynamic invoking method ObjectMethods#bootstrap with an invalid type.";
- return false;
- }
- DexClass recordClass = appView.definitionFor(recordType.getValue());
- if (recordClass == null || recordClass.isNotProgramClass()) {
- return false;
- }
- DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
- if (valueString == null) {
- assert false
- : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid field names.";
- return false;
- }
- DexString fieldNames = valueString.getValue();
- assert fieldNames.toString().isEmpty()
- || (fieldNames.toString().split(";").length == callSite.bootstrapArgs.size() - 2);
- assert recordClass.instanceFields().size() == callSite.bootstrapArgs.size() - 2;
- for (int i = 2; i < callSite.bootstrapArgs.size(); i++) {
- DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
- if (handle == null
- || !handle.value.type.isInstanceGet()
- || !handle.value.member.isDexField()) {
- assert false
- : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid getters.";
- return false;
- }
- }
- // 3. Create the invoke-record instruction.
- if (callSite.methodName == factory.toStringMethodName) {
- assert callSite.methodProto == factory.createProto(factory.stringType, recordClass.getType());
- return true;
- }
- if (callSite.methodName == factory.hashCodeMethodName) {
- assert callSite.methodProto == factory.createProto(factory.intType, recordClass.getType());
- return true;
- }
- if (callSite.methodName == factory.equalsMethodName) {
- assert callSite.methodProto
- == factory.createProto(factory.booleanType, recordClass.getType(), factory.objectType);
- return true;
- }
- return false;
+ return isInvokeDynamicOnRecord(invokeDynamic, appView, context);
}
@SuppressWarnings("ConstantConditions")
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
new file mode 100644
index 0000000..dc589e2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
@@ -0,0 +1,166 @@
+// Copyright (c) 2021, 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.ir.desugar.records;
+
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.naming.dexitembasedstring.RecordFieldNamesComputationInfo;
+
+public class RecordRewriterHelper {
+
+ public static boolean isInvokeDynamicOnRecord(
+ CfInvokeDynamic invokeDynamic, AppView<?> appView, ProgramMethod context) {
+ DexItemFactory factory = appView.dexItemFactory();
+ DexCallSite callSite = invokeDynamic.getCallSite();
+ // 1. Validates this is an invoke-static to ObjectMethods#bootstrap.
+ DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
+ if (!bootstrapMethod.type.isInvokeStatic()) {
+ return false;
+ }
+ if (bootstrapMethod.member != factory.objectMethodsMembers.bootstrap) {
+ return false;
+ }
+ // From there on we assume in the assertions that the invoke to the library method is
+ // well-formed. If the invoke is not well formed assertions will fail but the execution is
+ // correct.
+ if (bootstrapMethod.isInterface) {
+ assert false
+ : "Invoke-dynamic invoking non interface method ObjectMethods#bootstrap as an interface"
+ + " method.";
+ return false;
+ }
+ // 2. Validate the bootstrapArgs include the record type, the instance field names and
+ // the corresponding instance getters.
+ if (callSite.bootstrapArgs.size() < 2) {
+ assert false
+ : "Invoke-dynamic invoking method ObjectMethods#bootstrap with less than 2 parameters.";
+ return false;
+ }
+ DexValueType recordType = callSite.bootstrapArgs.get(0).asDexValueType();
+ if (recordType == null) {
+ assert false : "Invoke-dynamic invoking method ObjectMethods#bootstrap with an invalid type.";
+ return false;
+ }
+ DexClass recordClass = appView.definitionFor(recordType.getValue(), context);
+ if (recordClass == null || recordClass.isNotProgramClass()) {
+ return false;
+ }
+ DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
+ if (valueString == null) {
+ assert false
+ : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid field names.";
+ return false;
+ }
+ DexString fieldNames = valueString.getValue();
+ assert fieldNames.toString().isEmpty()
+ || (fieldNames.toString().split(";").length == callSite.bootstrapArgs.size() - 2);
+ assert recordClass.instanceFields().size() == callSite.bootstrapArgs.size() - 2;
+ for (int i = 2; i < callSite.bootstrapArgs.size(); i++) {
+ DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
+ if (handle == null
+ || !handle.value.type.isInstanceGet()
+ || !handle.value.member.isDexField()) {
+ assert false
+ : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid getters.";
+ return false;
+ }
+ }
+ // 3. Check it matches one of the 3 invokeDynamicOnRecord instruction.
+ if (callSite.methodName == factory.toStringMethodName) {
+ assert callSite.methodProto == factory.createProto(factory.stringType, recordClass.getType());
+ return true;
+ }
+ if (callSite.methodName == factory.hashCodeMethodName) {
+ assert callSite.methodProto == factory.createProto(factory.intType, recordClass.getType());
+ return true;
+ }
+ if (callSite.methodName == factory.equalsMethodName) {
+ assert callSite.methodProto
+ == factory.createProto(factory.booleanType, recordClass.getType(), factory.objectType);
+ return true;
+ }
+ return false;
+ }
+
+ public static RecordInvokeDynamic parseInvokeDynamicOnRecord(
+ CfInvokeDynamic invokeDynamic, AppView<?> appView, ProgramMethod context) {
+ assert isInvokeDynamicOnRecord(invokeDynamic, appView, context);
+ DexCallSite callSite = invokeDynamic.getCallSite();
+ DexValueType recordValueType = callSite.bootstrapArgs.get(0).asDexValueType();
+ DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
+ DexString fieldNames = valueString.getValue();
+ DexField[] fields = new DexField[callSite.bootstrapArgs.size() - 2];
+ for (int i = 2; i < callSite.bootstrapArgs.size(); i++) {
+ DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
+ fields[i - 2] = handle.value.member.asDexField();
+ }
+ DexProgramClass recordClass =
+ appView.definitionFor(recordValueType.getValue()).asProgramClass();
+ return new RecordInvokeDynamic(
+ callSite.methodName, callSite.methodProto, fieldNames, fields, recordClass);
+ }
+
+ static class RecordInvokeDynamic {
+
+ private final DexString methodName;
+ private final DexProto methodProto;
+ private final DexString fieldNames;
+ private final DexField[] fields;
+ private final DexProgramClass recordClass;
+
+ private RecordInvokeDynamic(
+ DexString methodName,
+ DexProto methodProto,
+ DexString fieldNames,
+ DexField[] fields,
+ DexProgramClass recordClass) {
+ this.methodName = methodName;
+ this.methodProto = methodProto;
+ this.fieldNames = fieldNames;
+ this.fields = fields;
+ this.recordClass = recordClass;
+ }
+
+ RecordInvokeDynamic withFieldNamesAndFields(DexString fieldNames, DexField[] fields) {
+ return new RecordInvokeDynamic(methodName, methodProto, fieldNames, fields, recordClass);
+ }
+
+ DexField[] getFields() {
+ return fields;
+ }
+
+ DexProgramClass getRecordClass() {
+ return recordClass;
+ }
+
+ DexString getFieldNames() {
+ return fieldNames;
+ }
+
+ DexString getMethodName() {
+ return methodName;
+ }
+
+ DexProto getMethodProto() {
+ return methodProto;
+ }
+
+ RecordFieldNamesComputationInfo computeRecordFieldNamesComputationInfo() {
+ return RecordFieldNamesComputationInfo.forFieldNamesAndFields(getFieldNames(), getFields());
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index abf7239..ecf8f25 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import static com.google.common.base.Predicates.not;
import com.android.tools.r8.androidapi.AvailableApiExceptions;
@@ -47,6 +48,7 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.LensCodeRewriter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
@@ -78,10 +80,12 @@
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
public class Inliner {
protected final AppView<AppInfoWithLiveness> appView;
+ private final IRConverter converter;
private final Set<DexMethod> extraNeverInlineMethods;
private final LensCodeRewriter lensCodeRewriter;
final MainDexInfo mainDexInfo;
@@ -96,13 +100,18 @@
private final Map<DexEncodedMethod, ProgramMethod> doubleInlineeCandidates =
new IdentityHashMap<>();
+ private final Map<DexProgramClass, ProgramMethodSet> singleCallerInlinedMethods =
+ new ConcurrentHashMap<>();
+
private final AvailableApiExceptions availableApiExceptions;
public Inliner(
AppView<AppInfoWithLiveness> appView,
+ IRConverter converter,
LensCodeRewriter lensCodeRewriter) {
Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
this.appView = appView;
+ this.converter = converter;
this.extraNeverInlineMethods =
appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations
? ImmutableSet.of()
@@ -1130,7 +1139,15 @@
appView, code, inlinee.code, blockIterator, blocksToRemove, downcastClass);
if (inlinee.reason == Reason.SINGLE_CALLER) {
+ assert converter.isInWave();
feedback.markInlinedIntoSingleCallSite(singleTargetMethod);
+ if (singleCallerInlinedMethods.isEmpty()) {
+ converter.addWaveDoneAction(this::onWaveDone);
+ }
+ singleCallerInlinedMethods
+ .computeIfAbsent(
+ singleTarget.getHolder(), ignoreKey(ProgramMethodSet::createConcurrent))
+ .add(singleTarget);
}
classInitializationAnalysis.notifyCodeHasChanged();
@@ -1304,11 +1321,41 @@
singleInlineCallers.remove(method.getReference(), appView.graphLens());
}
- public static boolean verifyNoMethodsInlinedDueToSingleCallSite(AppView<?> appView) {
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- for (DexEncodedMethod method : clazz.methods()) {
- assert !method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite();
- }
+ private void onWaveDone() {
+ singleCallerInlinedMethods.forEach(
+ (clazz, singleCallerInlinedMethodsForClass) -> {
+ // Convert and remove virtual single caller inlined methods to abstract or throw null.
+ singleCallerInlinedMethodsForClass.removeIf(
+ singleCallerInlinedMethod -> {
+ if (singleCallerInlinedMethod.getDefinition().belongsToVirtualPool() || true) {
+ singleCallerInlinedMethod.convertToAbstractOrThrowNullMethod(appView);
+ return true;
+ }
+ return false;
+ });
+
+ // Remove direct single caller inlined methods from the application.
+ if (!singleCallerInlinedMethodsForClass.isEmpty()) {
+ clazz
+ .getMethodCollection()
+ .removeMethods(
+ singleCallerInlinedMethodsForClass.toDefinitionSet(
+ SetUtils::newIdentityHashSet));
+ singleCallerInlinedMethodsForClass.forEach(converter::pruneMethod);
+ }
+ });
+ singleCallerInlinedMethods.clear();
+ }
+
+ public static boolean verifyAllSingleCallerMethodsHaveBeenPruned(
+ AppView<AppInfoWithLiveness> appView) {
+ for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
+ clazz.forEachProgramMethodMatching(
+ method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite(),
+ method -> {
+ assert !method.getDefinition().hasCode()
+ || !method.canBeConvertedToAbstractMethod(appView);
+ });
}
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 26430a4..854a4e0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -638,9 +638,21 @@
postMethodProcessorBuilder
.getMethodsToReprocessBuilder()
.rewrittenWithLens(appView)
- .merge(dependencies)
- .merge(methodsDependingOnLibraryModelisation)
- .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods());
+ .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods())
+ .merge(
+ dependencies
+ .rewrittenWithLens(appView)
+ .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods())
+ .removeIf(
+ appView,
+ method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()))
+ .merge(
+ methodsDependingOnLibraryModelisation
+ .rewrittenWithLens(appView)
+ .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods())
+ .removeIf(
+ appView,
+ method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()));
methodsDependingOnLibraryModelisation.clear();
updateOptimizationInfos(executorService, feedback, treeFixerResult);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index a9c52e4..4959de0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -179,7 +179,7 @@
DexEncodedMethod method = context.getDefinition();
List<BasicBlock> normalExits = code.computeNormalExitBlocks();
if (normalExits.isEmpty()) {
- feedback.methodNeverReturnsNormally(method);
+ feedback.methodNeverReturnsNormally(context);
return;
}
Return firstExit = normalExits.get(0).exit().asReturn();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 9f1ea2d..06a25cf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.util.BitSet;
+import java.util.Collections;
import java.util.Set;
public class MutableMethodOptimizationInfo extends MethodOptimizationInfo
@@ -251,6 +252,10 @@
setFlag(CLASS_INITIALIZER_MAY_BE_POSTPONED_FLAG);
}
+ void unsetClassInitializerMayBePostponed() {
+ clearFlag(CLASS_INITIALIZER_MAY_BE_POSTPONED_FLAG);
+ }
+
@Override
public ClassInlinerMethodConstraint getClassInlinerMethodConstraint() {
return classInlinerConstraint;
@@ -341,6 +346,10 @@
this.nonNullParamOrThrow = facts;
}
+ void unsetNonNullParamOrThrow() {
+ this.nonNullParamOrThrow = null;
+ }
+
@Override
public BitSet getNonNullParamOnNormalExits() {
return nonNullParamOnNormalExits;
@@ -356,19 +365,23 @@
this.nonNullParamOnNormalExits = facts;
}
+ void unsetNonNullParamOnNormalExits() {
+ nonNullParamOnNormalExits = null;
+ }
+
@Override
public boolean hasBeenInlinedIntoSingleCallSite() {
return isFlagSet(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
}
- void unsetInlinedIntoSingleCallSite() {
- clearFlag(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
- }
-
void markInlinedIntoSingleCallSite() {
setFlag(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
}
+ void unsetInlinedIntoSingleCallSite() {
+ clearFlag(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
+ }
+
@Override
public boolean isReachabilitySensitive() {
return isFlagSet(REACHABILITY_SENSITIVE_FLAG);
@@ -449,6 +462,10 @@
unusedArguments != null && !unusedArguments.isEmpty() ? unusedArguments : null;
}
+ void unsetUnusedArguments() {
+ unusedArguments = null;
+ }
+
@Override
public boolean isInitializerEnablingJavaVmAssertions() {
return isFlagSet(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
@@ -494,10 +511,18 @@
setFlag(REACHABILITY_SENSITIVE_FLAG, reachabilitySensitive);
}
+ void unsetReachabilitySensitive() {
+ clearFlag(REACHABILITY_SENSITIVE_FLAG);
+ }
+
void setSimpleInliningConstraint(SimpleInliningConstraint constraint) {
this.simpleInliningConstraint = constraint;
}
+ void unsetSimpleInliningConstraint() {
+ simpleInliningConstraint = NeverSimpleInliningConstraint.getInstance();
+ }
+
public MutableMethodOptimizationInfo fixupSimpleInliningConstraint(
AppView<AppInfoWithLiveness> appView, MethodOptimizationInfoFixer fixer) {
simpleInliningConstraint =
@@ -511,20 +536,36 @@
this.instanceInitializerInfoCollection = instanceInitializerInfoCollection;
}
+ void unsetInstanceInitializerInfoCollection() {
+ instanceInitializerInfoCollection = InstanceInitializerInfoCollection.empty();
+ }
+
void setInitializerEnablingJavaAssertions() {
setFlag(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
}
+ void unsetInitializerEnablingJavaVmAssertions() {
+ clearFlag(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
+ }
+
void markInitializesClassesOnNormalExit(Set<DexType> initializedClassesOnNormalExit) {
this.initializedClassesOnNormalExit = initializedClassesOnNormalExit;
}
+ void unsetInitializedClassesOnNormalExit() {
+ initializedClassesOnNormalExit = Collections.emptySet();
+ }
+
void markReturnsArgument(int returnedArgumentIndex) {
assert returnedArgumentIndex >= 0;
assert returnedArgument == -1 || returnedArgument == returnedArgumentIndex;
returnedArgument = returnedArgumentIndex;
}
+ void unsetReturnedArgument() {
+ returnedArgument = -1;
+ }
+
public MutableMethodOptimizationInfo fixupReturnedArgumentIndex(
MethodOptimizationInfoFixer fixer) {
returnedArgument = fixer.fixupReturnedArgumentIndex(returnedArgument);
@@ -535,14 +576,26 @@
clearFlag(MAY_HAVE_SIDE_EFFECT_FLAG);
}
+ void unsetMayNotHaveSideEffects() {
+ setFlag(MAY_HAVE_SIDE_EFFECT_FLAG);
+ }
+
void markReturnValueOnlyDependsOnArguments() {
setFlag(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG);
}
+ void unsetReturnValueOnlyDependsOnArguments() {
+ clearFlag(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG);
+ }
+
void markNeverReturnsNormally() {
setFlag(NEVER_RETURNS_NORMALLY_FLAG);
}
+ void unsetNeverReturnsNormally() {
+ clearFlag(NEVER_RETURNS_NORMALLY_FLAG);
+ }
+
void markReturnsAbstractValue(AbstractValue value) {
assert !abstractReturnValue.isSingleValue() || abstractReturnValue.equals(value)
: "return single value changed from " + abstractReturnValue + " to " + value;
@@ -571,6 +624,10 @@
}
}
+ void unsetDynamicUpperBoundReturnType() {
+ returnsObjectWithUpperBoundType = null;
+ }
+
void markReturnsObjectWithLowerBoundType(ClassTypeElement type) {
assert type != null;
// Currently, we only have a lower bound type when we have _exact_ runtime type information.
@@ -581,6 +638,10 @@
returnsObjectWithLowerBoundType = type;
}
+ void unsetDynamicLowerBoundReturnType() {
+ returnsObjectWithLowerBoundType = null;
+ }
+
// TODO(b/140214568): Should be package-private.
public void markForceInline() {
// For concurrent scenarios we should allow the flag to be already set
@@ -599,10 +660,18 @@
setFlag(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG, mark);
}
+ void unsetCheckNullReceiverBeforeAnySideEffect() {
+ clearFlag(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG);
+ }
+
void markTriggerClassInitBeforeAnySideEffect(boolean mark) {
setFlag(TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG, mark);
}
+ void unsetTriggerClassInitBeforeAnySideEffect() {
+ clearFlag(TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG);
+ }
+
// TODO(b/140214568): Should be package-private.
public void markAsPropagated() {
setFlag(RETURN_VALUE_HAS_BEEN_PROPAGATED_FLAG);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
index b38f436..6e7bf18 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
@@ -36,10 +36,18 @@
}
}
+ public static OptimizationFeedbackIgnore getIgnoreFeedback() {
+ return OptimizationFeedbackIgnore.getInstance();
+ }
+
public static OptimizationFeedbackSimple getSimple() {
return OptimizationFeedbackSimple.getInstance();
}
+ public static OptimizationFeedbackSimple getSimpleFeedback() {
+ return getSimple();
+ }
+
public void fixupOptimizationInfos(
AppView<?> appView, ExecutorService executorService, OptimizationInfoFixer fixer)
throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 1b4acdd..23bedd0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -199,11 +199,6 @@
}
@Override
- public synchronized void unsetAbstractReturnValue(ProgramMethod method) {
- getMethodOptimizationInfoForUpdating(method).unsetAbstractReturnValue();
- }
-
- @Override
public synchronized void methodReturnsObjectWithUpperBoundType(
DexEncodedMethod method, AppView<?> appView, TypeElement type) {
getMethodOptimizationInfoForUpdating(method).markReturnsObjectWithUpperBoundType(appView, type);
@@ -216,7 +211,7 @@
}
@Override
- public synchronized void methodNeverReturnsNormally(DexEncodedMethod method) {
+ public synchronized void methodNeverReturnsNormally(ProgramMethod method) {
getMethodOptimizationInfoForUpdating(method).markNeverReturnsNormally();
}
@@ -272,11 +267,6 @@
}
@Override
- public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {
- getMethodOptimizationInfoForUpdating(method).unsetEnumUnboxerMethodClassification();
- }
-
- @Override
public synchronized void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
@@ -314,4 +304,121 @@
public synchronized void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) {
getMethodOptimizationInfoForUpdating(method).setUnusedArguments(unusedArguments);
}
+
+ // Unset methods.
+
+ @Override
+ public synchronized void unsetAbstractReturnValue(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetAbstractReturnValue();
+ }
+
+ @Override
+ public synchronized void unsetBridgeInfo(DexEncodedMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetBridgeInfo();
+ }
+
+ @Override
+ public synchronized void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetCheckNullReceiverBeforeAnySideEffect();
+ }
+
+ @Override
+ public synchronized void unsetClassInitializerMayBePostponed(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetClassInitializerMayBePostponed();
+ }
+
+ @Override
+ public synchronized void unsetClassInlinerMethodConstraint(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetClassInlinerMethodConstraint();
+ }
+
+ @Override
+ public synchronized void unsetDynamicLowerBoundReturnType(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetDynamicLowerBoundReturnType();
+ }
+
+ @Override
+ public synchronized void unsetDynamicUpperBoundReturnType(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetDynamicUpperBoundReturnType();
+ }
+
+ @Override
+ public synchronized void unsetEnumUnboxerMethodClassification(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetEnumUnboxerMethodClassification();
+ }
+
+ @Override
+ public synchronized void unsetForceInline(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetForceInline();
+ }
+
+ @Override
+ public synchronized void unsetInitializedClassesOnNormalExit(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetInitializedClassesOnNormalExit();
+ }
+
+ @Override
+ public synchronized void unsetInitializerEnablingJavaVmAssertions(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetInitializerEnablingJavaVmAssertions();
+ }
+
+ @Override
+ public synchronized void unsetInlinedIntoSingleCallSite(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetInlinedIntoSingleCallSite();
+ }
+
+ @Override
+ public synchronized void unsetInstanceInitializerInfoCollection(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetInstanceInitializerInfoCollection();
+ }
+
+ @Override
+ public synchronized void unsetMayNotHaveSideEffects(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetMayNotHaveSideEffects();
+ }
+
+ @Override
+ public synchronized void unsetNeverReturnsNormally(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetNeverReturnsNormally();
+ }
+
+ @Override
+ public synchronized void unsetNonNullParamOnNormalExits(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetNonNullParamOnNormalExits();
+ }
+
+ @Override
+ public synchronized void unsetNonNullParamOrThrow(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetNonNullParamOrThrow();
+ }
+
+ @Override
+ public synchronized void unsetReachabilitySensitive(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetReachabilitySensitive();
+ }
+
+ @Override
+ public synchronized void unsetReturnedArgument(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetReturnedArgument();
+ }
+
+ @Override
+ public synchronized void unsetReturnValueOnlyDependsOnArguments(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetReturnValueOnlyDependsOnArguments();
+ }
+
+ @Override
+ public synchronized void unsetSimpleInliningConstraint(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetSimpleInliningConstraint();
+ }
+
+ @Override
+ public synchronized void unsetTriggerClassInitBeforeAnySideEffect(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetTriggerClassInitBeforeAnySideEffect();
+ }
+
+ @Override
+ public synchronized void unsetUnusedArguments(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetUnusedArguments();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 8b13771..3bcd3fc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -79,9 +79,6 @@
DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {}
@Override
- public void unsetAbstractReturnValue(ProgramMethod method) {}
-
- @Override
public void methodReturnsObjectWithUpperBoundType(
DexEncodedMethod method, AppView<?> appView, TypeElement type) {}
@@ -96,7 +93,7 @@
public void methodReturnValueOnlyDependsOnArguments(DexEncodedMethod method) {}
@Override
- public void methodNeverReturnsNormally(DexEncodedMethod method) {}
+ public void methodNeverReturnsNormally(ProgramMethod method) {}
@Override
public void markAsPropagated(DexEncodedMethod method) {}
@@ -122,9 +119,6 @@
ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {}
@Override
- public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {}
-
- @Override
public void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {}
@@ -147,4 +141,75 @@
@Override
public void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) {}
+
+ // Unset methods.
+
+ @Override
+ public void unsetAbstractReturnValue(ProgramMethod method) {}
+
+ @Override
+ public void unsetBridgeInfo(DexEncodedMethod method) {}
+
+ @Override
+ public void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method) {}
+
+ @Override
+ public void unsetClassInitializerMayBePostponed(ProgramMethod method) {}
+
+ @Override
+ public void unsetClassInlinerMethodConstraint(ProgramMethod method) {}
+
+ @Override
+ public void unsetDynamicLowerBoundReturnType(ProgramMethod method) {}
+
+ @Override
+ public void unsetDynamicUpperBoundReturnType(ProgramMethod method) {}
+
+ @Override
+ public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {}
+
+ @Override
+ public void unsetForceInline(ProgramMethod method) {}
+
+ @Override
+ public void unsetInitializedClassesOnNormalExit(ProgramMethod method) {}
+
+ @Override
+ public void unsetInitializerEnablingJavaVmAssertions(ProgramMethod method) {}
+
+ @Override
+ public void unsetInlinedIntoSingleCallSite(ProgramMethod method) {}
+
+ @Override
+ public void unsetInstanceInitializerInfoCollection(ProgramMethod method) {}
+
+ @Override
+ public void unsetMayNotHaveSideEffects(ProgramMethod method) {}
+
+ @Override
+ public void unsetNeverReturnsNormally(ProgramMethod method) {}
+
+ @Override
+ public void unsetNonNullParamOnNormalExits(ProgramMethod method) {}
+
+ @Override
+ public void unsetNonNullParamOrThrow(ProgramMethod method) {}
+
+ @Override
+ public void unsetReachabilitySensitive(ProgramMethod method) {}
+
+ @Override
+ public void unsetReturnedArgument(ProgramMethod method) {}
+
+ @Override
+ public void unsetReturnValueOnlyDependsOnArguments(ProgramMethod method) {}
+
+ @Override
+ public void unsetSimpleInliningConstraint(ProgramMethod method) {}
+
+ @Override
+ public void unsetTriggerClassInitBeforeAnySideEffect(ProgramMethod method) {}
+
+ @Override
+ public void unsetUnusedArguments(ProgramMethod method) {}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 86ea927..77615e2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.BitSet;
import java.util.Set;
+import java.util.function.Consumer;
public class OptimizationFeedbackSimple extends OptimizationFeedback {
@@ -80,7 +81,7 @@
}
@Override
- public synchronized void markInlinedIntoSingleCallSite(DexEncodedMethod method) {
+ public void markInlinedIntoSingleCallSite(DexEncodedMethod method) {
method.getMutableOptimizationInfo().markInlinedIntoSingleCallSite();
}
@@ -107,13 +108,6 @@
}
@Override
- public void unsetAbstractReturnValue(ProgramMethod method) {
- if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
- method.getDefinition().getMutableOptimizationInfo().unsetAbstractReturnValue();
- }
- }
-
- @Override
public void methodReturnsObjectWithUpperBoundType(
DexEncodedMethod method, AppView<?> appView, TypeElement type) {
method.getMutableOptimizationInfo().markReturnsObjectWithUpperBoundType(appView, type);
@@ -136,8 +130,8 @@
}
@Override
- public void methodNeverReturnsNormally(DexEncodedMethod method) {
- // Ignored.
+ public void methodNeverReturnsNormally(ProgramMethod method) {
+ method.getDefinition().getMutableOptimizationInfo().markNeverReturnsNormally();
}
@Override
@@ -152,7 +146,7 @@
@Override
public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
- // Ignored.
+ method.getMutableOptimizationInfo().markCheckNullReceiverBeforeAnySideEffect(mark);
}
@Override
@@ -165,27 +159,12 @@
method.getMutableOptimizationInfo().setBridgeInfo(bridgeInfo);
}
- public void unsetBridgeInfo(DexEncodedMethod method) {
- if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
- method.getOptimizationInfo().asMutableMethodOptimizationInfo().unsetBridgeInfo();
- }
- }
-
@Override
public void setClassInlinerMethodConstraint(
ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint) {
// Ignored.
}
- public void unsetClassInlinerMethodConstraint(ProgramMethod method) {
- if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
- method
- .getOptimizationInfo()
- .asMutableMethodOptimizationInfo()
- .unsetClassInlinerMethodConstraint();
- }
- }
-
@Override
public void setEnumUnboxerMethodClassification(
ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
@@ -196,16 +175,6 @@
}
@Override
- public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {
- if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
- method
- .getOptimizationInfo()
- .asMutableMethodOptimizationInfo()
- .unsetEnumUnboxerMethodClassification();
- }
- }
-
- @Override
public void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
@@ -245,12 +214,154 @@
method.getDefinition().getMutableOptimizationInfo().setUnusedArguments(unusedArguments);
}
+ // Unset methods.
+
+ @Override
+ public void unsetAbstractReturnValue(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetAbstractReturnValue);
+ }
+
+ @Override
+ public void unsetBridgeInfo(DexEncodedMethod method) {
+ withMutableMethodOptimizationInfo(method, MutableMethodOptimizationInfo::unsetBridgeInfo);
+ }
+
+ @Override
+ public void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetCheckNullReceiverBeforeAnySideEffect);
+ }
+
+ @Override
+ public void unsetClassInitializerMayBePostponed(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetClassInitializerMayBePostponed);
+ }
+
+ @Override
+ public void unsetClassInlinerMethodConstraint(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetClassInlinerMethodConstraint);
+ }
+
+ @Override
+ public void unsetDynamicLowerBoundReturnType(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetDynamicLowerBoundReturnType);
+ }
+
+ @Override
+ public void unsetDynamicUpperBoundReturnType(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetDynamicUpperBoundReturnType);
+ }
+
+ @Override
+ public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetEnumUnboxerMethodClassification);
+ }
+
+ @Override
+ public void unsetForceInline(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(method, MutableMethodOptimizationInfo::unsetForceInline);
+ }
+
+ @Override
+ public void unsetInitializedClassesOnNormalExit(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetInitializedClassesOnNormalExit);
+ }
+
+ @Override
+ public void unsetInitializerEnablingJavaVmAssertions(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetInitializerEnablingJavaVmAssertions);
+ }
+
+ @Override
public void unsetInlinedIntoSingleCallSite(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetInlinedIntoSingleCallSite);
+ }
+
+ @Override
+ public void unsetInstanceInitializerInfoCollection(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetInstanceInitializerInfoCollection);
+ }
+
+ @Override
+ public void unsetMayNotHaveSideEffects(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetMayNotHaveSideEffects);
+ }
+
+ @Override
+ public void unsetNeverReturnsNormally(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetNeverReturnsNormally);
+ }
+
+ @Override
+ public void unsetNonNullParamOnNormalExits(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetNonNullParamOnNormalExits);
+ }
+
+ @Override
+ public void unsetNonNullParamOrThrow(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetNonNullParamOrThrow);
+ }
+
+ @Override
+ public void unsetReachabilitySensitive(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetReachabilitySensitive);
+ }
+
+ @Override
+ public void unsetReturnedArgument(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(method, MutableMethodOptimizationInfo::unsetReturnedArgument);
+ }
+
+ @Override
+ public void unsetReturnValueOnlyDependsOnArguments(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetReturnValueOnlyDependsOnArguments);
+ }
+
+ @Override
+ public void unsetSimpleInliningConstraint(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetSimpleInliningConstraint);
+ }
+
+ @Override
+ public void unsetTriggerClassInitBeforeAnySideEffect(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(
+ method, MutableMethodOptimizationInfo::unsetTriggerClassInitBeforeAnySideEffect);
+ }
+
+ @Override
+ public void unsetUnusedArguments(ProgramMethod method) {
+ withMutableMethodOptimizationInfo(method, MutableMethodOptimizationInfo::unsetUnusedArguments);
+ }
+
+ private void withMutableMethodOptimizationInfo(
+ ProgramMethod method, Consumer<MutableMethodOptimizationInfo> consumer) {
if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
- method
- .getOptimizationInfo()
- .asMutableMethodOptimizationInfo()
- .unsetInlinedIntoSingleCallSite();
+ consumer.accept(method.getOptimizationInfo().asMutableMethodOptimizationInfo());
+ }
+ }
+
+ @Deprecated
+ private void withMutableMethodOptimizationInfo(
+ DexEncodedMethod method, Consumer<MutableMethodOptimizationInfo> consumer) {
+ if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
+ consumer.accept(method.getOptimizationInfo().asMutableMethodOptimizationInfo());
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java
index 16e4dab..78dc248 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java
@@ -97,12 +97,15 @@
DexProgramClass.asProgramClassOrNull(
appView.definitionFor(rewrittenReference.getHolderType()));
ProgramMethod method = rewrittenReference.lookupOnProgramClass(holder);
- if (method != null) {
- for (Outline outline : outlinesForMethod) {
- methodsPerOutline.computeIfAbsent(outline, ignoreKey(ArrayList::new)).add(method);
- }
- } else {
+ if (method == null) {
assert false;
+ return;
+ }
+ if (method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()) {
+ return;
+ }
+ for (Outline outline : outlinesForMethod) {
+ methodsPerOutline.computeIfAbsent(outline, ignoreKey(ArrayList::new)).add(method);
}
});
return methodsPerOutline;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 2fbaa44..c73132f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -234,7 +234,13 @@
LongLivedProgramMethodSetBuilder<?> referencedFromBuilder =
classStaticizer.referencedFrom.remove(info);
assert referencedFromBuilder != null;
- referencedFrom = referencedFromBuilder.build(appView);
+ referencedFrom =
+ referencedFromBuilder
+ .rewrittenWithLens(appView)
+ .removeIf(
+ appView,
+ method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite())
+ .build(appView);
materializedReferencedFromCollections.put(info, referencedFrom);
} else {
referencedFrom = ProgramMethodSet.empty();
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index c3677ba..7b8da09 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.SourceFileEnvironment;
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.dex.Marker;
@@ -40,6 +41,7 @@
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.ProguardMapSupplier;
+import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.AsmUtils;
@@ -131,16 +133,24 @@
}
private void writeApplication(ClassFileConsumer consumer) {
- if (proguardMapSupplier != null && options.proguardMapConsumer != null) {
- marker.setPgMapId(proguardMapSupplier.writeProguardMap().getId());
+ ProguardMapId proguardMapId =
+ (proguardMapSupplier != null && options.proguardMapConsumer != null)
+ ? proguardMapSupplier.writeProguardMap()
+ : null;
+ if (proguardMapId != null) {
+ marker.setPgMapId(proguardMapId.getId());
}
Optional<String> markerString =
includeMarker(marker) ? Optional.of(marker.toString()) : Optional.empty();
+ SourceFileEnvironment sourceFileEnvironment = null;
+ if (options.sourceFileProvider != null) {
+ sourceFileEnvironment = ApplicationWriter.createSourceFileEnvironment(proguardMapId);
+ }
LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView);
for (DexProgramClass clazz : application.classes()) {
assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
try {
- writeClass(clazz, consumer, rewriter, markerString);
+ writeClass(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
} catch (ClassTooLargeException e) {
throw appView
.options()
@@ -172,14 +182,21 @@
DexProgramClass clazz,
ClassFileConsumer consumer,
LensCodeRewriterUtils rewriter,
- Optional<String> markerString) {
+ Optional<String> markerString,
+ SourceFileEnvironment sourceFileEnvironment) {
ClassWriter writer = new ClassWriter(0);
if (markerString.isPresent()) {
int markerStringPoolIndex = writer.newConst(markerString.get());
assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
}
+ String sourceFile;
+ if (options.sourceFileProvider == null) {
+ sourceFile = clazz.sourceFile != null ? clazz.sourceFile.toString() : null;
+ } else {
+ sourceFile = options.sourceFileProvider.get(sourceFileEnvironment);
+ }
String sourceDebug = getSourceDebugExtension(clazz.annotations());
- writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, sourceDebug);
+ writer.visitSource(sourceFile, sourceDebug);
CfVersion version = getClassFileVersion(clazz);
if (version.isGreaterThanOrEqualTo(CfVersion.V1_8)) {
// JDK8 and after ignore ACC_SUPER so unset it.
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index a775a72..a22a766 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -19,10 +19,12 @@
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexItemBasedValueString;
import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.records.RecordCfToCfRewriter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.ThreadUtils;
-import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -35,11 +37,13 @@
private final AppView<AppInfoWithLiveness> appView;
private final ProguardClassFilter adaptClassStrings;
+ private final RecordCfToCfRewriter recordCfToCfRewriter;
private final NamingLens lens;
IdentifierMinifier(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
this.appView = appView;
this.adaptClassStrings = appView.options().getProguardConfiguration().getAdaptClassStrings();
+ this.recordCfToCfRewriter = RecordCfToCfRewriter.create(appView);
this.lens = lens;
}
@@ -117,12 +121,10 @@
for (DexEncodedField field : clazz.staticFields()) {
replaceDexItemBasedConstStringInStaticField(field);
}
- clazz
- .methods(DexEncodedMethod::hasCode)
- .forEach(this::replaceDexItemBasedConstStringInMethod);
+ clazz.forEachProgramMethodMatching(
+ DexEncodedMethod::hasCode, this::replaceDexItemBasedConstStringInMethod);
},
- executorService
- );
+ executorService);
}
private void replaceDexItemBasedConstStringInStaticField(DexEncodedField encodedField) {
@@ -137,8 +139,8 @@
}
}
- private void replaceDexItemBasedConstStringInMethod(DexEncodedMethod encodedMethod) {
- Code code = encodedMethod.getCode();
+ private void replaceDexItemBasedConstStringInMethod(ProgramMethod programMethod) {
+ Code code = programMethod.getDefinition().getCode();
assert code != null;
if (code.isDexCode()) {
Instruction[] instructions = code.asDexCode().instructions;
@@ -157,23 +159,23 @@
} else {
assert code.isCfCode();
List<CfInstruction> instructions = code.asCfCode().getInstructions();
- List<CfInstruction> newInstructions = null;
- for (int i = 0; i < instructions.size(); ++i) {
- CfInstruction instruction = instructions.get(i);
- if (instruction.isDexItemBasedConstString()) {
- CfDexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
- DexString replacement =
- cnst.getNameComputationInfo()
- .computeNameFor(cnst.getItem(), appView, appView.graphLens(), lens);
- if (newInstructions == null) {
- newInstructions = new ArrayList<>(instructions);
- }
- newInstructions.set(i, new CfConstString(replacement));
- }
- }
- if (newInstructions != null) {
- code.asCfCode().setInstructions(newInstructions);
- }
+ List<CfInstruction> newInstructions =
+ ListUtils.mapOrElse(
+ instructions,
+ (int i, CfInstruction instruction) -> {
+ if (instruction.isDexItemBasedConstString()) {
+ CfDexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
+ return new CfConstString(
+ cnst.getNameComputationInfo()
+ .computeNameFor(cnst.getItem(), appView, appView.graphLens(), lens));
+ } else if (recordCfToCfRewriter != null && instruction.isInvokeDynamic()) {
+ return recordCfToCfRewriter.rewriteRecordInvokeDynamic(
+ instruction.asInvokeDynamic(), programMethod, lens);
+ }
+ return instruction;
+ },
+ instructions);
+ code.asCfCode().setInstructions(newInstructions);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index 8b650cd..696bce1 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -3,74 +3,73 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import com.android.tools.r8.SourceFileEnvironment;
+import com.android.tools.r8.SourceFileProvider;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.shaking.ProguardConfiguration;
-/**
- * Visit program {@link DexClass}es and replace their sourceFile with the given string.
- *
- * If -keepattribute SourceFile is not set, we rather remove that attribute.
- */
+/** Computes the source file provider based on the proguard configuration if none is set. */
public class SourceFileRewriter {
private final AppView<?> appView;
- private final DexApplication application;
- public SourceFileRewriter(AppView<?> appView, DexApplication application) {
+ public SourceFileRewriter(AppView<?> appView) {
this.appView = appView;
- this.application = application;
}
public void run() {
- boolean isMinifying = appView.options().isMinifying();
- boolean isCompatR8 = appView.options().forceProguardCompatibility;
- if (!isMinifying && isCompatR8) {
- // Compatibility mode will only apply -renamesourcefileattribute when minifying names.
+ if (appView.options().sourceFileProvider != null) {
return;
}
- ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
- boolean hasKeptNonRenamedSourceFile =
- proguardConfiguration.getRenameSourceFileAttribute() == null
- && proguardConfiguration.getKeepAttributes().sourceFile;
- // If source file is kept without a rewrite, it is only modified in a minifing full-mode.
- if (hasKeptNonRenamedSourceFile && (!isMinifying || isCompatR8)) {
- return;
- }
- assert !isMinifying || appView.appInfo().hasLiveness();
- DexString defaultRenaming = getSourceFileRenaming(proguardConfiguration);
- for (DexClass clazz : application.classes()) {
- clazz.sourceFile = defaultRenaming;
- }
+ appView.options().sourceFileProvider = computeSourceFileProvider();
}
- private DexString getSourceFileRenaming(ProguardConfiguration proguardConfiguration) {
- // If we should not be keeping the source file, null it out.
- if (!proguardConfiguration.getKeepAttributes().sourceFile) {
- // For class files, we always remove the attribute
- if (appView.options().isGeneratingClassFiles()) {
- return null;
- }
- assert appView.options().isGeneratingDex();
- // When generating DEX we only remove the attribute for full-mode to ensure that we get
- // line-numbers printed in stack traces.
- if (!appView.options().forceProguardCompatibility) {
- return null;
+ public SourceFileProvider computeSourceFileProvider() {
+ if (!appView.options().getProguardConfiguration().getKeepAttributes().sourceFile) {
+ return rewriteToDefaultSourceFile();
+ }
+ if (appView.options().forceProguardCompatibility) {
+ return computeCompatProvider();
+ }
+ return computeNonCompatProvider();
+ }
+
+ private SourceFileProvider computeCompatProvider() {
+ // Compatibility mode will only apply -renamesourcefileattribute when minifying names.
+ if (appView.options().isMinifying()) {
+ String renaming = getRenameSourceFileAttribute();
+ if (renaming != null) {
+ return rewriteTo(renaming);
}
}
+ return null;
+ }
- String renamedSourceFileAttribute = proguardConfiguration.getRenameSourceFileAttribute();
- if (renamedSourceFileAttribute != null) {
- return appView.dexItemFactory().createString(renamedSourceFileAttribute);
+ private SourceFileProvider computeNonCompatProvider() {
+ String renaming = getRenameSourceFileAttribute();
+ if (renaming != null) {
+ return rewriteTo(renaming);
}
+ if (appView.options().isMinifying()) {
+ // TODO(b/202367773): This should also apply if optimizing.
+ return rewriteToDefaultSourceFile();
+ }
+ return null;
+ }
- // Otherwise, take the smallest size depending on platform. We cannot use NULL since the jvm
- // and art will write at foo.bar.baz(Unknown Source) without a line-number. Newer version of ART
- // will report the DEX PC.
- return appView
- .dexItemFactory()
- .createString(appView.options().isGeneratingClassFiles() ? "SourceFile" : "");
+ private String getRenameSourceFileAttribute() {
+ return appView.options().getProguardConfiguration().getRenameSourceFileAttribute();
+ }
+
+ private SourceFileProvider rewriteToDefaultSourceFile() {
+ return rewriteTo(appView.dexItemFactory().defaultSourceFileAttributeString);
+ }
+
+ private SourceFileProvider rewriteTo(String renaming) {
+ return new SourceFileProvider() {
+ @Override
+ public String get(SourceFileEnvironment environment) {
+ return renaming;
+ }
+ };
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java
index 67e574c..8a16fb0 100644
--- a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java
+++ b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java
@@ -27,6 +27,10 @@
return asClassNameComputationInfo()
.internalComputeNameFor(rewritten.asDexType(), definitions, namingLens);
}
+ if (isRecordFieldNamesComputationInfo()) {
+ return asRecordFieldNamesComputationInfo()
+ .internalComputeNameFor(rewritten.asDexType(), definitions, graphLens, namingLens);
+ }
}
return namingLens.lookupName(rewritten, definitions.dexItemFactory());
}
@@ -53,4 +57,12 @@
public ClassNameComputationInfo asClassNameComputationInfo() {
return null;
}
+
+ public boolean isRecordFieldNamesComputationInfo() {
+ return false;
+ }
+
+ public RecordFieldNamesComputationInfo asRecordFieldNamesComputationInfo() {
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java
new file mode 100644
index 0000000..17ac83a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2021, 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.naming.dexitembasedstring;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.IntFunction;
+
+/**
+ * Specific subclass for Record field names. Record field names are specific DexString instances
+ * used at the Java class file level for Records which encode the record field names in a single
+ * String separated by semi-colon. The computation is able to minify and prune such names.
+ *
+ * <p>Example: record Person(String name, int age) {} The string is: name;age
+ *
+ * <p>The JVM normally generates the Record field names string such as it includes each field name
+ * in the string. However, bytecode manipulation tools may rewrite the field name and not the
+ * string. For this reason we have two subclasses. - MatchingRecordFieldNamesComputationInfo is used
+ * when the name in the string matches the field names, and the R8 compilation is able to minify and
+ * prune the string based on the fields. - MissMatchingRecordFieldNamesComputationInfo is used when
+ * the name in the string does not match the field names, and the R8 compilation is then only able
+ * to prune the fields while maintaining the non-matching field names.
+ */
+public abstract class RecordFieldNamesComputationInfo extends NameComputationInfo<DexType> {
+
+ final DexField[] fields;
+
+ protected RecordFieldNamesComputationInfo(DexField[] fields) {
+ this.fields = fields;
+ }
+
+ private static class MissMatchingRecordFieldNamesComputationInfo
+ extends RecordFieldNamesComputationInfo {
+
+ private final String[] fieldNames;
+
+ private MissMatchingRecordFieldNamesComputationInfo(String[] fieldNames, DexField[] fields) {
+ super(fields);
+ this.fieldNames = fieldNames;
+ }
+
+ @Override
+ public DexString internalComputeNameFor(
+ DexType type,
+ DexDefinitionSupplier definitions,
+ GraphLens graphLens,
+ NamingLens namingLens) {
+ return internalComputeNameFor(type, definitions, graphLens, i -> fieldNames[i]);
+ }
+ }
+
+ private static class MatchingRecordFieldNamesComputationInfo
+ extends RecordFieldNamesComputationInfo {
+
+ public MatchingRecordFieldNamesComputationInfo(DexField[] fields) {
+ super(fields);
+ }
+
+ @Override
+ public DexString internalComputeNameFor(
+ DexType type,
+ DexDefinitionSupplier definitions,
+ GraphLens graphLens,
+ NamingLens namingLens) {
+ return internalComputeNameFor(
+ type,
+ definitions,
+ graphLens,
+ i ->
+ namingLens
+ .lookupField(
+ graphLens.getRenamedFieldSignature(fields[i]), definitions.dexItemFactory())
+ .name
+ .toString());
+ }
+ }
+
+ static DexString dexStringFromFieldNames(List<String> fieldNames, DexItemFactory factory) {
+ return factory.createString(StringUtils.join(";", fieldNames));
+ }
+
+ public static RecordFieldNamesComputationInfo forFieldNamesAndFields(
+ DexString fieldNames, DexField[] fields) {
+ String fieldNamesString = fieldNames.toString();
+ String[] fieldNamesSplit =
+ fieldNamesString.isEmpty() ? new String[0] : fieldNamesString.split(";");
+ assert fieldNamesSplit.length == fields.length;
+ if (fieldsMatchNames(fieldNamesSplit, fields)) {
+ return new MatchingRecordFieldNamesComputationInfo(fields);
+ }
+ return new MissMatchingRecordFieldNamesComputationInfo(fieldNamesSplit, fields);
+ }
+
+ private static boolean fieldsMatchNames(String[] fieldNames, DexField[] fields) {
+ for (int i = 0; i < fieldNames.length; i++) {
+ if (!(fields[i].name.toString().equals(fieldNames[i]))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public DexString internalComputeNameFor(
+ DexType type,
+ DexDefinitionSupplier definitions,
+ GraphLens graphLens,
+ IntFunction<String> nameSupplier) {
+ assert Arrays.stream(fields).allMatch(f -> f.holder == type);
+ DexClass recordClass = definitions.contextIndependentDefinitionFor(type);
+ assert recordClass != null;
+ List<String> names = new ArrayList<>(fields.length);
+ for (int i = 0; i < fields.length; i++) {
+ DexEncodedField recordField =
+ recordClass.lookupInstanceField(graphLens.getRenamedFieldSignature(fields[i]));
+ if (recordField != null) {
+ names.add(nameSupplier.apply(i));
+ } else {
+ // TODO(b/201277582): Temporarily work-around as long as the field values are not also
+ // shrunk in dex.
+ names.add("<pruned>");
+ }
+ }
+ return dexStringFromFieldNames(names, definitions.dexItemFactory());
+ }
+
+ @Override
+ DexString internalComputeNameFor(
+ DexType reference, DexDefinitionSupplier definitions, NamingLens namingLens) {
+ throw new Unreachable();
+ }
+
+ public abstract DexString internalComputeNameFor(
+ DexType type, DexDefinitionSupplier definitions, GraphLens graphLens, NamingLens namingLens);
+
+ @Override
+ public boolean needsToComputeName() {
+ return true;
+ }
+
+ @Override
+ public boolean needsToRegisterReference() {
+ return false;
+ }
+
+ @Override
+ public boolean isRecordFieldNamesComputationInfo() {
+ return true;
+ }
+
+ @Override
+ public RecordFieldNamesComputationInfo asRecordFieldNamesComputationInfo() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 080e691..922e198 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -213,4 +213,20 @@
reprocessingCriteriaCollection = null;
timing.end();
}
+
+ /**
+ * Called by {@link IRConverter} at the end of a wave if a method is pruned by an optimization.
+ *
+ * <p>We only prune (1) direct single caller methods and (2) isX()/asX() virtual method overrides.
+ * For (2), we always transfer the argument information for the isX()/asX() method to its parent
+ * method using {@link #transferArgumentInformation(ProgramMethod, ProgramMethod)}, which unsets
+ * the argument information for the override.
+ *
+ * <p>Therefore, we assert that we only find a method state for direct methods.
+ */
+ public void pruneMethod(ProgramMethod method) {
+ assert codeScanner != null;
+ MethodState methodState = codeScanner.getMethodStates().removeOrElse(method, null);
+ assert methodState == null || method.getDefinition().belongsToDirectPool();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
index c01508c..efa1985 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -81,7 +81,7 @@
CallSiteOptimizationInfo callSiteOptimizationInfo =
method.getDefinition().getCallSiteOptimizationInfo();
if (callSiteOptimizationInfo.isConcreteCallSiteOptimizationInfo()
- && !appView.appInfo().isNeverReprocessMethod(method.getReference())) {
+ && !appView.appInfo().isNeverReprocessMethod(method)) {
methodsToReprocessBuilder.add(method, currentGraphLens);
appView.testing().callSiteOptimizationInfoInspector.accept(method);
}
@@ -107,11 +107,14 @@
method -> {
if (graphLens.internalGetNextMethodSignature(method.getReference())
!= method.getReference()) {
- methodsToReprocessInClass.add(method);
+ if (!method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()) {
+ methodsToReprocessInClass.add(method);
+ }
} else {
AffectedMethodUseRegistry registry =
new AffectedMethodUseRegistry(appView, method, graphLens);
if (method.registerCodeReferencesWithResult(registry)) {
+ assert !method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite();
methodsToReprocessInClass.add(method);
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
index 6e90810..cd1e030 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
@@ -20,10 +20,18 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
+/**
+ * Computes the set of virtual methods for which we can use a monomorphic method state as well as
+ * the mapping from virtual methods to their representative root methods.
+ *
+ * <p>The analysis can be used to easily mark effectively final classes and methods as final, and
+ * therefore does this as a side effect.
+ */
public class VirtualRootMethodsAnalysis extends DepthFirstTopDownClassHierarchyTraversal {
static class VirtualRootMethod {
@@ -118,6 +126,16 @@
}
@Override
+ public void forEachSubClass(DexProgramClass clazz, Consumer<DexProgramClass> consumer) {
+ List<DexProgramClass> subclasses = immediateSubtypingInfo.getSubclasses(clazz);
+ if (subclasses.isEmpty()) {
+ promoteToFinalIfPossible(clazz);
+ } else {
+ subclasses.forEach(consumer);
+ }
+ }
+
+ @Override
public void visit(DexProgramClass clazz) {
Map<DexMethodSignature, VirtualRootMethod> state = computeVirtualRootMethodsState(clazz);
virtualRootMethodsPerClass.put(clazz, state);
@@ -157,12 +175,7 @@
rootCandidate -> {
VirtualRootMethod virtualRootMethod =
virtualRootMethodsForClass.remove(rootCandidate.getMethodSignature());
- if (!clazz.isInterface()
- && !rootCandidate.getAccessFlags().isAbstract()
- && !virtualRootMethod.hasOverrides()
- && appView.getKeepInfo(rootCandidate).isOptimizationAllowed(appView.options())) {
- rootCandidate.getAccessFlags().promoteToFinal();
- }
+ promoteToFinalIfPossible(rootCandidate, virtualRootMethod);
if (!rootCandidate.isStructurallyEqualTo(virtualRootMethod.getRoot())) {
return;
}
@@ -193,4 +206,21 @@
}
});
}
+
+ private void promoteToFinalIfPossible(DexProgramClass clazz) {
+ if (!clazz.isAbstract()
+ && !clazz.isInterface()
+ && appView.getKeepInfo(clazz).isOptimizationAllowed(appView.options())) {
+ clazz.getAccessFlags().promoteToFinal();
+ }
+ }
+
+ private void promoteToFinalIfPossible(ProgramMethod method, VirtualRootMethod virtualRootMethod) {
+ if (!method.getHolder().isInterface()
+ && !method.getAccessFlags().isAbstract()
+ && !virtualRootMethod.hasOverrides()
+ && appView.getKeepInfo(method).isOptimizationAllowed(appView.options())) {
+ method.getAccessFlags().promoteToFinal();
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 91289b7..45630fa 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -657,8 +657,9 @@
return keepUnusedArguments.contains(method);
}
- public boolean isNeverReprocessMethod(DexMethod method) {
- return neverReprocess.contains(method);
+ public boolean isNeverReprocessMethod(ProgramMethod method) {
+ return neverReprocess.contains(method.getReference())
+ || method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite();
}
public Set<DexMethod> getReprocessMethods() {
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 43d2d16..f837095 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -17,13 +17,13 @@
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IterableUtils;
@@ -283,11 +283,13 @@
return -1;
}
- private DexEncodedMethod[] reachableMethods(Iterable<DexEncodedMethod> methods, DexClass clazz) {
+ private DexEncodedMethod[] reachableMethods(
+ Iterable<DexEncodedMethod> methods, DexProgramClass clazz) {
return reachableMethods(IterableUtils.ensureUnmodifiableList(methods), clazz);
}
- private DexEncodedMethod[] reachableMethods(List<DexEncodedMethod> methods, DexClass clazz) {
+ private DexEncodedMethod[] reachableMethods(
+ List<DexEncodedMethod> methods, DexProgramClass clazz) {
AppInfoWithLiveness appInfo = appView.appInfo();
InternalOptions options = appView.options();
int firstUnreachable =
@@ -320,27 +322,11 @@
if (Log.ENABLED) {
Log.debug(getClass(), "Making method %s abstract.", method.getReference());
}
- // Final classes cannot be abstract, so we have to keep the method in that case.
- // Also some other kinds of methods cannot be abstract, so keep them around.
- boolean allowAbstract =
- (options.canUseAbstractMethodOnNonAbstractClass() || clazz.isAbstract())
- && !method.isFinal()
- && !method.accessFlags.isNative()
- && !method.accessFlags.isStrict()
- && !method.isSynchronized()
- && !method.accessFlags.isPrivate()
- && !method.isStatic()
- && !appInfo.isFailedResolutionTarget(method.getReference());
// Private methods and static methods can only be targeted yet non-live as the result of
// an invalid invoke. They will not actually be called at runtime but we have to keep them
// as non-abstract (see above) to produce the same failure mode.
- if (!allowAbstract) {
- // If the method was not marked as live and we cannot make it abstract, set the api level
- // to be min or unknown.
- method.setApiLevelForCode(AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView));
- }
- reachableMethods.add(
- allowAbstract ? method.toAbstractMethod() : method.toEmptyThrowingMethod(options));
+ new ProgramMethod(clazz, method).convertToAbstractOrThrowNullMethod(appView);
+ reachableMethods.add(method);
} else {
if (Log.ENABLED) {
Log.debug(getClass(), "Removing method %s.", method.getReference());
diff --git a/src/main/java/com/android/tools/r8/utils/BitSetUtils.java b/src/main/java/com/android/tools/r8/utils/BitSetUtils.java
index 483eb67..085a368 100644
--- a/src/main/java/com/android/tools/r8/utils/BitSetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/BitSetUtils.java
@@ -8,6 +8,14 @@
public class BitSetUtils {
+ public static BitSet createFilled(boolean value, int length) {
+ BitSet bitSet = new BitSet(length);
+ for (int i = 0; i < length; i++) {
+ bitSet.set(i, value);
+ }
+ return bitSet;
+ }
+
@SuppressWarnings("unchecked")
public static BitSet or(BitSet bitSet, BitSet other) {
BitSet newBitSet = (BitSet) bitSet.clone();
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 ff3878e..24e9259 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1590,6 +1590,10 @@
public boolean testEnableTestAssertions = false;
public boolean keepMetadataInR8IfNotRewritten = true;
+ // If set, pruned record fields are not used in hashCode/equals/toString and toString prints
+ // minified field names instead of original field names.
+ public boolean enableRecordModeling = false;
+
public boolean allowConflictingSyntheticTypes = false;
// Flag to allow processing of resources in D8. A data resource consumer still needs to be
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
index 5fe5cf1..00749bb 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
@@ -7,11 +7,13 @@
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
-import com.google.common.collect.Sets;
+import com.android.tools.r8.utils.SetUtils;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
+import java.util.function.IntFunction;
+import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
@@ -78,6 +80,10 @@
return remove(method.getReference());
}
+ public boolean removeIf(Predicate<? super T> predicate) {
+ return backing.values().removeIf(predicate);
+ }
+
public int size() {
return backing.size();
}
@@ -88,7 +94,11 @@
public Set<DexEncodedMethod> toDefinitionSet() {
assert backing instanceof IdentityHashMap;
- Set<DexEncodedMethod> definitions = Sets.newIdentityHashSet();
+ return toDefinitionSet(SetUtils::newIdentityHashSet);
+ }
+
+ public Set<DexEncodedMethod> toDefinitionSet(IntFunction<Set<DexEncodedMethod>> factory) {
+ Set<DexEncodedMethod> definitions = factory.apply(size());
forEach(method -> definitions.add(method.getDefinition()));
return definitions;
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
index 911290b..ce4399b 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
@@ -68,7 +68,7 @@
public void addAll(Iterable<ProgramMethod> methodsToAdd, GraphLens currentGraphLens) {
assert verifyIsRewrittenWithLens(currentGraphLens);
- methodsToAdd.forEach(method -> methods.add(method.getReference()));
+ methodsToAdd.forEach(method -> add(method, currentGraphLens));
}
public void clear() {
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
index 7b653ed..df6be44 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
@@ -29,6 +29,13 @@
private static final String EXPECTED_RESULT_D8 =
String.format(EXPECTED_RESULT, "Person", "Person");
private static final String EXPECTED_RESULT_R8 = String.format(EXPECTED_RESULT, "a", "a");
+ // TODO(b/201277582): These results are temporary while we transition into pruned minified record
+ // fields.
+ private static final String EXPECTED_RESULT_R8_ADVANCED_DEX =
+ StringUtils.lines(
+ "a[a=Jane Doe, <pruned>=42, <pruned>=-1]", "a[a=Bob, <pruned>=42, <pruned>=-1]");
+ private static final String EXPECTED_RESULT_R8_ADVANCED_CF =
+ StringUtils.lines("a[a=Jane Doe, b=42, c=-1]", "a[a=Bob, b=42, c=-1]");
private final TestParameters parameters;
@@ -83,6 +90,43 @@
}
@Test
+ public void testR8AdvancedShrinking() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(opt -> opt.testing.enableRecordModeling = true)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .inspect(this::assertSingleField)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT_R8_ADVANCED_DEX);
+ }
+
+ @Test
+ public void testR8CfThenDexAdvancedShrinking() throws Exception {
+ Path desugared =
+ testForR8(Backend.CF)
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .addOptionsModification(opt -> opt.testing.enableRecordModeling = true)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .writeToZip();
+ testForR8(parameters.getBackend())
+ .addProgramFiles(desugared)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .inspect(this::assertSingleField)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT_R8_ADVANCED_CF);
+ }
+
+ @Test
public void testR8CfThenDex() throws Exception {
Path desugared =
testForR8(Backend.CF)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/LibraryOverrideInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/LibraryOverrideInliningTest.java
index 0b58101..3882a7b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/LibraryOverrideInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/LibraryOverrideInliningTest.java
@@ -6,16 +6,14 @@
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -24,17 +22,14 @@
@RunWith(Parameterized.class)
public class LibraryOverrideInliningTest extends TestBase {
- private final boolean disableInliningOfLibraryMethodOverrides;
private final TestParameters parameters;
- @Parameters(name = "{1}, disableInliningOfLibraryMethodOverrides: {0}")
- public static List<Object[]> data() {
- return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public LibraryOverrideInliningTest(
- boolean disableInliningOfLibraryMethodOverrides, TestParameters parameters) {
- this.disableInliningOfLibraryMethodOverrides = disableInliningOfLibraryMethodOverrides;
+ public LibraryOverrideInliningTest(TestParameters parameters) {
this.parameters = parameters;
}
@@ -43,12 +38,8 @@
testForR8(parameters.getBackend())
.addInnerClasses(LibraryOverrideInliningTest.class)
.addKeepMainRule(TestClass.class)
- .addOptionsModification(
- options ->
- options.disableInliningOfLibraryMethodOverrides =
- disableInliningOfLibraryMethodOverrides)
.enableNeverClassInliningAnnotations()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(
inspector -> {
@@ -64,12 +55,7 @@
MethodSubject mainMethodSubject = testClassSubject.mainMethod();
assertThat(mainMethodSubject, isPresent());
-
- if (disableInliningOfLibraryMethodOverrides) {
- assertThat(mainMethodSubject, invokesMethod(toStringMethodSubject));
- } else {
- assertThat(mainMethodSubject, not(invokesMethod(toStringMethodSubject)));
- }
+ assertThat(mainMethodSubject, invokesMethod(toStringMethodSubject));
})
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("Hello world!");
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 5bae6d3..105d097 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -6,8 +6,10 @@
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -120,7 +122,7 @@
assertThat(
inspector.clazz(
"class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda2"),
- isPresent());
+ isAbsent());
} else {
assertThat(
inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"),
@@ -164,7 +166,7 @@
!hasKotlinCGeneratedLambdaClasses
? "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda2"
: "class_inliner_lambda_j_style.MainKt$testStateful$1"),
- isPresent());
+ onlyIf(hasKotlinCGeneratedLambdaClasses, isPresent()));
});
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
index b7b1f1b..89e517a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
@@ -60,8 +60,9 @@
@Test
public void test_dex() {
- // TODO(b/179860027): Make it run on all tests.
- assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
+ assumeTrue(
+ "kotlinc > 1.3.72 will no longer compile kotlin files with duplicate annotations",
+ kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
assumeTrue("test DEX", parameters.isDexRuntime());
try {
testForR8(parameters.getBackend())
@@ -80,8 +81,9 @@
@Test
public void test_cf() throws Exception {
- // TODO(b/179860027): Make it run on all tests.
- assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
+ assumeTrue(
+ "kotlinc > 1.3.72 will no longer compile kotlin files with duplicate annotations",
+ kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
assumeTrue("test CF", parameters.isCfRuntime());
testForR8(parameters.getBackend())
.addProgramFiles(compiledJars.getForConfiguration(kotlinc, targetVersion))
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index 0f534e0..282d8d7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -10,7 +10,6 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.KotlinCompilerTool.KotlinTargetVersion;
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestParameters;
@@ -91,6 +90,11 @@
runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+ return;
+ }
+
ClassSubject outerClass =
checkClassIsKept(inspector, testedClass.getOuterClassName());
String propertyName = "primitiveProp";
@@ -124,6 +128,11 @@
runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+ return;
+ }
+
ClassSubject outerClass =
checkClassIsKept(inspector, testedClass.getOuterClassName());
String propertyName = "privateProp";
@@ -159,6 +168,11 @@
runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+ return;
+ }
+
ClassSubject outerClass =
checkClassIsKept(inspector, testedClass.getOuterClassName());
String propertyName = "internalProp";
@@ -193,6 +207,11 @@
runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+ return;
+ }
+
ClassSubject outerClass =
checkClassIsKept(inspector, testedClass.getOuterClassName());
String propertyName = "publicProp";
@@ -227,6 +246,11 @@
runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+ return;
+ }
+
ClassSubject outerClass =
checkClassIsKept(inspector, testedClass.getOuterClassName());
String propertyName = "privateLateInitProp";
@@ -260,6 +284,11 @@
runTest(PROPERTIES_PACKAGE_NAME, mainClass)
.inspect(
inspector -> {
+ if (true) {
+ checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+ return;
+ }
+
ClassSubject outerClass =
checkClassIsKept(inspector, testedClass.getOuterClassName());
String propertyName = "internalLateInitProp";
@@ -293,6 +322,11 @@
runTest(PROPERTIES_PACKAGE_NAME, mainClass)
.inspect(
inspector -> {
+ if (true) {
+ checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+ return;
+ }
+
ClassSubject outerClass =
checkClassIsKept(inspector, testedClass.getOuterClassName());
String propertyName = "publicLateInitProp";
@@ -375,9 +409,7 @@
runTest("accessors", mainClass)
.inspect(
inspector -> {
- if (allowAccessModification
- && (testParameters.isCfRuntime()
- || !kotlinParameters.is(KOTLINC_1_5_0, KotlinTargetVersion.JAVA_8))) {
+ if (allowAccessModification) {
checkClassIsRemoved(inspector, testedClass.getClassName());
return;
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index c605951..5a60483 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -87,7 +87,7 @@
.addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
.addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
- private Consumer<InternalOptions> disableAggressiveClassOptimizations =
+ private final Consumer<InternalOptions> disableAggressiveClassOptimizations =
o -> {
o.enableClassInlining = false;
o.enableVerticalClassMerging = false;
@@ -116,13 +116,9 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
- inspector -> {
- checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
- });
+ inspector -> checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName()));
}
@Test
@@ -132,21 +128,20 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
+ return;
+ }
+
ClassSubject classSubject =
checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
String propertyName = "privateProp";
FieldSubject fieldSubject =
checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
- if (!allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- }
+ assertTrue(fieldSubject.getField().isPrivate());
// Private property has no getter or setter.
checkMethodIsAbsent(
@@ -163,11 +158,14 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
+ return;
+ }
+
ClassSubject classSubject =
checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
String propertyName = "protectedProp";
@@ -176,13 +174,8 @@
// Protected property has private field.
MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- checkMethodIsRemoved(classSubject, getter);
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- checkMethodIsKept(classSubject, getter);
- }
+ assertTrue(fieldSubject.getField().isPrivate());
+ checkMethodIsKept(classSubject, getter);
});
}
@@ -193,11 +186,14 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
+ return;
+ }
+
ClassSubject classSubject =
checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
String propertyName = "internalProp";
@@ -206,13 +202,8 @@
// Internal property has private field
MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- checkMethodIsRemoved(classSubject, getter);
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- checkMethodIsKept(classSubject, getter);
- }
+ assertTrue(fieldSubject.getField().isPrivate());
+ checkMethodIsKept(classSubject, getter);
});
}
@@ -223,11 +214,14 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
+ return;
+ }
+
ClassSubject classSubject =
checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
String propertyName = "publicProp";
@@ -236,13 +230,8 @@
// Public property has private field
MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- checkMethodIsRemoved(classSubject, getter);
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- checkMethodIsKept(classSubject, getter);
- }
+ assertTrue(fieldSubject.getField().isPrivate());
+ checkMethodIsKept(classSubject, getter);
});
}
@@ -256,6 +245,11 @@
testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
+ return;
+ }
+
ClassSubject classSubject =
checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
String propertyName = "primitiveProp";
@@ -263,15 +257,9 @@
MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
MethodSignature setter = MUTABLE_PROPERTY_CLASS.getSetterForProperty(propertyName);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- checkMethodIsRemoved(classSubject, getter);
- checkMethodIsRemoved(classSubject, setter);
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- checkMethodIsKept(classSubject, getter);
- checkMethodIsRemoved(classSubject, setter);
- }
+ assertTrue(fieldSubject.getField().isPrivate());
+ checkMethodIsKept(classSubject, getter);
+ checkMethodIsRemoved(classSubject, setter);
});
}
@@ -284,9 +272,7 @@
mainClass,
testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
- inspector -> {
- checkClassIsRemoved(inspector, LATE_INIT_PROPERTY_CLASS.getClassName());
- });
+ inspector -> checkClassIsRemoved(inspector, LATE_INIT_PROPERTY_CLASS.getClassName()));
}
@Test
@@ -296,19 +282,20 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, LATE_INIT_PROPERTY_CLASS.getClassName());
+ return;
+ }
+
ClassSubject classSubject =
checkClassIsKept(inspector, LATE_INIT_PROPERTY_CLASS.getClassName());
String propertyName = "privateLateInitProp";
FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
assertTrue("Field is absent", fieldSubject.isPresent());
- if (!allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- }
+ assertTrue(fieldSubject.getField().isPrivate());
// Private late init property have no getter or setter.
checkMethodIsAbsent(
@@ -325,24 +312,9 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
- inspector -> {
- ClassSubject classSubject =
- checkClassIsKept(inspector, LATE_INIT_PROPERTY_CLASS.getClassName());
- String propertyName = "protectedLateInitProp";
- FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
- assertTrue("Field is absent", fieldSubject.isPresent());
- if (!allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isProtected());
- }
-
- // Protected late init property have protected getter
- checkMethodIsRemoved(
- classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
- });
+ inspector -> checkClassIsRemoved(inspector, LATE_INIT_PROPERTY_CLASS.getClassName()));
}
@Test
@@ -352,22 +324,9 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
- inspector -> {
- ClassSubject classSubject =
- checkClassIsKept(inspector, LATE_INIT_PROPERTY_CLASS.getClassName());
- String propertyName = "internalLateInitProp";
- FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
- assertTrue("Field is absent", fieldSubject.isPresent());
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
-
- // Internal late init property have protected getter
- checkMethodIsRemoved(
- classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
- });
+ inspector -> checkClassIsRemoved(inspector, LATE_INIT_PROPERTY_CLASS.getClassName()));
}
@Test
@@ -377,22 +336,9 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
- inspector -> {
- ClassSubject classSubject =
- checkClassIsKept(inspector, LATE_INIT_PROPERTY_CLASS.getClassName());
- String propertyName = "publicLateInitProp";
- FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
- assertTrue("Field is absent", fieldSubject.isPresent());
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
-
- // Internal late init property have protected getter
- checkMethodIsRemoved(
- classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
- });
+ inspector -> checkClassIsRemoved(inspector, LATE_INIT_PROPERTY_CLASS.getClassName()));
}
@Test
@@ -404,9 +350,8 @@
mainClass,
testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
- inspector -> {
- checkClassIsRemoved(inspector, USER_DEFINED_PROPERTY_CLASS.getClassName());
- });
+ inspector ->
+ checkClassIsRemoved(inspector, USER_DEFINED_PROPERTY_CLASS.getClassName()));
}
@Test
@@ -419,6 +364,11 @@
testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, USER_DEFINED_PROPERTY_CLASS.getClassName());
+ return;
+ }
+
ClassSubject classSubject =
checkClassIsKept(inspector, USER_DEFINED_PROPERTY_CLASS.getClassName());
String propertyName = "durationInSeconds";
@@ -430,13 +380,8 @@
checkFieldIsKept(classSubject, "int", "durationInMilliSeconds");
MethodSignature getter =
USER_DEFINED_PROPERTY_CLASS.getGetterForProperty(propertyName);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- checkMethodIsRemoved(classSubject, getter);
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- checkMethodIsKept(classSubject, getter);
- }
+ assertTrue(fieldSubject.getField().isPrivate());
+ checkMethodIsKept(classSubject, getter);
});
}
@@ -447,22 +392,22 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, "properties.CompanionProperties");
+ return;
+ }
+
ClassSubject outerClass =
checkClassIsKept(inspector, "properties.CompanionProperties");
- checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
String propertyName = "primitiveProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- }
+ assertTrue(fieldSubject.getField().isStatic());
+ assertTrue(fieldSubject.getField().isPrivate());
});
}
@@ -473,34 +418,29 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, "properties.CompanionProperties");
+ return;
+ }
+
ClassSubject outerClass =
checkClassIsKept(inspector, "properties.CompanionProperties");
- checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
String propertyName = "privateProp";
FieldSubject fieldSubject =
checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
- MemberNaming.MethodSignature getter =
- COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
- MemberNaming.MethodSignature setter =
- COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
+ assertTrue(fieldSubject.getField().isStatic());
// Because the getter/setter are private, they can only be called from another method
// in the class. If this is an instance method, they will be called on 'this' which is
// known to be non-null, thus the getter/setter can be inlined if their code is small
// enough. Because the backing field is private, they will call into an accessor
- // (static) method. If access relaxation is enabled, this accessor can be removed.
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- }
+ // (static) method.
+ assertTrue(fieldSubject.getField().isPrivate());
});
}
@@ -511,23 +451,22 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, "properties.CompanionProperties");
+ return;
+ }
+
ClassSubject outerClass =
checkClassIsKept(inspector, "properties.CompanionProperties");
checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
String propertyName = "internalProp";
FieldSubject fieldSubject =
checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- }
+ assertTrue(fieldSubject.getField().isStatic());
+ assertTrue(fieldSubject.getField().isPrivate());
});
}
@@ -538,23 +477,23 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, "properties.CompanionProperties");
+ return;
+ }
+
ClassSubject outerClass =
checkClassIsKept(inspector, "properties.CompanionProperties");
- checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
String propertyName = "publicProp";
FieldSubject fieldSubject =
checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- }
+ assertTrue(fieldSubject.getField().isStatic());
+ assertTrue(fieldSubject.getField().isPrivate());
});
}
@@ -566,29 +505,29 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ checkClassIsRemoved(inspector, testedClass.getClassName());
+
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+ return;
+ }
+
ClassSubject outerClass =
checkClassIsKept(inspector, testedClass.getOuterClassName());
- checkClassIsRemoved(inspector, testedClass.getClassName());
String propertyName = "privateLateInitProp";
FieldSubject fieldSubject =
checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
+ assertTrue(fieldSubject.getField().isStatic());
// Because the getter/setter are private, they can only be called from another method
// in the class. If this is an instance method, they will be called on 'this' which is
// known to be non-null, thus the getter/setter can be inlined if their code is small
// enough. Because the backing field is private, they will call into an accessor
// (static) method. If access relaxation is enabled, this accessor can be removed.
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().isPublic());
- } else {
- assertTrue(fieldSubject.getField().isPrivate());
- }
+ assertTrue(fieldSubject.getField().isPrivate());
});
}
@@ -600,19 +539,11 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
- ClassSubject outerClass =
- checkClassIsKept(inspector, testedClass.getOuterClassName());
checkClassIsRemoved(inspector, testedClass.getClassName());
- String propertyName = "internalLateInitProp";
- FieldSubject fieldSubject =
- checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ checkClassIsRemoved(inspector, testedClass.getOuterClassName());
});
}
@@ -624,19 +555,11 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
- ClassSubject outerClass =
- checkClassIsKept(inspector, testedClass.getOuterClassName());
checkClassIsRemoved(inspector, testedClass.getClassName());
- String propertyName = "publicLateInitProp";
- FieldSubject fieldSubject =
- checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ checkClassIsRemoved(inspector, testedClass.getClassName());
});
}
@@ -651,30 +574,21 @@
testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getClassName());
+ return;
+ }
+
ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
String propertyName = "primitiveProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, "int", propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
+ assertTrue(fieldSubject.getField().isStatic());
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-
- if (allowAccessModification) {
- // Getter and setter is inlined because the constructor of ObjectProperties is
- // considered trivial, which implies that the member value propagation marks the
- // INSTANCE field as being non-null.
- checkMethodIsRemoved(objectClass, getter);
- checkMethodIsRemoved(objectClass, setter);
- } else {
- checkMethodIsKept(objectClass, getter);
- checkMethodIsRemoved(objectClass, setter);
- }
-
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- }
+ checkMethodIsKept(objectClass, getter);
+ checkMethodIsRemoved(objectClass, setter);
+ assertTrue(fieldSubject.getField().isPrivate());
});
}
@@ -686,16 +600,19 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getClassName());
+ return;
+ }
+
ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
String propertyName = "privateProp";
FieldSubject fieldSubject =
checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
+ assertTrue(fieldSubject.getField().isStatic());
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
@@ -704,11 +621,7 @@
checkMethodIsAbsent(objectClass, getter);
checkMethodIsAbsent(objectClass, setter);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- }
+ assertTrue(fieldSubject.getField().isPrivate());
});
}
@@ -723,31 +636,24 @@
testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getClassName());
+ return;
+ }
+
ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
String propertyName = "internalProp";
FieldSubject fieldSubject =
checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
+ assertTrue(fieldSubject.getField().isStatic());
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
- if (allowAccessModification) {
- // Getter and setter is inlined because the constructor of ObjectProperties is
- // considered trivial, which implies that the member value propagation marks the
- // INSTANCE field as being non-null.
- checkMethodIsRemoved(objectClass, getter);
- checkMethodIsRemoved(objectClass, setter);
- } else {
- checkMethodIsKept(objectClass, getter);
- checkMethodIsRemoved(objectClass, setter);
- }
+ checkMethodIsKept(objectClass, getter);
+ checkMethodIsRemoved(objectClass, setter);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- }
+ assertTrue(fieldSubject.getField().isPrivate());
});
}
@@ -762,31 +668,24 @@
testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getClassName());
+ return;
+ }
+
ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
String propertyName = "publicProp";
FieldSubject fieldSubject =
checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
+ assertTrue(fieldSubject.getField().isStatic());
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
- if (allowAccessModification) {
- // Getter and setter is inlined because the constructor of ObjectProperties is
- // considered trivial, which implies that the member value propagation marks the
- // INSTANCE field as being non-null.
- checkMethodIsRemoved(objectClass, getter);
- checkMethodIsRemoved(objectClass, setter);
- } else {
- checkMethodIsKept(objectClass, getter);
- checkMethodIsRemoved(objectClass, setter);
- }
+ checkMethodIsKept(objectClass, getter);
+ checkMethodIsRemoved(objectClass, setter);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- }
+ assertTrue(fieldSubject.getField().isPrivate());
});
}
@@ -798,16 +697,19 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getClassName());
+ return;
+ }
+
ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
String propertyName = "privateLateInitProp";
FieldSubject fieldSubject =
checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
+ assertTrue(fieldSubject.getField().isStatic());
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
@@ -816,11 +718,7 @@
checkMethodIsAbsent(objectClass, getter);
checkMethodIsAbsent(objectClass, setter);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- }
+ assertTrue(fieldSubject.getField().isPrivate());
});
}
@@ -832,24 +730,8 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
- .inspect(
- inspector -> {
- ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
- String propertyName = "internalLateInitProp";
- FieldSubject fieldSubject =
- checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
- MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
- MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-
- checkMethodIsRemoved(objectClass, getter);
- checkMethodIsRemoved(objectClass, setter);
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- });
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+ .inspect(inspector -> checkClassIsRemoved(inspector, testedClass.getClassName()));
}
@Test
@@ -860,24 +742,8 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
- .inspect(
- inspector -> {
- ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
- String propertyName = "publicLateInitProp";
- FieldSubject fieldSubject =
- checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
- MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
- MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-
- checkMethodIsRemoved(objectClass, getter);
- checkMethodIsRemoved(objectClass, setter);
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- });
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+ .inspect(inspector -> checkClassIsRemoved(inspector, testedClass.getClassName()));
}
@Test
@@ -891,23 +757,22 @@
testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getClassName());
+ return;
+ }
+
ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
String propertyName = "primitiveProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, "int", propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
+ assertTrue(fieldSubject.getField().isStatic());
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- checkMethodIsRemoved(objectClass, getter);
- checkMethodIsRemoved(objectClass, setter);
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- checkMethodIsKept(objectClass, getter);
- checkMethodIsRemoved(objectClass, setter);
- }
+ assertTrue(fieldSubject.getField().isPrivate());
+ checkMethodIsKept(objectClass, getter);
+ checkMethodIsRemoved(objectClass, setter);
});
}
@@ -919,16 +784,19 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getClassName());
+ return;
+ }
+
ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
String propertyName = "privateProp";
FieldSubject fieldSubject =
checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
+ assertTrue(fieldSubject.getField().isStatic());
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
@@ -936,11 +804,7 @@
// A private property has no getter/setter.
checkMethodIsAbsent(objectClass, getter);
checkMethodIsAbsent(objectClass, setter);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- }
+ assertTrue(fieldSubject.getField().isPrivate());
});
}
@@ -952,16 +816,19 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getClassName());
+ return;
+ }
+
ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
String propertyName = "internalProp";
FieldSubject fieldSubject =
checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
+ assertTrue(fieldSubject.getField().isStatic());
// We expect getter to be inlined when access (of the backing field) is relaxed to
// public.
@@ -969,13 +836,8 @@
// Note: the setter is considered as a regular method (because of KotlinC adding extra
// null checks), thus we cannot say if the setter would be inlined or not by R8.
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- checkMethodIsRemoved(objectClass, getter);
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- checkMethodIsKept(objectClass, getter);
- }
+ assertTrue(fieldSubject.getField().isPrivate());
+ checkMethodIsKept(objectClass, getter);
});
}
@@ -987,29 +849,27 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getClassName());
+ return;
+ }
+
ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
String propertyName = "publicProp";
FieldSubject fieldSubject =
checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
+ assertTrue(fieldSubject.getField().isStatic());
// We expect getter to be inlined when access (of the backing field) is relaxed to
// public. On the other hand, the setter is considered as a regular method (because of
// null checks), thus we cannot say if it can be inlined or not.
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- checkMethodIsRemoved(objectClass, getter);
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- checkMethodIsKept(objectClass, getter);
- }
+ assertTrue(fieldSubject.getField().isPrivate());
+ checkMethodIsKept(objectClass, getter);
});
}
@@ -1021,16 +881,19 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getClassName());
+ return;
+ }
+
ClassSubject fileClass = checkClassIsKept(inspector, testedClass.getClassName());
String propertyName = "privateLateInitProp";
FieldSubject fieldSubject =
checkFieldIsKept(fileClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
+ assertTrue(fieldSubject.getField().isStatic());
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
@@ -1038,11 +901,7 @@
// A private property has no getter/setter.
checkMethodIsAbsent(fileClass, getter);
checkMethodIsAbsent(fileClass, setter);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- }
+ assertTrue(fieldSubject.getField().isPrivate());
});
}
@@ -1054,26 +913,8 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
- .inspect(
- inspector -> {
- ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
- String propertyName = "internalLateInitProp";
- FieldSubject fieldSubject =
- checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
-
- MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
- MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-
- // Field is public and getter/setter is only called from one place so we expect to
- // always inline it.
- checkMethodIsRemoved(objectClass, getter);
- checkMethodIsRemoved(objectClass, setter);
- });
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+ .inspect(inspector -> checkClassIsRemoved(inspector, testedClass.getClassName()));
}
@Test
@@ -1084,24 +925,8 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
- .inspect(
- inspector -> {
- ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
- String propertyName = "publicLateInitProp";
- FieldSubject fieldSubject =
- checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
- MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
- MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-
- checkMethodIsRemoved(objectClass, getter);
- checkMethodIsRemoved(objectClass, setter);
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- });
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+ .inspect(inspector -> checkClassIsRemoved(inspector, testedClass.getClassName()));
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
index 1652222..1d25958 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -9,7 +9,6 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
-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;
@@ -70,9 +69,6 @@
ClassSubject enumClass = inspector.clazz(ENUM_CLASS_NAME);
assertThat(enumClass, isPresent());
assertEquals(minify, enumClass.isRenamed());
- // TODO(b/179994975): Kotlin enum changed in 1.5.
- assertThat(
- enumClass.clinit(),
- kotlinc.is(KotlinCompilerVersion.KOTLINC_1_5_0) ? isPresent() : isAbsent());
+ assertThat(enumClass.clinit(), isAbsent());
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
index 257e189..e62b1e0 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
@@ -105,14 +105,12 @@
}
private String getDefaultExpectedName() {
- return getDefaultExpectedName(parameters.getBackend() == Backend.CF ? "SourceFile" : "");
+ return "SourceFile";
}
private String getDefaultExpectedName(String name) {
if (!keepSourceFile) {
- if (parameters.getBackend() == Backend.CF || !isCompat) {
- return null;
- }
+ return getDefaultExpectedName();
}
return name;
}
diff --git a/src/test/java/com/android/tools/r8/naming/sourcefile/RenameSourceFileAttributeCompatTest.java b/src/test/java/com/android/tools/r8/naming/sourcefile/RenameSourceFileAttributeCompatTest.java
index 55784ce..ae03cae 100644
--- a/src/test/java/com/android/tools/r8/naming/sourcefile/RenameSourceFileAttributeCompatTest.java
+++ b/src/test/java/com/android/tools/r8/naming/sourcefile/RenameSourceFileAttributeCompatTest.java
@@ -46,8 +46,8 @@
}
private void checkSourceFileIsRemoved(SingleTestRunResult<?> result) throws Exception {
- // TODO(b/202368282): We should likely emit a "default" source file attribute rather than strip.
- checkSourceFile(result, null, null, null);
+ String removalValue = result.isR8TestRunResult() ? "SourceFile" : null;
+ checkSourceFile(result, removalValue, removalValue, removalValue);
}
private void checkSourceFileIsOriginal(SingleTestRunResult<?> result) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java b/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java
index 8793045..c4b0920 100644
--- a/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java
+++ b/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java
@@ -45,8 +45,13 @@
}
private void checkSourceFileIsRemoved(SingleTestRunResult<?> result) throws Exception {
- // TODO(b/202368282): We should likely emit a "default" source file attribute rather than strip.
- checkSourceFile(result, null, null, null);
+ if (result.isR8TestRunResult()) {
+ // R8 and R8/compat differ from PG in that at least the default source file attribute is
+ // retained. This ensures better stack traces on various VMs and has next to no size overhead.
+ checkSourceFileIsReplacedByDefault(result);
+ } else {
+ checkSourceFile(result, null, null, null);
+ }
}
private void checkSourceFileIsOriginal(SingleTestRunResult<?> result) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeKeptClassTest.java b/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeKeptClassTest.java
new file mode 100644
index 0000000..b20a64b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeKeptClassTest.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2021, 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.optimize.finalize;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isFinal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.AllOf.allOf;
+
+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 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 FinalizeKeptClassTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, allOf(isPresent(), not(isFinal())));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ // Should not become final.
+ static class Main {
+
+ public static void main(String[] args) {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeSubclassTest.java b/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeSubclassTest.java
new file mode 100644
index 0000000..f71d961
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeSubclassTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2021, 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.optimize.finalize;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isFinal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.AllOf.allOf;
+
+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 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 FinalizeSubclassTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, allOf(isPresent(), not(isFinal())));
+
+ ClassSubject bClassSubject = inspector.clazz(B.class);
+ assertThat(bClassSubject, allOf(isPresent(), not(isFinal())));
+
+ ClassSubject cClassSubject = inspector.clazz(C.class);
+ assertThat(cClassSubject, allOf(isPresent(), isFinal()));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A.m()", "B.m()", "C.m()");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A().m();
+ new B().m();
+ new C().m();
+ }
+ }
+
+ @NeverClassInline
+ @NoVerticalClassMerging
+ static class A {
+
+ @NeverInline
+ void m() {
+ System.out.println("A.m()");
+ }
+ }
+
+ @NeverClassInline
+ @NoVerticalClassMerging
+ static class B extends A {
+
+ @NeverInline
+ void m() {
+ System.out.println("B.m()");
+ }
+ }
+
+ // Should become final.
+ @NeverClassInline
+ static class C extends B {
+
+ @NeverInline
+ void m() {
+ System.out.println("C.m()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
index a913463..5c7a755 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
@@ -29,7 +29,6 @@
@RunWith(Parameterized.class)
public class KeepAttributesDotsTest extends TestBase {
- private final TestParameters parameters;
private final String keepAttributes;
@Parameterized.Parameters(name = "-keepattributes {1}")
@@ -40,7 +39,7 @@
}
public KeepAttributesDotsTest(TestParameters parameters, String keepAttributes) {
- this.parameters = parameters;
+ parameters.assertNoneRuntime();
this.keepAttributes = keepAttributes;
}
@@ -76,7 +75,7 @@
assertTrue(field.getField().annotations().isEmpty());
assertTrue(
clazz.getDexProgramClass().sourceFile == null
- || clazz.getDexProgramClass().sourceFile.size == 0);
+ || clazz.getDexProgramClass().sourceFile.toString().equals("SourceFile"));
assertNull(main.getLineNumberTable());
assertTrue(main.getLocalVariableTable().isEmpty());
}