Merge "Add Proguard testing rules for controlling inlining"
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 9fcca89..584b06d 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -68,6 +68,7 @@
Path proguardCompatibilityRulesOutput = null;
private boolean allowPartiallyImplementedProguardOptions = false;
+ private boolean allowTestProguardOptions = false;
private StringConsumer mainDexListConsumer = null;
@@ -290,7 +291,8 @@
}
ProguardConfigurationParser parser = new ProguardConfigurationParser(
- factory, reporter, !allowPartiallyImplementedProguardOptions);
+ factory, reporter,
+ !allowPartiallyImplementedProguardOptions, allowTestProguardOptions);
if (!proguardConfigs.isEmpty()) {
parser.parse(proguardConfigs);
}
@@ -393,6 +395,11 @@
void allowPartiallyImplementedProguardOptions() {
allowPartiallyImplementedProguardOptions = true;
}
+
+ // Internal for-testing method to allow proguard options only available for testing.
+ void allowTestProguardOptions() {
+ allowTestProguardOptions = true;
+ }
}
// Wrapper class to ensure that R8 does not allow DEX as program inputs.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index cc7f571..631f234 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -91,7 +91,9 @@
}
private Reason computeInliningReason(DexEncodedMethod target) {
- if (target.getOptimizationInfo().forceInline()) {
+ if (target.getOptimizationInfo().forceInline()
+ || (inliner.appInfo.hasLiveness()
+ && inliner.appInfo.withLiveness().forceInline.contains(target))) {
return Reason.FORCE;
}
if (inliner.appInfo.hasLiveness()
@@ -252,7 +254,7 @@
public InlineAction computeForInvokeWithReceiver(
InvokeMethodWithReceiver invoke, DexType invocationContext) {
DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
- if (candidate == null || inliner.isBlackListed(candidate.method)) {
+ if (candidate == null || inliner.isBlackListed(candidate)) {
return null;
}
@@ -268,6 +270,7 @@
if (info != null) {
info.exclude(invoke, "receiver for candidate can be null");
}
+ assert !inliner.appInfo.forceInline.contains(candidate.method);
return null;
}
@@ -293,7 +296,7 @@
@Override
public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
- if (candidate == null || inliner.isBlackListed(candidate.method)) {
+ if (candidate == null || inliner.isBlackListed(candidate)) {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 44ebdc7..35e9799 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -70,8 +70,8 @@
blackList.add(appInfo.dexItemFactory.kotlin.intrinsics.throwNpe);
}
- public boolean isBlackListed(DexMethod method) {
- return blackList.contains(method);
+ public boolean isBlackListed(DexEncodedMethod method) {
+ return blackList.contains(method.method) || appInfo.neverInline.contains(method);
}
private Constraint instructionAllowedForInlining(
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 baacb1e..a1cc440 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1544,10 +1544,18 @@
*/
public final Map<DexItem, ProguardMemberRule> assumedValues;
/**
- * All methods that have to be inlined due to a configuration directive.
+ * All methods that should be inlined if possible due to a configuration directive.
*/
public final Set<DexItem> alwaysInline;
/**
+ * All methods that *must* be inlined due to a configuration directive (testing only).
+ */
+ public final Set<DexItem> forceInline;
+ /**
+ * All methods that *must* never be inlined due to a configuration directive (testing only).
+ */
+ public final Set<DexItem> neverInline;
+ /**
* All items with -identifiernamestring rule.
*/
public final Set<DexItem> identifierNameStrings;
@@ -1602,6 +1610,8 @@
this.noSideEffects = enqueuer.rootSet.noSideEffects;
this.assumedValues = enqueuer.rootSet.assumedValues;
this.alwaysInline = enqueuer.rootSet.alwaysInline;
+ this.forceInline = enqueuer.rootSet.forceInline;
+ this.neverInline = enqueuer.rootSet.neverInline;
this.identifierNameStrings =
Sets.union(enqueuer.rootSet.identifierNameStrings, enqueuer.identifierNameStrings);
this.protoLiteFields = enqueuer.protoLiteFields;
@@ -1640,6 +1650,8 @@
this.brokenSuperInvokes = previous.brokenSuperInvokes;
this.protoLiteFields = previous.protoLiteFields;
this.alwaysInline = previous.alwaysInline;
+ this.forceInline = previous.forceInline;
+ this.neverInline = previous.neverInline;
this.identifierNameStrings = previous.identifierNameStrings;
this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
this.switchMaps = previous.switchMaps;
@@ -1683,6 +1695,8 @@
this.assumedValues = previous.assumedValues;
assert lense.assertNotModified(previous.alwaysInline);
this.alwaysInline = previous.alwaysInline;
+ this.forceInline = previous.forceInline;
+ this.neverInline = previous.neverInline;
this.identifierNameStrings =
rewriteMixedItemsConservatively(previous.identifierNameStrings, lense);
// Switchmap classes should never be affected by renaming.
@@ -1724,6 +1738,8 @@
this.brokenSuperInvokes = previous.brokenSuperInvokes;
this.protoLiteFields = previous.protoLiteFields;
this.alwaysInline = previous.alwaysInline;
+ this.forceInline = previous.forceInline;
+ this.neverInline = previous.neverInline;
this.identifierNameStrings = previous.identifierNameStrings;
this.prunedTypes = previous.prunedTypes;
this.switchMaps = switchMaps;
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
new file mode 100644
index 0000000..565856c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2017, 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;
+
+import com.android.tools.r8.errors.Unreachable;
+import java.util.List;
+
+public class InlineRule extends ProguardConfigurationRule {
+
+ public enum Type {
+ ALWAYS, FORCE, NEVER
+ }
+
+ public static class Builder extends ProguardConfigurationRule.Builder {
+
+ private Builder() {
+ }
+
+ Type type;
+
+ public Builder setType(Type type) {
+ this.type = type;
+ return this;
+ }
+
+ public InlineRule build() {
+ return new InlineRule(classAnnotation, classAccessFlags,
+ negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+ inheritanceClassName, inheritanceIsExtends, memberRules, type);
+ }
+ }
+
+ private final Type type;
+
+ private InlineRule(
+ ProguardTypeMatcher classAnnotation,
+ ProguardAccessFlags classAccessFlags,
+ ProguardAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ ProguardClassNameList classNames,
+ ProguardTypeMatcher inheritanceAnnotation,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ List<ProguardMemberRule> memberRules,
+ Type type) {
+ super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+ classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ this.type = type;
+ }
+
+ public static InlineRule.Builder builder() {
+ return new InlineRule.Builder();
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public ProguardCheckDiscardRule asProguardCheckDiscardRule() {
+ assert type == Type.FORCE;
+ ProguardCheckDiscardRule.Builder builder = ProguardCheckDiscardRule.builder();
+ builder.setClassAnnotation(getClassAnnotation());
+ builder.setClassAccessFlags(getClassAccessFlags());
+ builder.setNegatedClassAccessFlags(getNegatedClassAccessFlags());
+ builder.setClassTypeNegated(getClassTypeNegated());
+ builder.setClassType(getClassType());
+ builder.setClassNames(getClassNames());
+ builder.setInheritanceAnnotation(getInheritanceAnnotation());
+ builder.setInheritanceIsExtends(getInheritanceIsExtends());
+ builder.setMemberRules(getMemberRules());
+ return builder.build();
+ }
+
+ @Override
+ String typeString() {
+ switch (type) {
+ case ALWAYS:
+ return "alwaysinline";
+ case FORCE:
+ return "forceinline";
+ case NEVER:
+ return "neverinline";
+ }
+ throw new Unreachable("Unknown inline type " + type);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAlwaysInlineRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAlwaysInlineRule.java
deleted file mode 100644
index 8d4a14b..0000000
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAlwaysInlineRule.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (c) 2017, 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;
-
-import java.util.List;
-
-public class ProguardAlwaysInlineRule extends ProguardConfigurationRule {
-
- public static class Builder extends ProguardConfigurationRule.Builder {
-
- private Builder() {
- }
-
- public ProguardAlwaysInlineRule build() {
- return new ProguardAlwaysInlineRule(classAnnotation, classAccessFlags,
- negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
- inheritanceClassName, inheritanceIsExtends, memberRules);
- }
- }
-
- private ProguardAlwaysInlineRule(
- ProguardTypeMatcher classAnnotation,
- ProguardAccessFlags classAccessFlags,
- ProguardAccessFlags negatedClassAccessFlags,
- boolean classTypeNegated,
- ProguardClassType classType,
- ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
- ProguardTypeMatcher inheritanceClassName,
- boolean inheritanceIsExtends,
- List<ProguardMemberRule> memberRules) {
- super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
- classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
- }
-
- public static ProguardAlwaysInlineRule.Builder builder() {
- return new ProguardAlwaysInlineRule.Builder();
- }
-
- @Override
- String typeString() {
- return "alwaysinline";
- }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 1de3f17..5b22456 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
+import com.android.tools.r8.Version;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
@@ -15,6 +16,7 @@
import com.android.tools.r8.position.Position;
import com.android.tools.r8.position.TextPosition;
import com.android.tools.r8.position.TextRange;
+import com.android.tools.r8.shaking.InlineRule.Type;
import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
@@ -48,6 +50,7 @@
private final Reporter reporter;
private final boolean failOnPartiallyImplementedOptions;
+ private final boolean allowTestOptions;
private static final List<String> IGNORED_SINGLE_ARG_OPTIONS = ImmutableList.of(
"protomapping",
@@ -97,16 +100,18 @@
public ProguardConfigurationParser(
DexItemFactory dexItemFactory, Reporter reporter) {
- this(dexItemFactory, reporter, true);
+ this(dexItemFactory, reporter, true, false);
}
public ProguardConfigurationParser(
- DexItemFactory dexItemFactory, Reporter reporter, boolean failOnPartiallyImplementedOptions) {
+ DexItemFactory dexItemFactory, Reporter reporter, boolean failOnPartiallyImplementedOptions,
+ boolean allowTestOptions) {
this.dexItemFactory = dexItemFactory;
configurationBuilder = ProguardConfiguration.builder(dexItemFactory, reporter);
this.reporter = reporter;
this.failOnPartiallyImplementedOptions = failOnPartiallyImplementedOptions;
+ this.allowTestOptions = allowTestOptions;
}
public ProguardConfiguration.Builder getConfigurationBuilder() {
@@ -343,7 +348,16 @@
} else if (acceptString("packageobfuscationdictionary")) {
configurationBuilder.setPackageObfuscationDictionary(parseFileName());
} else if (acceptString("alwaysinline")) {
- ProguardAlwaysInlineRule rule = parseAlwaysInlineRule();
+ InlineRule rule = parseInlineRule(Type.ALWAYS);
+ configurationBuilder.addRule(rule);
+ } else if (allowTestOptions && acceptString("forceinline")) {
+ InlineRule rule = parseInlineRule(Type.FORCE);
+ configurationBuilder.addRule(rule);
+ // Insert a matching -checkdiscard rule to ensure force inlining happens.
+ ProguardCheckDiscardRule ruled = rule.asProguardCheckDiscardRule();
+ configurationBuilder.addRule(ruled);
+ } else if (allowTestOptions && acceptString("neverinline")) {
+ InlineRule rule = parseInlineRule(Type.NEVER);
configurationBuilder.addRule(rule);
} else if (acceptString("useuniqueclassmembernames")) {
configurationBuilder.setUseUniqueClassMemberNames(true);
@@ -367,8 +381,15 @@
configurationBuilder.addRule(parseIfRule(optionStart));
} else {
String unknownOption = acceptString();
+ String devMessage = "";
+ if (Version.isDev()
+ && unknownOption != null
+ && (unknownOption.equals("forceinline") || unknownOption.equals("neverinline"))) {
+ devMessage = ", this option needs to be turned on explicitly if used for tests.";
+ }
reporter.error(new StringDiagnostic(
- "Unknown option \"-" + unknownOption + "\"", origin, getPosition(optionStart)));
+ "Unknown option \"-" + unknownOption + "\"" + devMessage,
+ origin, getPosition(optionStart)));
}
return true;
}
@@ -563,9 +584,9 @@
return keepRuleBuilder.build();
}
- private ProguardAlwaysInlineRule parseAlwaysInlineRule()
+ private InlineRule parseInlineRule(InlineRule.Type type)
throws ProguardRuleParserException {
- ProguardAlwaysInlineRule.Builder keepRuleBuilder = ProguardAlwaysInlineRule.builder();
+ InlineRule.Builder keepRuleBuilder = InlineRule.builder().setType(type);
parseClassSpec(keepRuleBuilder, false);
return keepRuleBuilder.build();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index ce1840f..e4c5691 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -56,7 +56,7 @@
if (!(o instanceof ProguardConfigurationRule)) {
return false;
}
- ProguardKeepRule that = (ProguardKeepRule) o;
+ ProguardConfigurationRule that = (ProguardConfigurationRule) o;
return super.equals(that);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index c860d83..c95cfe9 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -60,6 +60,8 @@
Sets.newIdentityHashSet();
private final Set<DexItem> checkDiscarded = Sets.newIdentityHashSet();
private final Set<DexItem> alwaysInline = Sets.newIdentityHashSet();
+ private final Set<DexItem> forceInline = Sets.newIdentityHashSet();
+ private final Set<DexItem> neverInline = Sets.newIdentityHashSet();
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking =
new IdentityHashMap<>();
private final Map<DexItem, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
@@ -240,7 +242,7 @@
} else if (rule instanceof ProguardAssumeNoSideEffectRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
markMatchingFields(clazz, memberKeepRules, rule, null);
- } else if (rule instanceof ProguardAlwaysInlineRule) {
+ } else if (rule instanceof InlineRule) {
markMatchingMethods(clazz, memberKeepRules, rule, null);
} else if (rule instanceof ProguardAssumeValuesRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
@@ -310,6 +312,8 @@
keepPackageName,
checkDiscarded,
alwaysInline,
+ forceInline,
+ neverInline,
noSideEffects,
assumedValues,
dependentNoShrinking,
@@ -720,8 +724,20 @@
assumedValues.put(item, rule);
} else if (context instanceof ProguardCheckDiscardRule) {
checkDiscarded.add(item);
- } else if (context instanceof ProguardAlwaysInlineRule) {
- alwaysInline.add(item);
+ } else if (context instanceof InlineRule) {
+ switch (((InlineRule) context).getType()) {
+ case ALWAYS:
+ alwaysInline.add(item);
+ break;
+ case FORCE:
+ forceInline.add(item);
+ break;
+ case NEVER:
+ neverInline.add(item);
+ break;
+ default:
+ throw new Unreachable();
+ }
} else if (context instanceof ProguardIdentifierNameStringRule) {
if (item instanceof DexEncodedField) {
identifierNameStrings.add(((DexEncodedField) item).field);
@@ -740,6 +756,8 @@
public final Set<DexItem> keepPackageName;
public final Set<DexItem> checkDiscarded;
public final Set<DexItem> alwaysInline;
+ public final Set<DexItem> forceInline;
+ public final Set<DexItem> neverInline;
public final Map<DexItem, ProguardMemberRule> noSideEffects;
public final Map<DexItem, ProguardMemberRule> assumedValues;
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
@@ -775,6 +793,8 @@
Set<DexItem> keepPackageName,
Set<DexItem> checkDiscarded,
Set<DexItem> alwaysInline,
+ Set<DexItem> forceInline,
+ Set<DexItem> neverInline,
Map<DexItem, ProguardMemberRule> noSideEffects,
Map<DexItem, ProguardMemberRule> assumedValues,
Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking,
@@ -787,6 +807,8 @@
this.keepPackageName = Collections.unmodifiableSet(keepPackageName);
this.checkDiscarded = Collections.unmodifiableSet(checkDiscarded);
this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
+ this.forceInline = Collections.unmodifiableSet(forceInline);
+ this.neverInline = Collections.unmodifiableSet(neverInline);
this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
this.assumedValues = Collections.unmodifiableMap(assumedValues);
this.dependentNoShrinking = dependentNoShrinking;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index d5d0558..10f62a1 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1546,6 +1546,12 @@
return builder;
}
+ public static R8Command.Builder allowTestProguardOptions(
+ R8Command.Builder builder) {
+ builder.allowTestProguardOptions();
+ return builder;
+ }
+
public static AndroidApp getApp(BaseCommand command) {
return command.getInputApp();
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index f484bc8..1379b95 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -148,7 +148,14 @@
public void resetAllowPartiallyImplementedOptions() {
handler = new KeepingDiagnosticHandler();
reporter = new Reporter(handler);
- parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, false);
+ parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, false, false);
+ }
+
+ @Before
+ public void resetAllowTestOptions() {
+ handler = new KeepingDiagnosticHandler();
+ reporter = new Reporter(handler);
+ parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, true, true);
}
@Test
@@ -861,7 +868,7 @@
@Test
public void parseKeepdirectories() throws Exception {
ProguardConfigurationParser parser =
- new ProguardConfigurationParser(new DexItemFactory(), reporter, false);
+ new ProguardConfigurationParser(new DexItemFactory(), reporter, false, false);
parser.parse(Paths.get(KEEPDIRECTORIES));
verifyParserEndsCleanly();
}
@@ -1357,6 +1364,22 @@
}
@Test
+ public void parse_testInlineOptions() {
+ List<String> options = ImmutableList.of(
+ "-neverinline", "-forceinline");
+ for (String option : options) {
+ try {
+ reset();
+ parser.parse(createConfigurationForTesting(ImmutableList.of(option + " class A { *; }")));
+ fail("Expect to fail due to testing option being turned off.");
+ } catch (AbortException e) {
+ assertEquals(2, handler.errors.size());
+ checkDiagnostics(handler.errors, 0, null, 1, 1, "Unknown option \"" + option + "\"");
+ }
+ }
+ }
+
+ @Test
public void parse_if() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$$ModuleAdapter",
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/A.java b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
new file mode 100644
index 0000000..af05562
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, 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.testrules;
+
+public class A {
+
+ public static int m(int a, int b) {
+ int r = a + b;
+ System.out.println(a + " + " + b + " = " + r);
+ return r;
+ }
+
+ public static int method() {
+ return m(m(m(1, 2), m(3, 4)), m(m(5, 6), m(7, 8)));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/B.java b/src/test/java/com/android/tools/r8/shaking/testrules/B.java
new file mode 100644
index 0000000..4d1f085
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/B.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, 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.testrules;
+
+public class B {
+
+ public int m(int a, int b) {
+ int r = a + b;
+ System.out.println(a + " + " + b + " = " + r);
+ return r;
+ }
+ public int method() {
+ return m(m(m(1, 2), m(3, 4)), m(m(5, 6), m(7, 8)));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/C.java b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
new file mode 100644
index 0000000..5ee4d53
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2018, 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.testrules;
+
+public class C {
+
+ private static int i;
+
+ public static int x() {
+ return i;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
new file mode 100644
index 0000000..e4be101
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2018, 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.
+
+// Copyright (c) 2018, 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.testrules;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+
+public class ForceInlineTest extends TestBase {
+
+ private DexInspector runTest(List<String> proguardConfiguration) throws Exception {
+ R8Command.Builder builder =
+ ToolHelper.prepareR8CommandBuilder(readClasses(Main.class, A.class, B.class, C.class));
+ ToolHelper.allowTestProguardOptions(builder);
+ builder.addProguardConfiguration(proguardConfiguration, Origin.unknown());
+ return new DexInspector(ToolHelper.runR8(builder.build()));
+ }
+
+ @Test
+ public void testDefaultInlining() throws Exception {
+ DexInspector inspector = runTest(ImmutableList.of(
+ "-keep class **.Main { *; }",
+ "-dontobfuscate"
+ ));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ ClassSubject classB = inspector.clazz(B.class);
+ ClassSubject classC = inspector.clazz(C.class);
+ ClassSubject classMain = inspector.clazz(Main.class);
+ assertThat(classA, isPresent());
+ assertThat(classB, isPresent());
+ assertThat(classC, isPresent());
+ assertThat(classMain, isPresent());
+
+ // By default A.m *will not* be inlined (called several times and not small).
+ assertThat(classA.method("int", "m", ImmutableList.of("int", "int")), isPresent());
+ // By default A.method *will* be inlined (called only once).
+ assertThat(classA.method("int", "method", ImmutableList.of()), not(isPresent()));
+ // By default B.m *will not* be inlined (called several times and not small).
+ assertThat(classB.method("int", "m", ImmutableList.of("int", "int")), isPresent());
+ // By default B.method *will* be inlined (called only once).
+ assertThat(classB.method("int", "method", ImmutableList.of()), not(isPresent()));
+ }
+
+ @Test
+ public void testNeverInline() throws Exception {
+ DexInspector inspector = runTest(ImmutableList.of(
+ "-neverinline class **.A { method(); }",
+ "-neverinline class **.B { method(); }",
+ "-keep class **.Main { *; }",
+ "-dontobfuscate"
+ ));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ ClassSubject classB = inspector.clazz(B.class);
+ ClassSubject classC = inspector.clazz(C.class);
+ ClassSubject classMain = inspector.clazz(Main.class);
+ assertThat(classA, isPresent());
+ assertThat(classB, isPresent());
+ assertThat(classC, isPresent());
+ assertThat(classMain, isPresent());
+
+ // Compared to the default method is no longer inlined.
+ assertThat(classA.method("int", "m", ImmutableList.of("int", "int")), isPresent());
+ assertThat(classA.method("int", "method", ImmutableList.of()), isPresent());
+ assertThat(classB.method("int", "m", ImmutableList.of("int", "int")), isPresent());
+ assertThat(classB.method("int", "method", ImmutableList.of()), isPresent());
+ }
+
+ @Test
+ public void testForceInline() throws Exception {
+ DexInspector inspector = runTest(ImmutableList.of(
+ "-forceinline class **.A { int m(int, int); }",
+ "-forceinline class **.B { int m(int, int); }",
+ "-keep class **.Main { *; }",
+ "-dontobfuscate"
+ ));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ ClassSubject classB = inspector.clazz(B.class);
+ ClassSubject classC = inspector.clazz(C.class);
+ ClassSubject classMain = inspector.clazz(Main.class);
+
+ // Compared to the default m is now inlined and method still is, so classes A and B are gone.
+ assertThat(classA, not(isPresent()));
+ assertThat(classB, not(isPresent()));
+ assertThat(classC, isPresent());
+ assertThat(classMain, isPresent());
+ }
+
+ @Test
+ public void testForceInlineFails() throws Exception {
+ try {
+ DexInspector inspector = runTest(ImmutableList.of(
+ "-forceinline class **.A { int x(); }",
+ "-keep class **.Main { *; }",
+ "-dontobfuscate"
+ ));
+ fail("Force inline of non-inlinable method succeeded");
+ } catch (Throwable t) {
+ // Ignore assertion error.
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/Main.java b/src/test/java/com/android/tools/r8/shaking/testrules/Main.java
new file mode 100644
index 0000000..d326216
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/Main.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2018, 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.testrules;
+
+public class Main {
+
+ public static void main(String[] args) {
+ System.out.println(A.method());
+ System.out.println(new B().method());
+ System.out.println(C.x());
+ }
+}