Merge commit 'f2c3df4e1cecb6215c901479c1ada04b95dfa336' into dev-release
diff --git a/compatibility-faq.md b/compatibility-faq.md index 3ba23c8..8ca09d5 100644 --- a/compatibility-faq.md +++ b/compatibility-faq.md
@@ -129,7 +129,7 @@ the fields to be renamed by R8 to the same name, but GSON serialization will work as expected. -### GSON with full mode +### GSON GSON uses type tokens to serialize and deserialize generic types. @@ -138,7 +138,7 @@ The anonymous class will have a generic signature argument of `List<String>` to the super type `TypeToken` that is reflective read for serialization. It is therefore necessary to keep both the `Signature` attribute, the -`com.google.gson.reflect.TypeToken` class and all sub-types: +`com.google.gson.reflect.TypeToken` class and all sub-types. ``` -keepattributes Signature @@ -146,6 +146,9 @@ -keep class * extends com.google.gson.reflect.TypeToken ``` +This is also needed for R8 in compat mode since multiple optimizations will +remove the generic signature such as class merging and argument removal. + ## Retrofit ### Objects instantiated with Retrofit's `create()` method are always replaced with `null`
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java index 6f1fde5..6fd3574 100644 --- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java +++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -153,6 +153,11 @@ } @Override + public AppView<?> getAppView() { + return appView; + } + + @Override public void allocateRegisters() { computeNeedsRegister(); ImmutableList<BasicBlock> blocks = computeLivenessInformation();
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java index 5a0ec34..a5d6098 100644 --- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java +++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -4,6 +4,8 @@ package com.android.tools.r8.dex; +import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.isVivifiedType; + import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexField; @@ -149,6 +151,9 @@ private void keepClass(DexType type) { DexType baseType = type.lookupBaseType(appView.dexItemFactory()); + if (isVivifiedType(baseType)) { + return; + } toKeep.putIfAbsent(baseType, new KeepStruct()); }
diff --git a/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java index dcb6bb9..10d8c1f 100644 --- a/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java +++ b/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java
@@ -4,6 +4,8 @@ package com.android.tools.r8.errors; import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.Keep; -/** Base class for diagnostics related to proguard keep rules. */ -public abstract class ProguardKeepRuleDiagnostic implements Diagnostic {} +/** Base interface for diagnostics related to proguard keep rules. */ +@Keep +public interface ProguardKeepRuleDiagnostic extends Diagnostic {}
diff --git a/src/main/java/com/android/tools/r8/errors/UnusedProguardKeepRuleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnusedProguardKeepRuleDiagnostic.java index 396f88b..2d2b326 100644 --- a/src/main/java/com/android/tools/r8/errors/UnusedProguardKeepRuleDiagnostic.java +++ b/src/main/java/com/android/tools/r8/errors/UnusedProguardKeepRuleDiagnostic.java
@@ -9,7 +9,7 @@ import com.android.tools.r8.shaking.ProguardConfigurationRule; @Keep -public class UnusedProguardKeepRuleDiagnostic extends ProguardKeepRuleDiagnostic { +public class UnusedProguardKeepRuleDiagnostic implements ProguardKeepRuleDiagnostic { private final ProguardConfigurationRule rule;
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java new file mode 100644 index 0000000..acd327d --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java
@@ -0,0 +1,45 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +/** + * A keep condition is the content of an item in the set of preconditions. + * + * <p>It can be trivially true, or represent some program item pattern that must be present in the + * program residual. When an condition is denoted by a program item, the condition also specifies + * the extent of the item for which it is predicated on. The extent is given by a "usage kind" that + * can be either its "symbolic reference" or its "actual use". + */ +public final class KeepCondition { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private KeepItemPattern itemPattern; + + private Builder() {} + + public Builder setItem(KeepItemPattern itemPattern) { + this.itemPattern = itemPattern; + return this; + } + + public KeepCondition build() { + return new KeepCondition(itemPattern); + } + } + + private final KeepItemPattern itemPattern; + + private KeepCondition(KeepItemPattern itemPattern) { + this.itemPattern = itemPattern; + } + + public KeepItemPattern getItemPattern() { + return itemPattern; + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java new file mode 100644 index 0000000..6a227fd --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java
@@ -0,0 +1,53 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Set of consequences of a keep edge. + * + * <p>The consequences are "targets" described by item patterns along with "keep options" which + * detail what aspects of the items must be retained. + * + * <p>The consequences come into effect if the preconditions of an edge are met. + */ +public final class KeepConsequences { + + public static class Builder { + + private List<KeepTarget> targets = new ArrayList<>(); + + private Builder() {} + + public Builder addTarget(KeepTarget target) { + targets.add(target); + return this; + } + + public KeepConsequences build() { + return new KeepConsequences(targets); + } + } + + public static Builder builder() { + return new Builder(); + } + + private final List<KeepTarget> targets; + + private KeepConsequences(List<KeepTarget> targets) { + this.targets = targets; + } + + public boolean isEmpty() { + return targets.isEmpty(); + } + + public void forEachTarget(Consumer<KeepTarget> fn) { + targets.forEach(fn); + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java new file mode 100644 index 0000000..f61483a --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java
@@ -0,0 +1,57 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +/** + * An edge in the keep graph. + * + * <p>An edge describes a set of preconditions and a set of consequences. If the preconditions are + * met, then the consequences are put into effect. + */ +public final class KeepEdge { + + public static class Builder { + private KeepPreconditions preconditions = KeepPreconditions.always(); + private KeepConsequences consequences; + + private Builder() {} + + public Builder setPreconditions(KeepPreconditions preconditions) { + this.preconditions = preconditions; + return this; + } + + public Builder setConsequences(KeepConsequences consequences) { + this.consequences = consequences; + return this; + } + + public KeepEdge build() { + if (consequences.isEmpty()) { + throw new KeepEdgeException("KeepEdge must have non-empty set of consequences."); + } + return new KeepEdge(preconditions, consequences); + } + } + + public static Builder builder() { + return new Builder(); + } + + private final KeepPreconditions preconditions; + private final KeepConsequences consequences; + + private KeepEdge(KeepPreconditions preconditions, KeepConsequences consequences) { + this.preconditions = preconditions; + this.consequences = consequences; + } + + public KeepPreconditions getPreconditions() { + return preconditions; + } + + public KeepConsequences getConsequences() { + return consequences; + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java new file mode 100644 index 0000000..3345c8f --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java
@@ -0,0 +1,11 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +public class KeepEdgeException extends RuntimeException { + + public KeepEdgeException(String message) { + super(message); + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java new file mode 100644 index 0000000..17e543f --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java
@@ -0,0 +1,68 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +/** Pattern for matching the "extends" or "implements" clause of a class. */ +public abstract class KeepExtendsPattern { + + public static KeepExtendsPattern any() { + return KeepExtendsAnyPattern.getInstance(); + } + + public static class Builder { + + private KeepExtendsPattern pattern; + + private Builder() {} + + public Builder any() { + pattern = KeepExtendsAnyPattern.getInstance(); + return this; + } + + public Builder classPattern(KeepQualifiedClassNamePattern pattern) { + this.pattern = new KeepExtendsClassPattern(pattern); + return this; + } + } + + private static class KeepExtendsAnyPattern extends KeepExtendsPattern { + + private static KeepExtendsAnyPattern INSTANCE = null; + + public static KeepExtendsAnyPattern getInstance() { + if (INSTANCE == null) { + INSTANCE = new KeepExtendsAnyPattern(); + } + return INSTANCE; + } + + @Override + public boolean isAny() { + return true; + } + } + + private static class KeepExtendsClassPattern extends KeepExtendsPattern { + + private final KeepQualifiedClassNamePattern pattern; + + public KeepExtendsClassPattern(KeepQualifiedClassNamePattern pattern) { + this.pattern = pattern; + } + + @Override + public boolean isAny() { + return pattern.isAny(); + } + } + + public static Builder builder() { + return new Builder(); + } + + private KeepExtendsPattern() {} + + public abstract boolean isAny(); +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java new file mode 100644 index 0000000..04e2697 --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java
@@ -0,0 +1,13 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +public class KeepFieldPattern extends KeepMemberPattern { + + private KeepFieldPattern() {} + + public boolean isAnyField() { + return false; + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java new file mode 100644 index 0000000..3c6f947 --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java
@@ -0,0 +1,138 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * A pattern for matching items in the program. + * + * <p>An item pattern can be any item, or it can describe a family of classes or a family of members + * on a classes. + * + * <p>A pattern cannot describe both a class *and* a member of a class. Either it is a pattern on + * classes or it is a pattern on members. The distinction is defined by having a "none" member + * pattern. + */ +public abstract class KeepItemPattern { + + public static Builder builder() { + return new Builder(); + } + + public static KeepItemPattern any() { + return KeepItemAnyPattern.getInstance(); + } + + public static class Builder { + + private KeepQualifiedClassNamePattern classNamePattern; + private KeepExtendsPattern extendsPattern = KeepExtendsPattern.any(); + private KeepMembersPattern membersPattern = KeepMembersPattern.none(); + + private Builder() {} + + public Builder any() { + classNamePattern = KeepQualifiedClassNamePattern.any(); + extendsPattern = KeepExtendsPattern.any(); + membersPattern = KeepMembersPattern.all(); + return this; + } + + public Builder setClassPattern(KeepQualifiedClassNamePattern qualifiedClassNamePattern) { + this.classNamePattern = qualifiedClassNamePattern; + return this; + } + + public Builder setExtendsPattern(KeepExtendsPattern extendsPattern) { + this.extendsPattern = extendsPattern; + return this; + } + + public Builder setMembersPattern(KeepMembersPattern membersPattern) { + this.membersPattern = membersPattern; + return this; + } + + public KeepItemPattern build() { + if (classNamePattern == null) { + throw new KeepEdgeException("Class pattern must define a class name pattern."); + } + if (classNamePattern.isAny() && extendsPattern.isAny() && membersPattern.isAll()) { + return KeepItemPattern.any(); + } + return new KeepClassPattern(classNamePattern, extendsPattern, membersPattern); + } + } + + private static class KeepItemAnyPattern extends KeepItemPattern { + + private static KeepItemAnyPattern INSTANCE = null; + + public static KeepItemAnyPattern getInstance() { + if (INSTANCE == null) { + INSTANCE = new KeepItemAnyPattern(); + } + return INSTANCE; + } + + @Override + public boolean isAny() { + return true; + } + + @Override + public <T> T match(Supplier<T> onAny, Function<KeepClassPattern, T> onItem) { + return onAny.get(); + } + } + + public static class KeepClassPattern extends KeepItemPattern { + + private final KeepQualifiedClassNamePattern qualifiedClassPattern; + private final KeepExtendsPattern extendsPattern; + private final KeepMembersPattern membersPattern; + // TODO: class annotations + + private KeepClassPattern( + KeepQualifiedClassNamePattern qualifiedClassPattern, + KeepExtendsPattern extendsPattern, + KeepMembersPattern membersPattern) { + this.qualifiedClassPattern = qualifiedClassPattern; + this.extendsPattern = extendsPattern; + this.membersPattern = membersPattern; + } + + @Override + public boolean isAny() { + return qualifiedClassPattern.isAny() && extendsPattern.isAny() && membersPattern.isAll(); + } + + @Override + public <T> T match(Supplier<T> onAny, Function<KeepClassPattern, T> onItem) { + if (isAny()) { + return onAny.get(); + } else { + return onItem.apply(this); + } + } + + public KeepQualifiedClassNamePattern getClassNamePattern() { + return qualifiedClassPattern; + } + + public KeepExtendsPattern getExtendsPattern() { + return extendsPattern; + } + + public KeepMembersPattern getMembersPattern() { + return membersPattern; + } + } + + public abstract boolean isAny(); + + public abstract <T> T match(Supplier<T> onAny, Function<KeepClassPattern, T> onItem); +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java new file mode 100644 index 0000000..73bae7e --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java
@@ -0,0 +1,38 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +public abstract class KeepMemberPattern { + + public static KeepMemberPattern anyMember() { + return KeepMemberAnyPattern.getInstance(); + } + + private static class KeepMemberAnyPattern extends KeepMemberPattern { + private static KeepMemberAnyPattern INSTANCE = null; + + public static KeepMemberAnyPattern getInstance() { + if (INSTANCE == null) { + INSTANCE = new KeepMemberAnyPattern(); + } + return INSTANCE; + } + + @Override + public boolean isAnyMember() { + return true; + } + } + + public boolean isAnyMember() { + return false; + } + + abstract static class Builder<T extends Builder<T>> { + + public abstract T self(); + + Builder() {} + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java new file mode 100644 index 0000000..6d3f93e --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java
@@ -0,0 +1,159 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +import com.android.tools.r8.errors.Unimplemented; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public abstract class KeepMembersPattern { + + public static Builder builder() { + return new Builder(); + } + + public static KeepMembersPattern none() { + return KeepMembersNonePattern.getInstance(); + } + + public static KeepMembersPattern all() { + return KeepMembersAllPattern.getInstance(); + } + + public static class Builder { + + private boolean anyMethod = false; + private boolean anyField = false; + private List<KeepMethodPattern> methods = new ArrayList<>(); + private List<KeepFieldPattern> fields = new ArrayList<>(); + + public Builder addMethodPattern(KeepMethodPattern methodPattern) { + if (anyMethod) { + return this; + } + if (methodPattern.isAnyMethod()) { + methods.clear(); + anyMethod = true; + } + methods.add(methodPattern); + return this; + } + + public Builder addFieldPattern(KeepFieldPattern fieldPattern) { + if (anyField) { + return this; + } + if (fieldPattern.isAnyField()) { + fields.clear(); + anyField = true; + } + fields.add(fieldPattern); + return this; + } + + public KeepMembersPattern build() { + if (methods.isEmpty() && fields.isEmpty()) { + return KeepMembersPattern.none(); + } + if (anyMethod && anyField) { + return KeepMembersPattern.all(); + } + return new KeepMembersSomePattern(methods, fields); + } + } + + private static class KeepMembersAllPattern extends KeepMembersPattern { + + private static KeepMembersAllPattern INSTANCE = null; + + public static KeepMembersAllPattern getInstance() { + if (INSTANCE == null) { + INSTANCE = new KeepMembersAllPattern(); + } + return INSTANCE; + } + + @Override + public boolean isAll() { + return true; + } + + @Override + public boolean isNone() { + return true; + } + + @Override + public void forEach(Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod) { + throw new Unimplemented("Should this include all and none?"); + } + } + + private static class KeepMembersNonePattern extends KeepMembersPattern { + + private static KeepMembersNonePattern INSTANCE = null; + + public static KeepMembersNonePattern getInstance() { + if (INSTANCE == null) { + INSTANCE = new KeepMembersNonePattern(); + } + return INSTANCE; + } + + @Override + public boolean isAll() { + return false; + } + + @Override + public boolean isNone() { + return true; + } + + @Override + public void forEach(Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod) { + throw new Unimplemented("Should this include all and none?"); + } + } + + private static class KeepMembersSomePattern extends KeepMembersPattern { + + private final List<KeepMethodPattern> methods; + private final List<KeepFieldPattern> fields; + + private KeepMembersSomePattern(List<KeepMethodPattern> methods, List<KeepFieldPattern> fields) { + assert !methods.isEmpty() || !fields.isEmpty(); + this.methods = methods; + this.fields = fields; + } + + @Override + public boolean isAll() { + // Since there is at least one none-all field or method this is not a match all. + return false; + } + + @Override + public boolean isNone() { + // Since there is at least one field or method this is not a match none. + return false; + } + + @Override + public void forEach(Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod) { + fields.forEach(onField); + methods.forEach(onMethod); + } + } + + private KeepMembersPattern() {} + + public abstract boolean isAll(); + + public abstract boolean isNone(); + + public abstract void forEach( + Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod); +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java new file mode 100644 index 0000000..7f83937 --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java
@@ -0,0 +1,16 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +// TODO: finish this. +public class KeepMethodAccessPattern { + + public static KeepMethodAccessPattern any() { + return new KeepMethodAccessPattern(); + } + + public boolean isAny() { + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java new file mode 100644 index 0000000..ada6701 --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java
@@ -0,0 +1,64 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +import java.util.function.Function; +import java.util.function.Supplier; + +public abstract class KeepMethodNamePattern { + + public static KeepMethodNamePattern any() { + return KeepMethodNameAnyPattern.getInstance(); + } + + public static KeepMethodNamePattern initializer() { + return new KeepMethodNameExactPattern("<init>"); + } + + public static KeepMethodNamePattern exact(String methodName) { + return new KeepMethodNameExactPattern(methodName); + } + + private KeepMethodNamePattern() {} + + public boolean isAny() { + return match(() -> true, ignore -> false); + } + + public boolean isExact() { + return match(() -> false, ignore -> true); + } + ; + + public abstract <T> T match(Supplier<T> onAny, Function<String, T> onExact); + + private static class KeepMethodNameAnyPattern extends KeepMethodNamePattern { + private static KeepMethodNameAnyPattern INSTANCE = null; + + public static KeepMethodNameAnyPattern getInstance() { + if (INSTANCE == null) { + INSTANCE = new KeepMethodNameAnyPattern(); + } + return INSTANCE; + } + + @Override + public <T> T match(Supplier<T> onAny, Function<String, T> onExact) { + return onAny.get(); + } + } + + private static class KeepMethodNameExactPattern extends KeepMethodNamePattern { + private final String name; + + public KeepMethodNameExactPattern(String name) { + this.name = name; + } + + @Override + public <T> T match(Supplier<T> onAny, Function<String, T> onExact) { + return onExact.apply(name); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java new file mode 100644 index 0000000..4f42202 --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java
@@ -0,0 +1,56 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; + +public abstract class KeepMethodParametersPattern { + + public static KeepMethodParametersPattern any() { + return Any.getInstance(); + } + + public static KeepMethodParametersPattern none() { + return None.getInstance(); + } + + private KeepMethodParametersPattern() {} + + public abstract <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList); + + private static class None extends KeepMethodParametersPattern { + private static None INSTANCE = null; + + public static None getInstance() { + if (INSTANCE == null) { + INSTANCE = new None(); + } + return INSTANCE; + } + + @Override + public <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList) { + return onList.apply(Collections.emptyList()); + } + } + + private static class Any extends KeepMethodParametersPattern { + private static Any INSTANCE = null; + + public static Any getInstance() { + if (INSTANCE == null) { + INSTANCE = new Any(); + } + return INSTANCE; + } + + @Override + public <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList) { + return onAny.get(); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java new file mode 100644 index 0000000..5db7d26 --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java
@@ -0,0 +1,90 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +public final class KeepMethodPattern extends KeepMemberPattern { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends KeepMemberPattern.Builder<Builder> { + + private KeepMethodAccessPattern accessPattern = KeepMethodAccessPattern.any(); + private KeepMethodNamePattern namePattern = null; + private KeepMethodReturnTypePattern returnTypePattern = KeepMethodReturnTypePattern.any(); + private KeepMethodParametersPattern parametersPattern = KeepMethodParametersPattern.any(); + + private Builder() {} + + @Override + public Builder self() { + return this; + } + + public Builder setAccessPattern(KeepMethodAccessPattern accessPattern) { + this.accessPattern = accessPattern; + return self(); + } + + public Builder setNamePattern(KeepMethodNamePattern namePattern) { + this.namePattern = namePattern; + return self(); + } + + public Builder setReturnTypeVoid() { + returnTypePattern = KeepMethodReturnTypePattern.voidType(); + return self(); + } + + public Builder setParametersPattern(KeepMethodParametersPattern parametersPattern) { + this.parametersPattern = parametersPattern; + return self(); + } + + public KeepMethodPattern build() { + if (namePattern == null) { + throw new KeepEdgeException("Method pattern must declar a name pattern"); + } + return new KeepMethodPattern( + accessPattern, namePattern, returnTypePattern, parametersPattern); + } + } + + private final KeepMethodAccessPattern accessPattern; + private final KeepMethodNamePattern namePattern; + private final KeepMethodReturnTypePattern returnTypePattern; + private final KeepMethodParametersPattern parametersPattern; + + private KeepMethodPattern( + KeepMethodAccessPattern accessPattern, + KeepMethodNamePattern namePattern, + KeepMethodReturnTypePattern returnTypePattern, + KeepMethodParametersPattern parametersPattern) { + this.accessPattern = accessPattern; + this.namePattern = namePattern; + this.returnTypePattern = returnTypePattern; + this.parametersPattern = parametersPattern; + } + + public boolean isAnyMethod() { + return false; + } + + public KeepMethodAccessPattern getAccessPattern() { + return accessPattern; + } + + public KeepMethodNamePattern getNamePattern() { + return namePattern; + } + + public KeepMethodReturnTypePattern getReturnTypePattern() { + return returnTypePattern; + } + + public KeepMethodParametersPattern getParametersPattern() { + return parametersPattern; + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java new file mode 100644 index 0000000..4e7b5b9 --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java
@@ -0,0 +1,55 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +import java.util.function.Function; +import java.util.function.Supplier; + +public abstract class KeepMethodReturnTypePattern { + + private static SomeType ANY_TYPE_INSTANCE = null; + + public static KeepMethodReturnTypePattern any() { + if (ANY_TYPE_INSTANCE == null) { + ANY_TYPE_INSTANCE = new SomeType(KeepTypePattern.any()); + } + return ANY_TYPE_INSTANCE; + } + + public static KeepMethodReturnTypePattern voidType() { + return VoidType.getInstance(); + } + + public abstract <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType); + + private static class VoidType extends KeepMethodReturnTypePattern { + private static VoidType INSTANCE = null; + + public static VoidType getInstance() { + if (INSTANCE == null) { + INSTANCE = new VoidType(); + } + return INSTANCE; + } + + @Override + public <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType) { + return onVoid.get(); + } + } + + private static class SomeType extends KeepMethodReturnTypePattern { + + private final KeepTypePattern typePattern; + + private SomeType(KeepTypePattern typePattern) { + this.typePattern = typePattern; + } + + @Override + public <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType) { + return onType.apply(typePattern); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java new file mode 100644 index 0000000..9fb521c --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java
@@ -0,0 +1,97 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +import com.android.tools.r8.utils.SetUtils; +import com.google.common.collect.ImmutableSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +public final class KeepOptions { + + public enum KeepOption { + SHRINKING, + OPTIMIZING, + OBFUSCATING, + ACCESS_MODIFING, + } + + public static KeepOptions keepAll() { + if (ALLOW_NONE_INSTANCE == null) { + ALLOW_NONE_INSTANCE = new KeepOptions(true, Collections.emptySet()); + } + return ALLOW_NONE_INSTANCE; + } + + public static Builder allowBuilder() { + return new Builder(true); + } + + public static Builder disallowBuilder() { + return new Builder(false); + } + + public static KeepOptions allow(KeepOption... options) { + return allowBuilder().addAll(options).build(); + } + + public static KeepOptions disallow(KeepOption... options) { + return disallowBuilder().addAll(options).build(); + } + + public static class Builder { + public final boolean allowIfSet; + public Set<KeepOption> options = SetUtils.newIdentityHashSet(); + + private Builder(boolean allowIfSet) { + this.allowIfSet = allowIfSet; + } + + public Builder add(KeepOption option) { + options.add(option); + return this; + } + + public Builder addAll(KeepOption... options) { + return addAll(Arrays.asList(options)); + } + + public Builder addAll(Collection<KeepOption> options) { + this.options.addAll(options); + return this; + } + + public KeepOptions build() { + if (options.isEmpty()) { + if (allowIfSet) { + return keepAll(); + } + throw new KeepEdgeException("Invalid keep options that disallow nothing."); + } + if (options.size() == KeepOption.values().length) { + if (!allowIfSet) { + return keepAll(); + } + throw new KeepEdgeException("Invalid keep options that allow everything."); + } + return new KeepOptions(allowIfSet, ImmutableSet.copyOf(options)); + } + } + + private static KeepOptions ALLOW_NONE_INSTANCE = null; + + private final boolean allowIfSet; + private final Set<KeepOption> options; + + private KeepOptions(boolean allowIfSet, Set<KeepOption> options) { + this.allowIfSet = allowIfSet; + this.options = options; + } + + public boolean isAllowed(KeepOption option) { + return options.contains(option) == allowIfSet; + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java new file mode 100644 index 0000000..10db385 --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java
@@ -0,0 +1,180 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +public abstract class KeepPackagePattern { + + public static Builder builder() { + return new Builder(); + } + + public static KeepPackagePattern any() { + return KeepPackageAnyPattern.getInstance(); + } + + public static KeepPackagePattern top() { + return KeepPackageTopPattern.getInstance(); + } + + public static KeepPackagePattern exact(String fullPackage) { + return KeepPackagePattern.builder().exact(fullPackage).build(); + } + + public static class Builder { + + private KeepPackagePattern pattern; + + public Builder any() { + pattern = KeepPackageAnyPattern.getInstance(); + return this; + } + + public Builder top() { + pattern = KeepPackageTopPattern.getInstance(); + return this; + } + + public Builder exact(String fullPackage) { + pattern = + fullPackage.isEmpty() + ? KeepPackagePattern.top() + : new KeepPackageExactPattern(fullPackage); + return this; + } + + public KeepPackagePattern build() { + if (pattern == null) { + throw new KeepEdgeException("Invalid package pattern: null"); + } + return pattern; + } + } + + private static final class KeepPackageAnyPattern extends KeepPackagePattern { + + private static KeepPackageAnyPattern INSTANCE = null; + + public static KeepPackageAnyPattern getInstance() { + if (INSTANCE == null) { + INSTANCE = new KeepPackageAnyPattern(); + } + return INSTANCE; + } + + private KeepPackageAnyPattern() {} + + @Override + public boolean isAny() { + return true; + } + + @Override + public boolean isTop() { + return false; + } + + @Override + public boolean isExact() { + return false; + } + + @Override + public boolean equals(Object obj) { + return obj == this; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + } + + private static final class KeepPackageTopPattern extends KeepPackageExactPattern { + + private static KeepPackageTopPattern INSTANCE = null; + + public static KeepPackageTopPattern getInstance() { + if (INSTANCE == null) { + INSTANCE = new KeepPackageTopPattern(); + } + return INSTANCE; + } + + private KeepPackageTopPattern() { + super(""); + } + + @Override + public boolean isAny() { + return false; + } + + @Override + public boolean isTop() { + return true; + } + } + + public static class KeepPackageExactPattern extends KeepPackagePattern { + + private final String fullPackage; + + private KeepPackageExactPattern(String fullPackage) { + assert fullPackage != null; + this.fullPackage = fullPackage; + // TODO: Verify valid package identifiers. + } + + @Override + public boolean isAny() { + return false; + } + + @Override + public boolean isTop() { + return fullPackage.equals(""); + } + + @Override + public boolean isExact() { + return true; + } + + @Override + public KeepPackageExactPattern asExact() { + return this; + } + + public String getExactPackageAsString() { + return fullPackage; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + KeepPackageExactPattern that = (KeepPackageExactPattern) o; + return fullPackage.equals(that.fullPackage); + } + + @Override + public int hashCode() { + return fullPackage.hashCode(); + } + } + + public abstract boolean isAny(); + + public abstract boolean isTop(); + + public abstract boolean isExact(); + + public KeepPackageExactPattern asExact() { + return null; + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java new file mode 100644 index 0000000..f3083df --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java
@@ -0,0 +1,82 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public abstract class KeepPreconditions { + + public abstract void forEach(Consumer<KeepCondition> fn); + + public static class Builder { + + private List<KeepCondition> preconditions = new ArrayList<>(); + + private Builder() {} + + public Builder addCondition(KeepCondition condition) { + preconditions.add(condition); + return this; + } + + public KeepPreconditions build() { + return preconditions.isEmpty() + ? KeepPreconditions.always() + : new KeepPreconditionsSome(preconditions); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static KeepPreconditions always() { + return KeepPreconditionsAlways.getInstance(); + } + + public abstract boolean isAlways(); + + private static class KeepPreconditionsAlways extends KeepPreconditions { + + private static KeepPreconditionsAlways INSTANCE = null; + + public static KeepPreconditionsAlways getInstance() { + if (INSTANCE == null) { + INSTANCE = new KeepPreconditionsAlways(); + } + return INSTANCE; + } + + @Override + public boolean isAlways() { + return true; + } + + @Override + public void forEach(Consumer<KeepCondition> fn) { + // Empty. + } + } + + private static class KeepPreconditionsSome extends KeepPreconditions { + + private final List<KeepCondition> preconditions; + + private KeepPreconditionsSome(List<KeepCondition> preconditions) { + this.preconditions = preconditions; + } + + @Override + public boolean isAlways() { + return false; + } + + @Override + public void forEach(Consumer<KeepCondition> fn) { + preconditions.forEach(fn); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java new file mode 100644 index 0000000..9485580 --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
@@ -0,0 +1,101 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +import java.util.Objects; + +public final class KeepQualifiedClassNamePattern { + + public static Builder builder() { + return new Builder(); + } + + public static KeepQualifiedClassNamePattern any() { + return KeepQualifiedClassNamePattern.builder() + .setPackagePattern(KeepPackagePattern.any()) + .setNamePattern(KeepUnqualfiedClassNamePattern.any()) + .build(); + } + + public static KeepQualifiedClassNamePattern exact(String qualifiedClassName) { + int pkgSeparator = qualifiedClassName.lastIndexOf('.'); + if (pkgSeparator == 0) { + throw new KeepEdgeException("Unexpected '.' at index 0 in '" + qualifiedClassName + "'"); + } + if (pkgSeparator > 0) { + return KeepQualifiedClassNamePattern.builder() + .setPackagePattern( + KeepPackagePattern.exact(qualifiedClassName.substring(0, pkgSeparator))) + .setNamePattern( + KeepUnqualfiedClassNamePattern.exact(qualifiedClassName.substring(pkgSeparator + 1))) + .build(); + } + return KeepQualifiedClassNamePattern.builder() + .setPackagePattern(KeepPackagePattern.top()) + .setNamePattern(KeepUnqualfiedClassNamePattern.exact(qualifiedClassName)) + .build(); + } + + public static class Builder { + + private KeepPackagePattern packagePattern; + private KeepUnqualfiedClassNamePattern namePattern; + + private Builder() {} + + public Builder setPackagePattern(KeepPackagePattern packagePattern) { + this.packagePattern = packagePattern; + return this; + } + + public Builder setNamePattern(KeepUnqualfiedClassNamePattern namePattern) { + this.namePattern = namePattern; + return this; + } + + public KeepQualifiedClassNamePattern build() { + return new KeepQualifiedClassNamePattern(packagePattern, namePattern); + } + } + + private final KeepPackagePattern packagePattern; + private final KeepUnqualfiedClassNamePattern namePattern; + + public KeepQualifiedClassNamePattern( + KeepPackagePattern packagePattern, KeepUnqualfiedClassNamePattern namePattern) { + assert packagePattern != null; + assert namePattern != null; + this.packagePattern = packagePattern; + this.namePattern = namePattern; + } + + public boolean isAny() { + return packagePattern.isAny() && namePattern.isAny(); + } + + public KeepPackagePattern getPackagePattern() { + return packagePattern; + } + + public KeepUnqualfiedClassNamePattern getNamePattern() { + return namePattern; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + KeepQualifiedClassNamePattern that = (KeepQualifiedClassNamePattern) o; + return packagePattern.equals(that.packagePattern) && namePattern.equals(that.namePattern); + } + + @Override + public int hashCode() { + return Objects.hash(packagePattern.hashCode(), namePattern.hashCode()); + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java new file mode 100644 index 0000000..a63a34a --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java
@@ -0,0 +1,56 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +public class KeepTarget { + + public static KeepTarget any() { + return KeepTarget.builder().setItem(KeepItemPattern.any()).build(); + } + + public static class Builder { + + private KeepItemPattern item; + private KeepOptions options = KeepOptions.keepAll(); + + private Builder() {} + + public Builder setItem(KeepItemPattern item) { + this.item = item; + return this; + } + + public Builder setOptions(KeepOptions options) { + this.options = options; + return this; + } + + public KeepTarget build() { + if (item == null) { + throw new KeepEdgeException("Target must define an item pattern"); + } + return new KeepTarget(item, options); + } + } + + private final KeepItemPattern item; + private final KeepOptions options; + + private KeepTarget(KeepItemPattern item, KeepOptions options) { + this.item = item; + this.options = options; + } + + public static Builder builder() { + return new Builder(); + } + + public KeepItemPattern getItem() { + return item; + } + + public KeepOptions getOptions() { + return options; + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java new file mode 100644 index 0000000..eff9e9c --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java
@@ -0,0 +1,29 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +public abstract class KeepTypePattern { + + public static KeepTypePattern any() { + return Any.getInstance(); + } + + private static class Any extends KeepTypePattern { + private static Any INSTANCE = null; + + public static Any getInstance() { + if (INSTANCE == null) { + INSTANCE = new Any(); + } + return INSTANCE; + } + + @Override + public boolean isAny() { + return true; + } + } + + public abstract boolean isAny(); +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java new file mode 100644 index 0000000..ab38ed2 --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java
@@ -0,0 +1,129 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +public abstract class KeepUnqualfiedClassNamePattern { + + public static Builder builder() { + return new Builder(); + } + + public static KeepUnqualfiedClassNamePattern any() { + return KeepClassNameAnyPattern.getInstance(); + } + + public static KeepUnqualfiedClassNamePattern exact(String className) { + return builder().exact(className).build(); + } + + public static class Builder { + + private KeepUnqualfiedClassNamePattern pattern; + + public Builder any() { + pattern = KeepClassNameAnyPattern.getInstance(); + return this; + } + + public Builder exact(String className) { + pattern = new KeepClassNameExactPattern(className); + return this; + } + + public KeepUnqualfiedClassNamePattern build() { + if (pattern == null) { + throw new KeepEdgeException("Invalid class name pattern: null"); + } + return pattern; + } + } + + private static class KeepClassNameAnyPattern extends KeepUnqualfiedClassNamePattern { + + private static KeepClassNameAnyPattern INSTANCE = null; + + public static KeepClassNameAnyPattern getInstance() { + if (INSTANCE == null) { + INSTANCE = new KeepClassNameAnyPattern(); + } + return INSTANCE; + } + + private KeepClassNameAnyPattern() {} + + @Override + public boolean isAny() { + return true; + } + + @Override + public boolean isExact() { + return false; + } + + @Override + public boolean equals(Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + } + + public static class KeepClassNameExactPattern extends KeepUnqualfiedClassNamePattern { + + private final String className; + + private KeepClassNameExactPattern(String className) { + assert className != null; + this.className = className; + } + + @Override + public boolean isAny() { + return false; + } + + @Override + public boolean isExact() { + return true; + } + + @Override + public KeepClassNameExactPattern asExact() { + return this; + } + + public String getExactNameAsString() { + return className; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + KeepClassNameExactPattern that = (KeepClassNameExactPattern) o; + return className.equals(that.className); + } + + @Override + public int hashCode() { + return className.hashCode(); + } + } + + public abstract boolean isAny(); + + public abstract boolean isExact(); + + public KeepClassNameExactPattern asExact() { + return null; + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java b/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java new file mode 100644 index 0000000..6c24ec1 --- /dev/null +++ b/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java
@@ -0,0 +1,262 @@ +// Copyright (c) 2022, 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.experimental.keepanno.keeprules; + +import com.android.tools.r8.errors.Unimplemented; +import com.android.tools.r8.experimental.keepanno.ast.KeepConsequences; +import com.android.tools.r8.experimental.keepanno.ast.KeepEdge; +import com.android.tools.r8.experimental.keepanno.ast.KeepFieldPattern; +import com.android.tools.r8.experimental.keepanno.ast.KeepItemPattern.KeepClassPattern; +import com.android.tools.r8.experimental.keepanno.ast.KeepMembersPattern; +import com.android.tools.r8.experimental.keepanno.ast.KeepMethodAccessPattern; +import com.android.tools.r8.experimental.keepanno.ast.KeepMethodNamePattern; +import com.android.tools.r8.experimental.keepanno.ast.KeepMethodParametersPattern; +import com.android.tools.r8.experimental.keepanno.ast.KeepMethodPattern; +import com.android.tools.r8.experimental.keepanno.ast.KeepMethodReturnTypePattern; +import com.android.tools.r8.experimental.keepanno.ast.KeepOptions; +import com.android.tools.r8.experimental.keepanno.ast.KeepOptions.KeepOption; +import com.android.tools.r8.experimental.keepanno.ast.KeepPackagePattern; +import com.android.tools.r8.experimental.keepanno.ast.KeepPreconditions; +import com.android.tools.r8.experimental.keepanno.ast.KeepQualifiedClassNamePattern; +import com.android.tools.r8.experimental.keepanno.ast.KeepTarget; +import com.android.tools.r8.experimental.keepanno.ast.KeepTypePattern; +import com.android.tools.r8.experimental.keepanno.ast.KeepUnqualfiedClassNamePattern; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.StringUtils.BraceType; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class KeepRuleExtractor { + + private final Consumer<String> ruleConsumer; + + public KeepRuleExtractor(Consumer<String> ruleConsumer) { + this.ruleConsumer = ruleConsumer; + } + + public void extract(KeepEdge edge) { + List<ItemRule> consequentRules = getConsequentRules(edge.getConsequences()); + printConditionalRules(consequentRules, edge.getPreconditions()); + } + + private List<ItemRule> getConsequentRules(KeepConsequences consequences) { + List<ItemRule> consequentItems = new ArrayList<>(); + consequences.forEachTarget(target -> consequentItems.add(new ItemRule(target))); + return consequentItems; + } + + private void printConditionalRules( + List<ItemRule> consequentRules, KeepPreconditions preconditions) { + boolean[] hasAtLeastOneConditionalClause = new boolean[1]; + preconditions.forEach( + condition -> { + // The usage kind for a predicate is not expressible in keep rules, so it is + // ignored. + condition + .getItemPattern() + .match( + () -> { + // If the conditions is "any" then we ignore it for now (identity of + // conjunction). + return null; + }, + conditionItem -> { + hasAtLeastOneConditionalClause[0] = true; + consequentRules.forEach( + consequentItem -> { + // Since conjunctions are not supported in keep rules, we expand them into + // disjunctions so conservatively we keep the consequences if any one of + // the preconditions hold. + StringBuilder builder = new StringBuilder(); + if (!consequentItem.isMemberConsequent() + || !conditionItem + .getClassNamePattern() + .equals(consequentItem.getHolderPattern())) { + builder.append("-if "); + printClassItem(builder, conditionItem); + builder.append(' '); + } + printConsequentRule(builder, consequentItem); + ruleConsumer.accept(builder.toString()); + }); + return null; + }); + }); + assert !(preconditions.isAlways() && hasAtLeastOneConditionalClause[0]); + if (!hasAtLeastOneConditionalClause[0]) { + // If there are no preconditions, print each consequent as is. + consequentRules.forEach( + r -> ruleConsumer.accept(printConsequentRule(new StringBuilder(), r).toString())); + } + } + + private static StringBuilder printConsequentRule(StringBuilder builder, ItemRule rule) { + if (rule.isMemberConsequent()) { + builder.append("-keepclassmembers"); + } else { + builder.append("-keep"); + } + for (KeepOption option : KeepOption.values()) { + if (rule.options.isAllowed(option)) { + builder.append(",allow").append(getOptionString(option)); + } + } + return builder.append(" ").append(rule.getKeepRuleForItem()); + } + + private static StringBuilder printClassItem( + StringBuilder builder, KeepClassPattern clazzPattern) { + builder.append("class "); + printClassName(builder, clazzPattern.getClassNamePattern()); + if (!clazzPattern.getExtendsPattern().isAny()) { + throw new Unimplemented(); + } + KeepMembersPattern members = clazzPattern.getMembersPattern(); + if (members.isNone()) { + return builder; + } + if (members.isAll()) { + return builder.append(" { *; }"); + } + builder.append(" {"); + members.forEach( + field -> printField(builder.append(' '), field), + method -> printMethod(builder.append(' '), method)); + return builder.append(" }"); + } + + private static StringBuilder printField(StringBuilder builder, KeepFieldPattern field) { + if (field.isAnyField()) { + return builder.append("<fields>;"); + } else { + throw new Unimplemented(); + } + } + + private static StringBuilder printMethod(StringBuilder builder, KeepMethodPattern methodPattern) { + if (methodPattern.isAnyMethod()) { + return builder.append("<methods>;"); + } + printAccess(builder, " ", methodPattern.getAccessPattern()); + printReturnType(builder, methodPattern.getReturnTypePattern()); + builder.append(' '); + printMethodName(builder, methodPattern.getNamePattern()); + printParameters(builder, methodPattern.getParametersPattern()); + return builder.append(';'); + } + + private static StringBuilder printParameters( + StringBuilder builder, KeepMethodParametersPattern parametersPattern) { + return parametersPattern.match( + () -> builder.append("(***)"), + list -> StringUtils.append(builder, list, ", ", BraceType.PARENS)); + } + + private static StringBuilder printMethodName( + StringBuilder builder, KeepMethodNamePattern namePattern) { + return namePattern.match(() -> builder.append("*"), builder::append); + } + + private static StringBuilder printReturnType( + StringBuilder builder, KeepMethodReturnTypePattern returnTypePattern) { + return returnTypePattern.match( + () -> builder.append("void"), typePattern -> printType(builder, typePattern)); + } + + private static StringBuilder printType(StringBuilder builder, KeepTypePattern typePattern) { + if (typePattern.isAny()) { + return builder.append("*"); + } + throw new Unimplemented(); + } + + private static StringBuilder printAccess( + StringBuilder builder, String indent, KeepMethodAccessPattern accessPattern) { + if (accessPattern.isAny()) { + // No text will match any access pattern. + // Don't print the indent in this case. + return builder; + } + throw new Unimplemented(); + } + + private static StringBuilder printClassName( + StringBuilder builder, KeepQualifiedClassNamePattern classNamePattern) { + if (classNamePattern.isAny()) { + return builder.append('*'); + } + printPackagePrefix(builder, classNamePattern.getPackagePattern()); + return printSimpleClassName(builder, classNamePattern.getNamePattern()); + } + + private static StringBuilder printPackagePrefix( + StringBuilder builder, KeepPackagePattern packagePattern) { + if (packagePattern.isAny()) { + return builder.append("**."); + } + if (packagePattern.isTop()) { + return builder; + } + assert packagePattern.isExact(); + return builder.append(packagePattern.asExact().getExactPackageAsString()).append('.'); + } + + private static StringBuilder printSimpleClassName( + StringBuilder builder, KeepUnqualfiedClassNamePattern namePattern) { + if (namePattern.isAny()) { + return builder.append('*'); + } + assert namePattern.isExact(); + return builder.append(namePattern.asExact().getExactNameAsString()); + } + + private static String getOptionString(KeepOption option) { + switch (option) { + case SHRINKING: + return "shrinking"; + case OPTIMIZING: + return "optimization"; + case OBFUSCATING: + return "obfuscation"; + case ACCESS_MODIFING: + return "accessmodification"; + default: + throw new Unimplemented(); + } + } + + private static class ItemRule { + private final KeepTarget target; + private final KeepOptions options; + private String ruleLine = null; + + public ItemRule(KeepTarget target) { + this.target = target; + this.options = target.getOptions(); + } + + public boolean isMemberConsequent() { + return target.getItem().match(() -> false, clazz -> !clazz.getMembersPattern().isNone()); + } + + public KeepQualifiedClassNamePattern getHolderPattern() { + return target + .getItem() + .match(KeepQualifiedClassNamePattern::any, KeepClassPattern::getClassNamePattern); + } + + public String getKeepRuleForItem() { + if (ruleLine == null) { + ruleLine = + target + .getItem() + .match( + () -> "class * { *; }", + clazz -> printClassItem(new StringBuilder(), clazz).toString()); + } + return ruleLine; + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java index 85bc79c..cd7b5b9 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -132,13 +132,11 @@ BasicBlock nextBlock; public DexBuilder( - AppView<?> appView, IRCode ir, BytecodeMetadataProvider bytecodeMetadataProvider, RegisterAllocator registerAllocator, InternalOptions options) { this( - appView, ir, bytecodeMetadataProvider, registerAllocator, @@ -147,14 +145,13 @@ } public DexBuilder( - AppView<?> appView, IRCode ir, BytecodeMetadataProvider bytecodeMetadataProvider, RegisterAllocator registerAllocator, InternalOptions options, MethodConversionOptions conversionOptions) { assert ir == null || conversionOptions == ir.getConversionOptions(); - this.appView = appView; + this.appView = registerAllocator.getAppView(); this.ir = ir; this.bytecodeMetadataBuilder = BytecodeMetadata.builder(bytecodeMetadataProvider); this.registerAllocator = registerAllocator; @@ -173,7 +170,6 @@ DexBuilder builder = new DexBuilder( null, - null, BytecodeMetadataProvider.empty(), allocator, allocator.options(),
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java index cbd0d06..cf59a7e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
@@ -51,8 +51,7 @@ RuntimeWorkaroundCodeRewriter.workaroundExceptionTargetingLoopHeaderBug(code, options); // Perform register allocation. RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing); - return new DexBuilder(appView, code, bytecodeMetadataProvider, registerAllocator, options) - .build(); + return new DexBuilder(code, bytecodeMetadataProvider, registerAllocator, options).build(); } private RegisterAllocator performRegisterAllocation(
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 b1ea459..7f0fd6f 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
@@ -1155,12 +1155,17 @@ if (field == null) { return Reason.INVALID_FIELD_PUT; } - DexProgramClass dexClass = appView.programDefinitionFor(field.getHolderType(), code.context()); - if (dexClass == null) { + DexProgramClass holderClass = + appView.programDefinitionFor(field.getHolderType(), code.context()); + if (holderClass == null) { return Reason.INVALID_FIELD_PUT; } if (fieldPut.isInstancePut() && fieldPut.asInstancePut().object() == enumValue) { - return Reason.ELIGIBLE; + // TODO(b/249752942): The requirement to be inside an initializer of the enum can be relaxed + // if we support puts. + return context.getHolder() == enumClass && context.getDefinition().isInstanceInitializer() + ? Reason.ELIGIBLE + : Reason.ASSIGNMENT_OUTSIDE_INIT; } // The put value has to be of the field type. if (field.getReference().type.toBaseType(factory) != enumClass.type) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java index f2fbb4c..9a88b2e 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -221,6 +221,12 @@ int fallthroughBlockIndex = switchInsn.getFallthroughBlockIndex(); if (ordinalToTargetMap.size() < switchInsn.numberOfKeys()) { + if (block.numberOfNormalSuccessors() != switchInsn.numberOfKeys() + 1) { + // This can happen in extremely rare cases where several switch targets are the same + // block (See b/231804008). + // TODO(b/249052389): Support removing switch map for such switches. + continue; + } // There is at least one dead switch case. This can happen when some dependencies use // different versions of the same enum. int numberOfNormalSuccessors = switchInsn.numberOfKeys() + 1;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java index 63fc7fd..3e17a0a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -34,6 +34,7 @@ public static final Reason INVALID_ARRAY_PUT = new StringReason("INVALID_ARRAY_PUT"); public static final Reason TYPE_MISMATCH_FIELD_PUT = new StringReason("TYPE_MISMATCH_FIELD_PUT"); public static final Reason INVALID_IF_TYPES = new StringReason("INVALID_IF_TYPES"); + public static final Reason ASSIGNMENT_OUTSIDE_INIT = new StringReason("ASSIGNMENT_OUTSIDE_INIT"); public static final Reason ENUM_METHOD_CALLED_WITH_NULL_RECEIVER = new StringReason("ENUM_METHOD_CALLED_WITH_NULL_RECEIVER"); public static final Reason OTHER_UNSUPPORTED_INSTRUCTION =
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java index 90eb8a9..15c762b 100644 --- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java +++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -640,6 +640,11 @@ return appView.options(); } + @Override + public AppView<?> getAppView() { + return appView; + } + private ImmutableList<BasicBlock> computeLivenessInformation() { ImmutableList<BasicBlock> blocks = code.numberInstructions(); liveAtEntrySets = code.computeLiveAtEntrySets();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java index 28fe1f9..6c2221c 100644 --- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java +++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.regalloc; +import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.Value; @@ -19,6 +20,8 @@ InternalOptions options(); + AppView<?> getAppView(); + void mergeBlocks(BasicBlock kept, BasicBlock removed); boolean hasEqualTypesAtEntry(BasicBlock first, BasicBlock second);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java index 9ddddfe..100d61a 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -54,6 +54,7 @@ private final String inlineClassUnderlyingPropertyName; private final KotlinTypeInfo inlineClassUnderlyingType; private final int jvmFlags; + private final String companionObjectName; // List of tracked assignments of kotlin metadata. private final KotlinMetadataMembersTracker originalMembersWithKotlinInfo; @@ -78,7 +79,8 @@ String inlineClassUnderlyingPropertyName, KotlinTypeInfo inlineClassUnderlyingType, KotlinMetadataMembersTracker originalMembersWithKotlinInfo, - int jvmFlags) { + int jvmFlags, + String companionObjectName) { this.flags = flags; this.name = name; this.nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin = @@ -100,6 +102,7 @@ this.inlineClassUnderlyingType = inlineClassUnderlyingType; this.originalMembersWithKotlinInfo = originalMembersWithKotlinInfo; this.jvmFlags = jvmFlags; + this.companionObjectName = companionObjectName; } public static KotlinClassInfo create( @@ -154,7 +157,7 @@ keepByteCode, extensionInformation, originalMembersWithKotlinInfo); - setCompanionObject(kmClass, hostClass, reporter); + String companionObjectName = setCompanionObject(kmClass, hostClass, reporter); KotlinTypeReference anonymousObjectOrigin = getAnonymousObjectOrigin(kmClass, factory); boolean nameCanBeDeducedFromClassOrOrigin = kmClass.name.equals( @@ -183,7 +186,8 @@ kmClass.getInlineClassUnderlyingPropertyName(), KotlinTypeInfo.create(kmClass.getInlineClassUnderlyingType(), factory, reporter), originalMembersWithKotlinInfo, - JvmExtensionsKt.getJvmFlags(kmClass)); + JvmExtensionsKt.getJvmFlags(kmClass), + companionObjectName); } private static KotlinTypeReference getAnonymousObjectOrigin( @@ -228,19 +232,20 @@ return superTypeInfos.build(); } - private static void setCompanionObject(KmClass kmClass, DexClass hostClass, Reporter reporter) { + private static String setCompanionObject(KmClass kmClass, DexClass hostClass, Reporter reporter) { String companionObjectName = kmClass.getCompanionObject(); if (companionObjectName == null) { - return; + return companionObjectName; } for (DexEncodedField field : hostClass.fields()) { if (field.getReference().name.toString().equals(companionObjectName)) { field.setKotlinMemberInfo(new KotlinCompanionInfo(companionObjectName)); - return; + return companionObjectName; } } reporter.warning( KotlinMetadataDiagnostic.missingCompanionObject(hostClass, companionObjectName)); + return companionObjectName; } @Override @@ -284,6 +289,7 @@ rewritten |= !name.equals(rewrittenName); } // Find a companion object. + boolean foundCompanion = false; for (DexEncodedField field : clazz.fields()) { if (field.getKotlinInfo().isCompanion()) { rewritten |= @@ -291,8 +297,14 @@ .getKotlinInfo() .asCompanion() .rewrite(kmClass, field.getReference(), appView.getNamingLens()); + foundCompanion = true; } } + // If we did not find a companion but it was there on input we have to emit a new metadata + // object. + if (!foundCompanion && companionObjectName != null) { + rewritten = true; + } // Take all not backed constructors because we will never find them in definitions. for (KotlinConstructorInfo constructorInfo : constructorsWithNoBacking) { rewritten |= constructorInfo.rewrite(kmClass, null, appView);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java index 1b77e82..09c87e2 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -137,6 +137,7 @@ import com.android.tools.r8.shaking.EnqueuerWorklist.TraceStaticFieldWriteAction; import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness; import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection; +import com.android.tools.r8.shaking.KeepMethodInfo.Joiner; import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet; import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder; import com.android.tools.r8.shaking.RootSetUtils.RootSet; @@ -943,9 +944,19 @@ if (clazz.hasDefaultInitializer()) { ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer(); if (forceProguardCompatibility) { - workList.enqueueMarkMethodKeptAction( - defaultInitializer, - graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer)); + Joiner joiner = KeepMethodInfo.newEmptyJoiner(); + for (ProguardKeepRuleBase rule : rules) { + if (!rule.getType().equals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS)) { + joiner.addRule(rule); + } + } + if (!joiner.getRules().isEmpty()) { + workList.enqueueMarkMethodKeptAction( + defaultInitializer, + graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer)); + applyMinimumKeepInfoWhenLiveOrTargeted( + defaultInitializer, joiner.disallowOptimization()); + } } if (clazz.isExternalizable(appView)) { workList.enqueueMarkMethodLiveAction(defaultInitializer, defaultInitializer, witness); @@ -2049,7 +2060,8 @@ || appView.appInfo().getMainDexInfo().isTracedRoot(clazz, appView.getSyntheticItems()) : "Class " + clazz.toSourceString() + " was not a main dex root in the first round"; - assert !appView.unboxedEnums().isUnboxedEnum(clazz); + assert !appView.unboxedEnums().isUnboxedEnum(clazz) + : "Enum " + clazz.toSourceString() + " has been unboxed but is still in the program."; if (options.isGeneratingClassFiles() && clazz.hasPermittedSubclassAttributes()) { throw new CompilationError(
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 f07a328..6bf0a8f 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1988,8 +1988,7 @@ public boolean allowInvokeErrors = false; public boolean allowUnnecessaryDontWarnWildcards = true; public boolean allowUnusedDontWarnRules = true; - public boolean reportUnusedProguardConfigurationRules = - System.getProperty("com.android.tools.r8.reportUnusedProguardConfigurationRules") != null; + public boolean reportUnusedProguardConfigurationRules = true; public boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding = true; public boolean alwaysUsePessimisticRegisterAllocation = false; public boolean enableCheckCastAndInstanceOfRemoval = true;
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java index 4a22769..7d2ea31 100644 --- a/src/test/java/com/android/tools/r8/L8TestBuilder.java +++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -6,8 +6,10 @@ import static junit.framework.Assert.assertNull; import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.fail; import com.android.tools.r8.TestBase.Backend; +import com.android.tools.r8.errors.UnusedProguardKeepRuleDiagnostic; import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification; import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser; import com.android.tools.r8.origin.Origin; @@ -249,14 +251,28 @@ || warnings.stream() .allMatch(warn -> warn.getDiagnosticMessage().contains("org.testng.Assert"))); List<Diagnostic> infos = diagnosticsMessages.getInfos(); - // The rewriting confuses the generic signatures in some methods. Such signatures are never - // used by tools (they use the non library desugared version) and are stripped when compiling - // with R8 anyway. - // TODO(b/243483320): Investigate the Invalid signature. - assertTrue( - infos.isEmpty() - || infos.stream() - .allMatch(info -> info.getDiagnosticMessage().contains("Invalid signature "))); + for (Diagnostic info : infos) { + // The rewriting confuses the generic signatures in some methods. Such signatures are never + // used by tools (they use the non library desugared version) and are stripped when compiling + // with R8 anyway. + if (info instanceof UnusedProguardKeepRuleDiagnostic) { + // The default keep rules on desugared library may be unused. They should all be defined + // with keepclassmembers. + if (info.getDiagnosticMessage().contains("keepclassmembers")) { + continue; + } + // We allow info regarding the extended version of desugared library for JDK11 testing. + if (info.getDiagnosticMessage().contains("org.testng.")) { + continue; + } + fail("Unexpected unused proguard keep rule diagnostic: " + info.getDiagnosticMessage()); + } + // TODO(b/243483320): Investigate the Invalid signature. + if (info.getDiagnosticMessage().contains("Invalid signature ")) { + continue; + } + fail("Unexpected info diagnostic: " + info.getDiagnosticMessage()); + } } private L8Command.Builder addProgramClassFileData(L8Command.Builder builder) {
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java index 73dbba2..fd3e16d 100644 --- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java +++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest; import com.android.tools.r8.compilerapi.classconflictresolver.ClassConflictResolverTest; import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest; +import com.android.tools.r8.compilerapi.diagnostics.ProguardKeepRuleDiagnosticsApiTest; import com.android.tools.r8.compilerapi.diagnostics.UnsupportedFeaturesDiagnosticApiTest; import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest; import com.android.tools.r8.compilerapi.inputdependencies.InputDependenciesTest; @@ -56,7 +57,8 @@ ImmutableList.of( ArtProfilesForRewritingApiTest.ApiTest.class, StartupProfileApiTest.ApiTest.class, - ClassConflictResolverTest.ApiTest.class); + ClassConflictResolverTest.ApiTest.class, + ProguardKeepRuleDiagnosticsApiTest.ApiTest.class); private final TemporaryFolder temp;
diff --git a/src/test/java/com/android/tools/r8/compilerapi/diagnostics/ProguardKeepRuleDiagnosticsApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/ProguardKeepRuleDiagnosticsApiTest.java new file mode 100644 index 0000000..fedb449 --- /dev/null +++ b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/ProguardKeepRuleDiagnosticsApiTest.java
@@ -0,0 +1,117 @@ +// Copyright (c) 2022, 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.compilerapi.diagnostics; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.containsString; + +import com.android.tools.r8.DexIndexedConsumer; +import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.DiagnosticsLevel; +import com.android.tools.r8.R8; +import com.android.tools.r8.R8Command; +import com.android.tools.r8.TestDiagnosticMessagesImpl; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.compilerapi.CompilerApiTest; +import com.android.tools.r8.compilerapi.CompilerApiTestRunner; +import com.android.tools.r8.errors.ProguardKeepRuleDiagnostic; +import com.android.tools.r8.errors.UnusedProguardKeepRuleDiagnostic; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.ThrowingConsumer; +import java.util.Collections; +import org.junit.Test; + +public class ProguardKeepRuleDiagnosticsApiTest extends CompilerApiTestRunner { + + public ProguardKeepRuleDiagnosticsApiTest(TestParameters parameters) { + super(parameters); + } + + @Override + public Class<? extends CompilerApiTest> binaryTestClass() { + return ApiTest.class; + } + + @Test + public void testR8() throws Exception { + ApiTest test = new ApiTest(ApiTest.PARAMETERS); + runTest(test::runR8); + } + + private void runTest(ThrowingConsumer<DiagnosticsHandler, Exception> test) throws Exception { + TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl(); + test.accept(diagnostics); + diagnostics + .assertOnlyWarnings() + // Check the diagnostic is an instance of both types. + .assertWarningsMatch( + allOf( + diagnosticType(UnusedProguardKeepRuleDiagnostic.class), + diagnosticMessage(containsString("does not match anything")))) + .assertWarningsMatch( + allOf( + diagnosticType(ProguardKeepRuleDiagnostic.class), + diagnosticMessage(containsString("does not match anything")))); + } + + public static class ApiTest extends CompilerApiTest { + + public ApiTest(Object parameters) { + super(parameters); + } + + public void runR8(DiagnosticsHandler handler) throws Exception { + R8.run( + R8Command.builder( + new DiagnosticsHandler() { + @Override + public DiagnosticsLevel modifyDiagnosticsLevel( + DiagnosticsLevel level, Diagnostic diagnostic) { + if (diagnostic instanceof ProguardKeepRuleDiagnostic + || diagnostic instanceof UnusedProguardKeepRuleDiagnostic) { + return handler.modifyDiagnosticsLevel(DiagnosticsLevel.WARNING, diagnostic); + } + return handler.modifyDiagnosticsLevel(level, diagnostic); + } + + @Override + public void error(Diagnostic error) { + handler.error(error); + } + + @Override + public void warning(Diagnostic warning) { + handler.warning(warning); + } + + @Override + public void info(Diagnostic info) { + handler.info(info); + } + }) + .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown()) + .addProguardConfiguration(getKeepMainRules(getMockClass()), Origin.unknown()) + .addProguardConfiguration( + Collections.singletonList("-keep class NotPresent {}"), Origin.unknown()) + .addLibraryFiles(getJava8RuntimeJar()) + .setProgramConsumer(DexIndexedConsumer.emptyConsumer()) + .setMinApiLevel(1) + .build()); + } + + @Test + public void testR8() throws Exception { + runR8( + new DiagnosticsHandler() { + @Override + public void warning(Diagnostic warning) { + // ignore the warnings. + } + }); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java index 2bd0f31..40942f7 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java
@@ -24,14 +24,32 @@ @RunWith(Parameterized.class) public class DesugaredLibraryWarningTest extends DesugaredLibraryTestBase { - private static final String FUNCTION_KEEP = - "-keep class j$.util.function.Function$-CC {\n" - + " j$.util.function.Function $default$compose(j$.util.function.Function," - + " j$.util.function.Function);\n" - + " j$.util.function.Function $default$andThen(j$.util.function.Function," - + " j$.util.function.Function);\n" - + "}\n" - + "-keep class j$.util.function.Function { *; }"; + private static final String getFunctionKeep(String prefix) { + return "-keep class j$.util.function.Function$-CC {\n" + + " " + + prefix + + ".util.function.Function $default$compose(" + + prefix + + ".util.function.Function," + + " " + + prefix + + ".util.function.Function);\n" + + " " + + prefix + + ".util.function.Function $default$andThen(" + + prefix + + ".util.function.Function," + + " " + + prefix + + ".util.function.Function);\n" + + "}\n" + + "-keep class " + + prefix + + ".util.function.Function { *; }"; + } + + private static final String FUNCTION_KEEP_J$ = getFunctionKeep("j$"); + private static final String FUNCTION_KEEP_JAVA = getFunctionKeep("java"); private final TestParameters parameters; private final CompilationSpecification compilationSpecification; @@ -60,18 +78,24 @@ .apply( l8TestBuilder -> libraryDesugaringSpecification.configureL8TestBuilder( - l8TestBuilder, compilationSpecification.isL8Shrink(), FUNCTION_KEEP)) + l8TestBuilder, + compilationSpecification.isL8Shrink(), + libraryDesugaringSpecification.hasEmulatedInterfaceDesugaring(parameters) + ? libraryDesugaringSpecification.hasJDollarFunction(parameters) + ? FUNCTION_KEEP_J$ + : FUNCTION_KEEP_JAVA + : "")) .compile() .inspectDiagnosticMessages( diagnosticsHandler -> { + diagnosticsHandler.assertNoErrors(); if (libraryDesugaringSpecification != JDK8) { - diagnosticsHandler.assertNoErrors(); diagnosticsHandler.assertAllWarningsMatch( diagnosticMessage(containsString("Specification conversion"))); } else { - - diagnosticsHandler.assertNoMessages(); + diagnosticsHandler.assertNoWarnings(); } + diagnosticsHandler.assertNoInfos(); }); } }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java index 542c71e..058f6cf 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java
@@ -61,9 +61,6 @@ .anyMatch( kr -> kr.contains("-keep class " + prefix + ".util.function.Consumer"))); - // TODO(b/158635415): Don't generate keep rules targeting items outside desugared - // library. - assertTrue(keepRule.stream().anyMatch(kr -> kr.contains("-keep class java.util"))); } }); }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java index 26e16bc..104876d 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java
@@ -66,8 +66,11 @@ @Test public void test() throws Throwable { testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification) - .addL8KeepRules("-keepnames class j$.desugar.sun.nio.fs.**") - .addL8KeepRules("-keepnames class j$.nio.file.FileSystem**") + .applyIf( + libraryDesugaringSpecification.hasNioFileDesugaring(parameters), + b -> + b.addL8KeepRules("-keepnames class j$.desugar.sun.nio.fs.**") + .addL8KeepRules("-keepnames class j$.nio.file.FileSystem**")) .addInnerClasses(PathTest.class) .addKeepMainRule(TestClass.class) .compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInEnumFieldTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInEnumFieldTest.java new file mode 100644 index 0000000..d2fe030 --- /dev/null +++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInEnumFieldTest.java
@@ -0,0 +1,86 @@ +// Copyright (c) 2022, 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.enumunboxing; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.TestParameters; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** This is a regression for b/247146910. */ +@RunWith(Parameterized.class) +public class EnumInEnumFieldTest extends EnumUnboxingTestBase { + + private final TestParameters parameters; + private final boolean enumValueOptimization; + private final EnumKeepRules enumKeepRules; + + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { + return enumUnboxingTestParameters(); + } + + public EnumInEnumFieldTest( + TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) { + this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addInnerClasses(EnumInEnumFieldTest.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("0"); + } + + @Test + public void testEnumUnboxing() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(EnumInEnumFieldTest.class) + .addKeepMainRule(Main.class) + .addKeepRules(enumKeepRules.getKeepRules()) + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) + .addEnumUnboxingInspector( + inspector -> { + inspector.assertNotUnboxed(MyEnum.class); + inspector.assertUnboxed(OtherEnum.class); + }) + .addNeverClassInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("0"); + } + + @NeverClassInline + public enum OtherEnum { + C, + D; + } + + @NeverClassInline + public enum MyEnum { + A, + B; + + public OtherEnum otherEnum; + } + + public static class Main { + + public static void main(String[] args) throws Exception { + set(System.currentTimeMillis() > 0 ? OtherEnum.C : OtherEnum.D); + System.out.println(MyEnum.A.otherEnum.ordinal()); + } + + public static void set(OtherEnum otherEnum) { + MyEnum.A.otherEnum = otherEnum; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceInsideAndOutsideTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceInsideAndOutsideTest.java new file mode 100644 index 0000000..55cdede --- /dev/null +++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceInsideAndOutsideTest.java
@@ -0,0 +1,83 @@ +// Copyright (c) 2022, 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.enumunboxing; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.TestParameters; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * This is a variant of a regression test for b/247146910 where the instance field on the enum is + * unrelated to enum unboxing. + */ +@RunWith(Parameterized.class) +public class EnumInstanceFieldReferenceInsideAndOutsideTest extends EnumUnboxingTestBase { + + private final TestParameters parameters; + private final boolean enumValueOptimization; + private final EnumKeepRules enumKeepRules; + + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { + return enumUnboxingTestParameters(); + } + + public EnumInstanceFieldReferenceInsideAndOutsideTest( + TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) { + this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addInnerClasses(EnumInstanceFieldReferenceInsideAndOutsideTest.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("42"); + } + + @Test + public void testEnumUnboxingAllowNotPrunedEnums() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(EnumInstanceFieldReferenceInsideAndOutsideTest.class) + .addKeepMainRule(Main.class) + .addKeepRules(enumKeepRules.getKeepRules()) + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) + .addNeverClassInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(MyEnum.class)) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("42"); + } + + @NeverClassInline + public enum MyEnum { + A(10), + B(20); + + public int instanceValue; + + MyEnum(int instanceValue) { + this.instanceValue = instanceValue; + } + } + + public static class Main { + + public static void main(String[] args) throws Exception { + set(System.currentTimeMillis() > 0 ? 42 : 0); + System.out.println(MyEnum.B.instanceValue); + } + + public static void set(int newValue) { + MyEnum.B.instanceValue = newValue; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceOutsideTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceOutsideTest.java new file mode 100644 index 0000000..4dac1af --- /dev/null +++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceOutsideTest.java
@@ -0,0 +1,79 @@ +// Copyright (c) 2022, 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.enumunboxing; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.TestParameters; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * This is a variant of a regression test for b/247146910 where the instance field on the enum is + * unrelated to enum unboxing. + */ +@RunWith(Parameterized.class) +public class EnumInstanceFieldReferenceOutsideTest extends EnumUnboxingTestBase { + + private final TestParameters parameters; + private final boolean enumValueOptimization; + private final EnumKeepRules enumKeepRules; + + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { + return enumUnboxingTestParameters(); + } + + public EnumInstanceFieldReferenceOutsideTest( + TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) { + this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addInnerClasses(EnumInstanceFieldReferenceOutsideTest.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("42"); + } + + @Test + public void testEnumUnboxingAllowNotPrunedEnums() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(EnumInstanceFieldReferenceOutsideTest.class) + .addKeepMainRule(Main.class) + .addKeepRules(enumKeepRules.getKeepRules()) + .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(MyEnum.class)) + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) + .addNeverClassInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("42"); + } + + @NeverClassInline + public enum MyEnum { + A, + B; + + public int instanceValue = 1; + } + + public static class Main { + + public static void main(String[] args) throws Exception { + set(System.currentTimeMillis() > 0 ? 42 : 0); + System.out.println(MyEnum.B.instanceValue); + } + + public static void set(int newValue) { + MyEnum.B.instanceValue = newValue; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java b/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java new file mode 100644 index 0000000..d69d055 --- /dev/null +++ b/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java
@@ -0,0 +1,191 @@ +// Copyright (c) 2022, 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.experimental.keepanno.ast; + +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.experimental.keepanno.ast.KeepOptions.KeepOption; +import com.android.tools.r8.experimental.keepanno.keeprules.KeepRuleExtractor; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class KeepEdgeApiTest extends TestBase { + + private static String CLASS = "com.example.Foo"; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public KeepEdgeApiTest(TestParameters parameters) { + parameters.assertNoneRuntime(); + } + + public static String extract(KeepEdge edge) { + StringBuilder builder = new StringBuilder(); + KeepRuleExtractor extractor = new KeepRuleExtractor(rule -> builder.append(rule).append('\n')); + extractor.extract(edge); + return builder.toString(); + } + + @Test + public void testKeepAll() { + KeepEdge edge = + KeepEdge.builder() + .setConsequences( + KeepConsequences.builder() + .addTarget(KeepTarget.builder().setItem(KeepItemPattern.any()).build()) + .build()) + .build(); + assertEquals(StringUtils.unixLines("-keep class * { *; }"), extract(edge)); + } + + @Test + public void testSoftPinViaDisallow() { + KeepEdge edge = + KeepEdge.builder() + .setConsequences( + KeepConsequences.builder() + .addTarget( + KeepTarget.builder() + .setItem(KeepItemPattern.any()) + .setOptions(KeepOptions.disallow(KeepOption.OPTIMIZING)) + .build()) + .build()) + .build(); + // Disallow will issue the full inverse of the known options, e.g., 'allowaccessmodification'. + assertEquals( + StringUtils.unixLines( + "-keep,allowshrinking,allowobfuscation,allowaccessmodification class * { *; }"), + extract(edge)); + } + + @Test + public void testSoftPinViaAllow() { + KeepEdge edge = + KeepEdge.builder() + .setConsequences( + KeepConsequences.builder() + .addTarget( + KeepTarget.builder() + .setItem(KeepItemPattern.any()) + .setOptions( + KeepOptions.allow(KeepOption.OBFUSCATING, KeepOption.SHRINKING)) + .build()) + .build()) + .build(); + // Allow is just the ordered list of options. + assertEquals( + StringUtils.unixLines("-keep,allowshrinking,allowobfuscation class * { *; }"), + extract(edge)); + } + + @Test + public void testKeepClass() throws Exception { + KeepTarget target = target(classItem(CLASS)); + KeepConsequences consequences = KeepConsequences.builder().addTarget(target).build(); + KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build(); + assertEquals(StringUtils.unixLines("-keep class " + CLASS), extract(edge)); + } + + @Test + public void testKeepInitIfReferenced() throws Exception { + KeepEdge edge = + KeepEdge.builder() + .setPreconditions( + KeepPreconditions.builder() + .addCondition( + KeepCondition.builder() + .setItem(classItem(CLASS)) + .build()) + .build()) + .setConsequences( + KeepConsequences.builder() + .addTarget( + target( + buildClassItem(CLASS) + .setMembersPattern(defaultInitializerPattern()) + .build())) + .build()) + .build(); + assertEquals( + StringUtils.unixLines("-keepclassmembers class " + CLASS + " { void <init>(); }"), + extract(edge)); + } + + @Test + public void testKeepInstanceIfReferenced() throws Exception { + KeepEdge edge = + KeepEdge.builder() + .setPreconditions( + KeepPreconditions.builder() + .addCondition( + KeepCondition.builder() + .setItem(classItem(CLASS)) + .build()) + .build()) + .setConsequences(KeepConsequences.builder().addTarget(target(classItem(CLASS))).build()) + .build(); + assertEquals( + StringUtils.unixLines("-if class " + CLASS + " -keep class " + CLASS), extract(edge)); + } + + @Test + public void testKeepInstanceAndInitIfReferenced() throws Exception { + KeepEdge edge = + KeepEdge.builder() + .setPreconditions( + KeepPreconditions.builder() + .addCondition( + KeepCondition.builder() + .setItem(classItem(CLASS)) + .build()) + .build()) + .setConsequences( + KeepConsequences.builder() + .addTarget(target(classItem(CLASS))) + .addTarget( + target( + buildClassItem(CLASS) + .setMembersPattern(defaultInitializerPattern()) + .build())) + .build()) + .build(); + assertEquals( + StringUtils.unixLines( + "-if class " + CLASS + " -keep class " + CLASS, + "-keepclassmembers class " + CLASS + " { void <init>(); }"), + extract(edge)); + } + + private KeepTarget target(KeepItemPattern item) { + return KeepTarget.builder().setItem(item).build(); + } + + private KeepItemPattern classItem(String typeName) { + return buildClassItem(typeName).build(); + } + + private KeepItemPattern.Builder buildClassItem(String typeName) { + return KeepItemPattern.builder().setClassPattern(KeepQualifiedClassNamePattern.exact(typeName)); + } + + private KeepMembersPattern defaultInitializerPattern() { + return KeepMembersPattern.builder() + .addMethodPattern( + KeepMethodPattern.builder() + .setNamePattern(KeepMethodNamePattern.initializer()) + .setParametersPattern(KeepMethodParametersPattern.none()) + .setReturnTypeVoid() + .build()) + .build(); + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java index c8760d8..5db89dc 100644 --- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java +++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -6,6 +6,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; @@ -78,6 +79,11 @@ } @Override + public AppView<?> getAppView() { + return null; + } + + @Override public void mergeBlocks(BasicBlock kept, BasicBlock removed) { // Intentionally empty, we don't need to track merging in this allocator. }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRemovedCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRemovedCompanionTest.java new file mode 100644 index 0000000..145cb4b --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRemovedCompanionTest.java
@@ -0,0 +1,116 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.kotlin.metadata; + +import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20; +import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.MIN_SUPPORTED_VERSION; + +import com.android.tools.r8.KotlinTestParameters; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.StringUtils; +import java.nio.file.Path; +import java.util.Collection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class MetadataRewriteRemovedCompanionTest extends KotlinMetadataTestBase { + private static final String EXPECTED = StringUtils.lines("Hello World!"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}, {1}") + public static Collection<Object[]> data() { + return buildParameters( + getTestParameters().withCfRuntimes().build(), + getKotlinTestParameters() + .withOldCompilersStartingFrom(KOTLINC_1_4_20) + .withCompilersStartingFromIncluding(MIN_SUPPORTED_VERSION) + .withAllTargetVersions() + .build()); + } + + public MetadataRewriteRemovedCompanionTest( + TestParameters parameters, KotlinTestParameters kotlinParameters) { + super(kotlinParameters); + this.parameters = parameters; + } + + private static final KotlinCompileMemoizer companionRemoveJarMap = + getCompileMemoizer(getKotlinFileInTest(PKG_PREFIX + "/companion_remove_lib", "lib")); + + @Test + public void smokeTest() throws Exception { + Path libJar = companionRemoveJarMap.getForConfiguration(kotlinc, targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_remove_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".companion_remove_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testMetadataInCompanion_kept() throws Exception { + Path libJar = + testForR8(parameters.getBackend()) + .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar()) + .addProgramFiles(companionRemoveJarMap.getForConfiguration(kotlinc, targetVersion)) + // Keep everything + .addKeepRules("-keep class **.companion_remove_lib.** { *; }") + .addKeepKotlinMetadata() + // To keep ...$Companion structure + .addKeepAttributeInnerClassesAndEnclosingMethod() + .compile() + .writeToZip(); + + Path output = + kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_remove_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".companion_remove_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testMetadataInCompanion_removedField() throws Exception { + Path libJar = + testForR8(parameters.getBackend()) + .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar()) + .addProgramFiles(companionRemoveJarMap.getForConfiguration(kotlinc, targetVersion)) + // Keep the ClasWithCompanion class. + .addKeepKotlinMetadata() + .addKeepRules("-keep class **.ClassWithCompanion { void <init>(); void doStuff(); }") + .addKeepRules("-keep class **.ClassWithCompanion$Companion") + .compile() + .writeToZip(); + + Path output = + kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_remove_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".companion_remove_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_app/main.kt new file mode 100644 index 0000000..d08a2d4 --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_app/main.kt
@@ -0,0 +1,10 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.kotlin.metadata.companion_remove_app + +import com.android.tools.r8.kotlin.metadata.companion_remove_lib.ClassWithCompanion + +fun main() { + ClassWithCompanion().doStuff() +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_lib/lib.kt new file mode 100644 index 0000000..a16595e --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_lib/lib.kt
@@ -0,0 +1,14 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.kotlin.metadata.companion_remove_lib + +class ClassWithCompanion { + fun doStuff() { + println(foo) + } + companion object { + val foo: String + get() = "Hello World!" + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassMembersDefaultCtorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassMembersDefaultCtorTest.java new file mode 100644 index 0000000..2e726b0 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassMembersDefaultCtorTest.java
@@ -0,0 +1,111 @@ +// Copyright (c) 2022, 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.shaking.forceproguardcompatibility.defaultctor; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.ProguardVersion; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestRunResult; +import com.android.tools.r8.TestShrinkerBuilder; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class KeepClassMembersDefaultCtorTest extends TestBase { + + static final String EXPECTED = StringUtils.lines("A()"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultCfRuntime().build(); + } + + public KeepClassMembersDefaultCtorTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testReference() throws Exception { + testForRuntime(parameters) + .addInnerClasses(KeepClassMembersDefaultCtorTest.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testCompatR8() throws Exception { + run(testForR8Compat(parameters.getBackend()), false); + } + + @Test + public void testR8() throws Exception { + run(testForR8(parameters.getBackend()), true); + } + + @Test + public void testPG() throws Exception { + run(testForProguard(ProguardVersion.getLatest()).addDontWarn(getClass()), false); + } + + private TestRunResult<?> run(TestShrinkerBuilder<?, ?, ?, ?, ?> builder, boolean fullMode) + throws Exception { + return builder + .addInnerClasses(KeepClassMembersDefaultCtorTest.class) + .addKeepRules("-keepclassmembers class * { <fields>; }") + .addKeepClassAndMembersRules(TestClass.class) + .addDontObfuscate() + .run(parameters.getRuntime(), TestClass.class) + .inspectFailure( + inspector -> { + assertThat(inspector.clazz(A.class), isPresent()); + assertThat(inspector.clazz(A.class).init(), isAbsent()); + assertThat(inspector.clazz(A.class).uniqueFieldWithName("y"), isPresent()); + assertThat( + inspector.clazz(A.class).uniqueFieldWithName("x"), + fullMode ? isAbsent() : isPresent()); + }) + .assertFailureWithErrorThatThrows(NoSuchMethodException.class); + } + + static class A { + + public long x = System.nanoTime(); + public static long y = System.nanoTime(); + + public A() { + System.out.println("A()"); + } + } + + static class TestClass { + + public static A getA() { + // Since TestClass.A is hard kept, the shrinker can't assume anything about the return value. + return null; + } + + public static void main(String[] args) throws Exception { + String name = args.length == 0 ? "A" : null; + Class<?> clazz = + Class.forName( + TestClass.class.getPackage().getName() + ".KeepClassMembersDefaultCtorTest$" + name); + Object obj = clazz.getConstructor().newInstance(); + if (args.length > 0) { + // Use the field so we are sure that the keep rule triggers. + A a = getA(); + System.out.println(a.y); + System.out.println(a.x); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassesWithMembersDefaultCtorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassesWithMembersDefaultCtorTest.java new file mode 100644 index 0000000..54ee56f --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassesWithMembersDefaultCtorTest.java
@@ -0,0 +1,112 @@ +// Copyright (c) 2022, 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.shaking.forceproguardcompatibility.defaultctor; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.ProguardVersion; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestRunResult; +import com.android.tools.r8.TestShrinkerBuilder; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class KeepClassesWithMembersDefaultCtorTest extends TestBase { + + static final String EXPECTED = StringUtils.lines("A()"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultCfRuntime().build(); + } + + public KeepClassesWithMembersDefaultCtorTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testReference() throws Exception { + testForRuntime(parameters) + .addInnerClasses(KeepClassesWithMembersDefaultCtorTest.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testCompatR8() throws Exception { + checkInitKept(run(testForR8Compat(parameters.getBackend()))); + } + + @Test + public void testR8() throws Exception { + checkInitNotKept(run(testForR8(parameters.getBackend()))); + } + + @Test + public void testPG() throws Exception { + checkInitKept(run(testForProguard(ProguardVersion.getLatest()).addDontWarn(getClass()))); + } + + private TestRunResult<?> run(TestShrinkerBuilder<?, ?, ?, ?, ?> builder) throws Exception { + return builder + .addInnerClasses(KeepClassesWithMembersDefaultCtorTest.class) + .addKeepRules("-keepclasseswithmembers class * { <fields>; }") + .addKeepClassAndMembersRules(TestClass.class) + .addDontObfuscate() + .run(parameters.getRuntime(), TestClass.class); + } + + private TestRunResult<?> checkInitKept(TestRunResult<?> result) throws Exception { + return result + .inspect(inspector -> assertThat(inspector.clazz(A.class).init(), isPresent())) + .assertSuccessWithOutput(EXPECTED); + } + + private TestRunResult<?> checkInitNotKept(TestRunResult<?> result) throws Exception { + return result + .inspectFailure(inspector -> assertThat(inspector.clazz(A.class).init(), isAbsent())) + .assertFailureWithErrorThatThrows(NoSuchMethodException.class); + } + + static class A { + + public long x = System.nanoTime(); + + public A() { + System.out.println("A()"); + } + } + + static class TestClass { + + public static A getA() { + // Since TestClass.A is hard kept, the shrinker can't assume anything about the return value. + return null; + } + + public static void main(String[] args) throws Exception { + String name = args.length == 0 ? "A" : null; + Class<?> clazz = + Class.forName( + TestClass.class.getPackage().getName() + + ".KeepClassesWithMembersDefaultCtorTest$" + + name); + Object obj = clazz.getConstructor().newInstance(); + if (args.length > 0) { + // Use the field so we are sure that the keep rule triggers. + A a = getA(); + System.out.println(a.x); + } + } + } +}
diff --git a/third_party/opensource-apps/systemui.tar.gz.sha1 b/third_party/opensource-apps/systemui.tar.gz.sha1 index 9d5b06c..4300260 100644 --- a/third_party/opensource-apps/systemui.tar.gz.sha1 +++ b/third_party/opensource-apps/systemui.tar.gz.sha1
@@ -1 +1 @@ -19afeb80f13ccaadddd62d9e1f8f2b1e894f99c8 \ No newline at end of file +b5b6321177b6f39ed446c5d408d570dc1598f32c \ No newline at end of file