Output inspection API.
Bug: 149814702
Change-Id: Ic706e5246ecd0182707c7f7583c053d4af2fdc57
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 8323666..1431a9a 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.inspector.Inspector;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
import com.android.tools.r8.origin.Origin;
@@ -16,8 +17,11 @@
import com.android.tools.r8.utils.Reporter;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.function.BiPredicate;
+import java.util.function.Consumer;
import java.util.function.Function;
/**
@@ -41,6 +45,7 @@
private final boolean optimizeMultidexForLinearAlloc;
private final BiPredicate<String, Long> dexClassChecksumFilter;
private final List<AssertionsConfiguration> assertionsConfiguration;
+ private final List<Consumer<Inspector>> outputInspections;
BaseCompilerCommand(boolean printHelp, boolean printVersion) {
super(printHelp, printVersion);
@@ -54,6 +59,7 @@
optimizeMultidexForLinearAlloc = false;
dexClassChecksumFilter = (name, checksum) -> true;
assertionsConfiguration = new ArrayList<>();
+ outputInspections = null;
}
BaseCompilerCommand(
@@ -67,7 +73,8 @@
boolean optimizeMultidexForLinearAlloc,
boolean includeClassesChecksum,
BiPredicate<String, Long> dexClassChecksumFilter,
- List<AssertionsConfiguration> assertionsConfiguration) {
+ List<AssertionsConfiguration> assertionsConfiguration,
+ List<Consumer<Inspector>> outputInspections) {
super(app);
assert minApiLevel > 0;
assert mode != null;
@@ -81,6 +88,7 @@
this.includeClassesChecksum = includeClassesChecksum;
this.dexClassChecksumFilter = dexClassChecksumFilter;
this.assertionsConfiguration = assertionsConfiguration;
+ this.outputInspections = outputInspections;
}
/**
@@ -140,7 +148,11 @@
}
public List<AssertionsConfiguration> getAssertionsConfiguration() {
- return assertionsConfiguration;
+ return Collections.unmodifiableList(assertionsConfiguration);
+ }
+
+ public Collection<Consumer<Inspector>> getOutputInspections() {
+ return Collections.unmodifiableList(outputInspections);
}
Reporter getReporter() {
@@ -173,6 +185,7 @@
private boolean optimizeMultidexForLinearAlloc = false;
private BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
private List<AssertionsConfiguration> assertionsConfiguration = new ArrayList<>();
+ private List<Consumer<Inspector>> outputInspections = new ArrayList<>();
abstract CompilationMode defaultCompilationMode();
@@ -552,5 +565,31 @@
}
super.validate();
}
+
+ /**
+ * Add an inspection of the output program.
+ *
+ * <p>On a successful compilation the inspection is guaranteed to be called with inspectors that
+ * combined cover all of the output program. The inspections may be called multiple times with
+ * inspectors that have overlapping content (eg, classes synthesized based on multiple inputs
+ * can lead to this). Any overlapping content will be consistent, e.g., the inspection of type T
+ * will be the same (equality, not identify) as any other inspection of type T.
+ *
+ * <p>There is no guarantee of the order inspections are called or on which thread they are
+ * called.
+ *
+ * <p>The validity of an {@code Inspector} and all of its sub-inspectors, eg,
+ * {@MethodInspector}, is that of the callback. If any inspector object escapes the scope of the
+ * callback, the behavior of that inspector is undefined.
+ *
+ * @param inspection Inspection callback receiving inspectors denoting parts of the output.
+ */
+ public void addOutputInspection(Consumer<Inspector> inspection) {
+ outputInspections.add(inspection);
+ }
+
+ List<Consumer<Inspector>> getOutputInspections() {
+ return outputInspections;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 93387f0..1ce0024 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.InitClassLens;
import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
+import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
@@ -221,6 +222,7 @@
markers.add(marker);
}
+ InspectorImpl.runInspections(options.outputInspections, app);
if (options.isGeneratingClassFiles()) {
new CfApplicationWriter(
app,
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index b7c9347..32e653c 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -6,6 +6,8 @@
import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.inspector.Inspector;
+import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -19,6 +21,7 @@
import java.util.Collection;
import java.util.List;
import java.util.function.BiPredicate;
+import java.util.function.Consumer;
/**
* Immutable command structure for an invocation of the {@link D8} compiler.
@@ -36,13 +39,6 @@
@Keep
public final class D8Command extends BaseCompilerCommand {
- private static class ClasspathInputOrigin extends InputFileOrigin {
-
- public ClasspathInputOrigin(Path file) {
- super("classpath input", file);
- }
- }
-
private static class DefaultD8DiagnosticsHandler implements DiagnosticsHandler {
@Override
@@ -229,6 +225,7 @@
desugaredLibraryKeepRuleConsumer,
libraryConfiguration,
getAssertionsConfiguration(),
+ getOutputInspections(),
factory);
}
}
@@ -297,6 +294,7 @@
StringConsumer desugaredLibraryKeepRuleConsumer,
DesugaredLibraryConfiguration libraryConfiguration,
List<AssertionsConfiguration> assertionsConfiguration,
+ List<Consumer<Inspector>> outputInspections,
DexItemFactory factory) {
super(
inputApp,
@@ -309,7 +307,8 @@
optimizeMultidexForLinearAlloc,
encodeChecksum,
dexClassChecksumFilter,
- assertionsConfiguration);
+ assertionsConfiguration,
+ outputInspections);
this.intermediate = intermediate;
this.desugarGraphConsumer = desugarGraphConsumer;
this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
@@ -379,6 +378,8 @@
new AssertionConfigurationWithDefault(
AssertionTransformation.DISABLE, getAssertionsConfiguration());
+ internal.outputInspections = InspectorImpl.wrapInspections(getOutputInspections());
+
return internal;
}
}
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 9db1c33..c863fe6 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.inspector.Inspector;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
@@ -21,6 +22,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
/** Immutable command structure for an invocation of the {@link L8} library compiler. */
@Keep
@@ -83,6 +85,7 @@
Reporter diagnosticsHandler,
DesugaredLibraryConfiguration libraryConfiguration,
List<AssertionsConfiguration> assertionsConfiguration,
+ List<Consumer<Inspector>> outputInspections,
DexItemFactory factory) {
super(
inputApp,
@@ -95,7 +98,8 @@
false,
false,
(name, checksum) -> true,
- assertionsConfiguration);
+ assertionsConfiguration,
+ outputInspections);
this.d8Command = d8Command;
this.r8Command = r8Command;
this.libraryConfiguration = libraryConfiguration;
@@ -317,6 +321,7 @@
getReporter(),
libraryConfiguration,
getAssertionsConfiguration(),
+ getOutputInspections(),
factory);
}
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index eb5ba8e..4961a3e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.graph.InitClassLens;
import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
+import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
@@ -201,6 +202,7 @@
InternalOptions options,
ProguardMapSupplier proguardMapSupplier)
throws ExecutionException {
+ InspectorImpl.runInspections(options.outputInspections, application);
try {
Marker marker = options.getMarker(Tool.R8);
assert marker != null;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4051f9c..a9c92d2 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -9,6 +9,8 @@
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.features.FeatureSplitConfiguration;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.inspector.Inspector;
+import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
@@ -567,7 +569,8 @@
desugaredLibraryKeepRuleConsumer,
libraryConfiguration,
featureSplitConfiguration,
- getAssertionsConfiguration());
+ getAssertionsConfiguration(),
+ getOutputInspections());
return command;
}
@@ -724,7 +727,8 @@
StringConsumer desugaredLibraryKeepRuleConsumer,
DesugaredLibraryConfiguration libraryConfiguration,
FeatureSplitConfiguration featureSplitConfiguration,
- List<AssertionsConfiguration> assertionsConfiguration) {
+ List<AssertionsConfiguration> assertionsConfiguration,
+ List<Consumer<Inspector>> outputInspections) {
super(
inputApp,
mode,
@@ -736,7 +740,8 @@
optimizeMultidexForLinearAlloc,
encodeChecksum,
dexClassChecksumFilter,
- assertionsConfiguration);
+ assertionsConfiguration,
+ outputInspections);
assert proguardConfiguration != null;
assert mainDexKeepRules != null;
this.mainDexKeepRules = mainDexKeepRules;
@@ -874,6 +879,8 @@
internal.syntheticProguardRulesConsumer = syntheticProguardRulesConsumer;
+ internal.outputInspections = InspectorImpl.wrapInspections(getOutputInspections());
+
// Default is to remove all javac generated assertion code when generating dex.
assert internal.assertionsConfiguration == null;
internal.assertionsConfiguration =
diff --git a/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java b/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java
new file mode 100644
index 0000000..1fba365
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, 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.inspector;
+
+public interface BooleanValueInspector extends ValueInspector {
+ boolean getBooleanValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java b/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java
new file mode 100644
index 0000000..718183e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, 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.inspector;
+
+public interface ByteValueInspector extends ValueInspector {
+ byte getByteValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java b/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java
new file mode 100644
index 0000000..d79ee9c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, 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.inspector;
+
+public interface CharValueInspector extends ValueInspector {
+ char getCharValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/ClassInspector.java b/src/main/java/com/android/tools/r8/inspector/ClassInspector.java
new file mode 100644
index 0000000..b18dd26
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/ClassInspector.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2020, 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.inspector;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+import java.util.function.Consumer;
+
+/** Inspector for a class or interface definition. */
+@Keep
+public interface ClassInspector {
+
+ /** Get the class reference for the class of this inspector. */
+ ClassReference getClassReference();
+
+ /** Iterate all fields declared in the class/interface (unspecified order). */
+ void forEachField(Consumer<FieldInspector> inspection);
+
+ /** Iterate all methods declared in the class/interface (unspecified order). */
+ void forEachMethod(Consumer<MethodInspector> inspection);
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java b/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java
new file mode 100644
index 0000000..334f7ac
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, 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.inspector;
+
+public interface DoubleValueInspector extends ValueInspector {
+ double getDoubleValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/FieldInspector.java b/src/main/java/com/android/tools/r8/inspector/FieldInspector.java
new file mode 100644
index 0000000..5d2e656
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/FieldInspector.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, 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.inspector;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.FieldReference;
+import java.util.Optional;
+
+/** Inspector for a field definition. */
+@Keep
+public interface FieldInspector {
+
+ /** Get the field reference for the field of this inspector. */
+ FieldReference getFieldReference();
+
+ /** True if the field is declared static. */
+ boolean isStatic();
+
+ /** True if the field is declared final. */
+ boolean isFinal();
+
+ /**
+ * Returns an inspector for the initial value if it is known by the compiler.
+ *
+ * <p>Note that the determination of the value is best effort, often the value will simply be the
+ * default value for the given type.
+ */
+ Optional<ValueInspector> getInitialValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java b/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java
new file mode 100644
index 0000000..cb4c12d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, 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.inspector;
+
+public interface FloatValueInspector extends ValueInspector {
+ float getFloatValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/Inspector.java b/src/main/java/com/android/tools/r8/inspector/Inspector.java
new file mode 100644
index 0000000..880b450
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/Inspector.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, 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.inspector;
+
+import com.android.tools.r8.Keep;
+import java.util.function.Consumer;
+
+/** Inspector providing access to various parts of an application. */
+@Keep
+public interface Inspector {
+
+ /** Iterate all classes and interfaces defined by the program (order unspecified). */
+ void forEachClass(Consumer<ClassInspector> inspection);
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java b/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java
new file mode 100644
index 0000000..38376c2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, 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.inspector;
+
+public interface IntValueInspector extends ValueInspector {
+ int getIntValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java b/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java
new file mode 100644
index 0000000..177a885
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, 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.inspector;
+
+public interface LongValueInspector extends ValueInspector {
+ long getLongValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/MethodInspector.java b/src/main/java/com/android/tools/r8/inspector/MethodInspector.java
new file mode 100644
index 0000000..41cc8de
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/MethodInspector.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, 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.inspector;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.MethodReference;
+
+/** Inspector for a method definition. */
+@Keep
+public interface MethodInspector {
+
+ /** Get the method reference for the method of this inspector. */
+ MethodReference getMethodReference();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java b/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java
new file mode 100644
index 0000000..fb1d823
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, 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.inspector;
+
+public interface ShortValueInspector extends ValueInspector {
+ short getShortValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java b/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java
new file mode 100644
index 0000000..c0b78c4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, 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.inspector;
+
+public interface StringValueInspector extends ValueInspector {
+ String getStringValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/ValueInspector.java b/src/main/java/com/android/tools/r8/inspector/ValueInspector.java
new file mode 100644
index 0000000..84f1fe0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/ValueInspector.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2020, 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.inspector;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.TypeReference;
+
+/** Inspector for a JVM representable value. */
+@Keep
+public interface ValueInspector {
+
+ /** Get the type reference describing the type of the value. */
+ TypeReference getTypeReference();
+
+ boolean isPrimitive();
+
+ boolean isBooleanValue();
+
+ boolean isByteValue();
+
+ boolean isCharValue();
+
+ boolean isShortValue();
+
+ boolean isIntValue();
+
+ boolean isLongValue();
+
+ boolean isFloatValue();
+
+ boolean isDoubleValue();
+
+ boolean isStringValue();
+
+ BooleanValueInspector asBooleanValue();
+
+ ByteValueInspector asByteValue();
+
+ CharValueInspector asCharValue();
+
+ ShortValueInspector asShortValue();
+
+ IntValueInspector asIntValue();
+
+ LongValueInspector asLongValue();
+
+ FloatValueInspector asFloatValue();
+
+ DoubleValueInspector asDoubleValue();
+
+ StringValueInspector asStringValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/ClassInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/ClassInspectorImpl.java
new file mode 100644
index 0000000..c2ebc8e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/internal/ClassInspectorImpl.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2020, 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.inspector.internal;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.inspector.ClassInspector;
+import com.android.tools.r8.inspector.FieldInspector;
+import com.android.tools.r8.inspector.MethodInspector;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import java.util.function.Consumer;
+
+public class ClassInspectorImpl implements ClassInspector {
+
+ private final DexClass clazz;
+ private ClassReference reference = null;
+
+ ClassInspectorImpl(DexClass clazz) {
+ this.clazz = clazz;
+ }
+
+ @Override
+ public ClassReference getClassReference() {
+ if (reference == null) {
+ reference = Reference.classFromDescriptor(clazz.type.toDescriptorString());
+ }
+ return reference;
+ }
+
+ @Override
+ public void forEachField(Consumer<FieldInspector> inspection) {
+ clazz.forEachField(field -> inspection.accept(new FieldInspectorImpl(this, field)));
+ }
+
+ @Override
+ public void forEachMethod(Consumer<MethodInspector> inspection) {
+ clazz.forEachMethod(method -> inspection.accept(new MethodInspectorImpl(this, method)));
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/FieldInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/FieldInspectorImpl.java
new file mode 100644
index 0000000..056d652e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/internal/FieldInspectorImpl.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2020, 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.inspector.internal;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.inspector.FieldInspector;
+import com.android.tools.r8.inspector.ValueInspector;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import java.util.Optional;
+
+public class FieldInspectorImpl implements FieldInspector {
+ private final ClassInspectorImpl parent;
+ private final DexEncodedField field;
+ private FieldReference reference = null;
+
+ public FieldInspectorImpl(ClassInspectorImpl parent, DexEncodedField field) {
+ this.parent = parent;
+ this.field = field;
+ }
+
+ @Override
+ public FieldReference getFieldReference() {
+ if (reference == null) {
+ reference =
+ Reference.field(
+ parent.getClassReference(),
+ field.field.name.toString(),
+ Reference.typeFromDescriptor(field.field.type.toDescriptorString()));
+ }
+ return reference;
+ }
+
+ @Override
+ public boolean isStatic() {
+ return field.accessFlags.isStatic();
+ }
+
+ @Override
+ public boolean isFinal() {
+ return field.accessFlags.isFinal();
+ }
+
+ @Override
+ public Optional<ValueInspector> getInitialValue() {
+ if (field.isStatic() && field.getStaticValue() != null) {
+ return Optional.of(new ValueInspectorImpl(field.getStaticValue(), field.field.type));
+ }
+ return Optional.empty();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/InspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/InspectorImpl.java
new file mode 100644
index 0000000..c3d8a64
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/internal/InspectorImpl.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2020, 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.inspector.internal;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.inspector.ClassInspector;
+import com.android.tools.r8.inspector.Inspector;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class InspectorImpl implements Inspector {
+
+ // This wrapping appears odd, but allows hooking in inspections on the impl type from tests.
+ public static List<Consumer<InspectorImpl>> wrapInspections(
+ Collection<Consumer<Inspector>> inspections) {
+ if (inspections == null || inspections.isEmpty()) {
+ return Collections.emptyList();
+ }
+ List<Consumer<InspectorImpl>> wrapped = new ArrayList<>(inspections.size());
+ for (Consumer<Inspector> inspection : inspections) {
+ wrapped.add(inspection::accept);
+ }
+ return wrapped;
+ }
+
+ public static void runInspections(
+ List<Consumer<InspectorImpl>> inspections, DexApplication application) {
+ if (inspections == null || inspections.isEmpty()) {
+ return;
+ }
+ InspectorImpl inspector = new InspectorImpl(application);
+ for (Consumer<InspectorImpl> inspection : inspections) {
+ inspection.accept(inspector);
+ }
+ }
+
+ private final DexApplication application;
+
+ public InspectorImpl(DexApplication application) {
+ this.application = application;
+ }
+
+ @Override
+ public void forEachClass(Consumer<ClassInspector> inspection) {
+ for (DexProgramClass clazz : application.classes()) {
+ inspection.accept(new ClassInspectorImpl(clazz));
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/MethodInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/MethodInspectorImpl.java
new file mode 100644
index 0000000..1d868be
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/internal/MethodInspectorImpl.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2020, 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.inspector.internal;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.inspector.MethodInspector;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.ListUtils;
+import java.util.Arrays;
+
+public class MethodInspectorImpl implements MethodInspector {
+
+ private final ClassInspectorImpl parent;
+ private final DexEncodedMethod method;
+ private MethodReference reference;
+
+ public MethodInspectorImpl(ClassInspectorImpl parent, DexEncodedMethod method) {
+ this.parent = parent;
+ this.method = method;
+ }
+
+ @Override
+ public MethodReference getMethodReference() {
+ if (reference == null) {
+ reference =
+ Reference.method(
+ parent.getClassReference(),
+ method.method.name.toString(),
+ ListUtils.map(
+ Arrays.asList(method.method.proto.parameters.values),
+ param -> Reference.typeFromDescriptor(param.toDescriptorString())),
+ method.method.proto.returnType.isVoidType()
+ ? null
+ : Reference.typeFromDescriptor(
+ method.method.proto.returnType.toDescriptorString()));
+ }
+ return reference;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/ValueInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/ValueInspectorImpl.java
new file mode 100644
index 0000000..8f269d7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/internal/ValueInspectorImpl.java
@@ -0,0 +1,198 @@
+// Copyright (c) 2020, 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.inspector.internal;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.inspector.BooleanValueInspector;
+import com.android.tools.r8.inspector.ByteValueInspector;
+import com.android.tools.r8.inspector.CharValueInspector;
+import com.android.tools.r8.inspector.DoubleValueInspector;
+import com.android.tools.r8.inspector.FloatValueInspector;
+import com.android.tools.r8.inspector.IntValueInspector;
+import com.android.tools.r8.inspector.LongValueInspector;
+import com.android.tools.r8.inspector.ShortValueInspector;
+import com.android.tools.r8.inspector.StringValueInspector;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+
+public class ValueInspectorImpl
+ implements BooleanValueInspector,
+ ByteValueInspector,
+ CharValueInspector,
+ ShortValueInspector,
+ IntValueInspector,
+ LongValueInspector,
+ FloatValueInspector,
+ DoubleValueInspector,
+ StringValueInspector {
+
+ private final DexValue value;
+ private final DexType type;
+
+ public ValueInspectorImpl(DexValue value, DexType type) {
+ this.value = value;
+ this.type = type;
+ }
+
+ @Override
+ public TypeReference getTypeReference() {
+ return Reference.typeFromDescriptor(type.toDescriptorString());
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return type.isPrimitiveType();
+ }
+
+ @Override
+ public boolean isBooleanValue() {
+ return type.isBooleanType();
+ }
+
+ @Override
+ public BooleanValueInspector asBooleanValue() {
+ return isBooleanValue() ? this : null;
+ }
+
+ @Override
+ public boolean getBooleanValue() {
+ guard(isBooleanValue());
+ return value.asDexValueBoolean().getValue();
+ }
+
+ @Override
+ public boolean isByteValue() {
+ return type.isByteType();
+ }
+
+ @Override
+ public ByteValueInspector asByteValue() {
+ return isByteValue() ? this : null;
+ }
+
+ @Override
+ public byte getByteValue() {
+ guard(isByteValue());
+ return value.asDexValueByte().getValue();
+ }
+
+ @Override
+ public boolean isCharValue() {
+ return type.isCharType();
+ }
+
+ @Override
+ public CharValueInspector asCharValue() {
+ return isCharValue() ? this : null;
+ }
+
+ @Override
+ public char getCharValue() {
+ guard(isCharValue());
+ return value.asDexValueChar().getValue();
+ }
+
+ @Override
+ public boolean isShortValue() {
+ return type.isShortType();
+ }
+
+ @Override
+ public ShortValueInspector asShortValue() {
+ return isShortValue() ? this : null;
+ }
+
+ @Override
+ public short getShortValue() {
+ guard(isShortValue());
+ return value.asDexValueShort().getValue();
+ }
+
+ @Override
+ public boolean isIntValue() {
+ return type.isIntType();
+ }
+
+ @Override
+ public IntValueInspector asIntValue() {
+ return isIntValue() ? this : null;
+ }
+
+ @Override
+ public int getIntValue() {
+ guard(isIntValue());
+ return value.asDexValueInt().value;
+ }
+
+ @Override
+ public boolean isLongValue() {
+ return type.isLongType();
+ }
+
+ @Override
+ public LongValueInspector asLongValue() {
+ return isLongValue() ? this : null;
+ }
+
+ @Override
+ public long getLongValue() {
+ guard(isLongValue());
+ return value.asDexValueLong().getValue();
+ }
+
+ @Override
+ public boolean isFloatValue() {
+ return type.isFloatType();
+ }
+
+ @Override
+ public FloatValueInspector asFloatValue() {
+ return isFloatValue() ? this : null;
+ }
+
+ @Override
+ public float getFloatValue() {
+ guard(isFloatValue());
+ return value.asDexValueFloat().getValue();
+ }
+
+ @Override
+ public boolean isDoubleValue() {
+ return type.isDoubleType();
+ }
+
+ @Override
+ public DoubleValueInspector asDoubleValue() {
+ return isDoubleValue() ? this : null;
+ }
+
+ @Override
+ public double getDoubleValue() {
+ guard(isDoubleValue());
+ return value.asDexValueDouble().getValue();
+ }
+
+ @Override
+ public boolean isStringValue() {
+ return type.isClassType() && value.isDexValueString();
+ }
+
+ @Override
+ public StringValueInspector asStringValue() {
+ return isStringValue() ? this : null;
+ }
+
+ @Override
+ public String getStringValue() {
+ guard(isStringValue());
+ return value.asDexValueString().getValue().toString();
+ }
+
+ private static void guard(boolean precondition) {
+ if (!precondition) {
+ throw new IllegalStateException("Invalid call on ValueInspector");
+ }
+ }
+}
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 3e44783..7ff5ff4 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -32,6 +32,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
import com.android.tools.r8.ir.optimize.Inliner;
@@ -58,6 +59,7 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
@@ -113,6 +115,8 @@
public DataResourceConsumer dataResourceConsumer;
public FeatureSplitConfiguration featureSplitConfiguration;
+ public List<Consumer<InspectorImpl>> outputInspections = Collections.emptyList();
+
// Constructor for testing and/or other utilities.
public InternalOptions() {
reporter = new Reporter();
diff --git a/src/test/java/com/android/tools/r8/inspection/FieldFlagsAndValueInspectionTest.java b/src/test/java/com/android/tools/r8/inspection/FieldFlagsAndValueInspectionTest.java
new file mode 100644
index 0000000..d64be27
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/inspection/FieldFlagsAndValueInspectionTest.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2020, 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.inspection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.inspector.Inspector;
+import com.android.tools.r8.inspector.ValueInspector;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldFlagsAndValueInspectionTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("30");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public FieldFlagsAndValueInspectionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClasses(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ return;
+ }
+ testForD8()
+ .addProgramClasses(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .apply(b -> b.getBuilder().addOutputInspection(inspector -> inspection(inspector, false)))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ assertFound();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepClassAndMembersRules(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .apply(b -> b.getBuilder().addOutputInspection(inspector -> inspection(inspector, true)))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ assertFound();
+ }
+
+ private int foundFields = 0;
+
+ private void inspection(Inspector inspector, boolean isR8) {
+ inspector.forEachClass(
+ classInspector -> {
+ classInspector.forEachField(
+ fieldInspector -> {
+ foundFields++;
+ String name = fieldInspector.getFieldReference().getFieldName();
+ assertEquals(name.contains("s"), fieldInspector.isStatic());
+ assertEquals(name.contains("f"), fieldInspector.isFinal());
+ assertEquals(Reference.INT, fieldInspector.getFieldReference().getFieldType());
+ Optional<ValueInspector> value = fieldInspector.getInitialValue();
+ if (fieldInspector.isStatic() && fieldInspector.isFinal()) {
+ // The static final 'sfi' is static initialized to 2.
+ assertTrue(value.isPresent());
+ assertEquals(2, value.get().asIntValue().getIntValue());
+ } else if (fieldInspector.isStatic()) {
+ // The static 'si' is default initialized to 0 and clinit sets it to 4.
+ // R8 optimizes that to directly set 4.
+ assertTrue(value.isPresent());
+ assertEquals(isR8 ? 4 : 0, value.get().asIntValue().getIntValue());
+ } else {
+ assertFalse(value.isPresent());
+ }
+ });
+ });
+ }
+
+ private void assertFound() {
+ assertEquals(4, foundFields);
+ }
+
+ static class TestClass {
+ public static final int sfi = 2;
+ public static int si = 4;
+ public final int fi = 8;
+ public int i = 16;
+
+ public static void main(String[] args) {
+ TestClass obj = new TestClass();
+ System.out.println(obj.sfi + obj.si + obj.fi + obj.i);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/inspection/FieldValueTypesInspectionTest.java b/src/test/java/com/android/tools/r8/inspection/FieldValueTypesInspectionTest.java
new file mode 100644
index 0000000..0031885
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/inspection/FieldValueTypesInspectionTest.java
@@ -0,0 +1,196 @@
+// Copyright (c) 2020, 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.inspection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.inspector.Inspector;
+import com.android.tools.r8.inspector.ValueInspector;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+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 FieldValueTypesInspectionTest extends TestBase {
+
+ static final String EXPECTED =
+ StringUtils.lines(
+ "" + TestClass.z,
+ "" + TestClass.b,
+ "" + TestClass.c,
+ "" + TestClass.s,
+ "" + TestClass.i,
+ "" + TestClass.j,
+ "" + TestClass.f,
+ "" + TestClass.d,
+ "foo");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public FieldValueTypesInspectionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClasses(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ return;
+ }
+ testForD8()
+ .addProgramClasses(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .apply(b -> b.getBuilder().addOutputInspection(this::inspection))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ assertFound();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepClassAndMembersRules(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .apply(b -> b.getBuilder().addOutputInspection(this::inspection))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ assertFound();
+ }
+
+ private int foundFields = 0;
+
+ private void inspection(Inspector inspector) {
+ inspector.forEachClass(
+ classInspector -> {
+ classInspector.forEachField(
+ fieldInspector -> {
+ foundFields++;
+ FieldReference reference = fieldInspector.getFieldReference();
+ ValueInspector value = fieldInspector.getInitialValue().get();
+ assertEquals(reference.getFieldType(), value.getTypeReference());
+ String name = reference.getFieldName();
+ boolean isBoolean = name.equals("z");
+ boolean isByte = name.equals("b");
+ boolean isChar = name.equals("c");
+ boolean isShort = name.equals("s");
+ boolean isInt = name.equals("i");
+ boolean isLong = name.equals("j");
+ boolean isFloat = name.equals("f");
+ boolean isDouble = name.equals("d");
+ boolean isString = name.equals("str");
+ assertEquals(isBoolean, value.isBooleanValue());
+ assertEquals(isByte, value.isByteValue());
+ assertEquals(isChar, value.isCharValue());
+ assertEquals(isShort, value.isShortValue());
+ assertEquals(isInt, value.isIntValue());
+ assertEquals(isLong, value.isLongValue());
+ assertEquals(isFloat, value.isFloatValue());
+ assertEquals(isDouble, value.isDoubleValue());
+ assertEquals(isString, value.isStringValue());
+ if (isBoolean) {
+ assertEquals(Reference.BOOL, reference.getFieldType());
+ assertEquals(TestClass.z, value.asBooleanValue().getBooleanValue());
+ } else {
+ assertNull(value.asBooleanValue());
+ }
+ if (isByte) {
+ assertEquals(Reference.BYTE, reference.getFieldType());
+ assertEquals(TestClass.b, value.asByteValue().getByteValue());
+ } else {
+ assertNull(value.asByteValue());
+ }
+ if (isChar) {
+ assertEquals(Reference.CHAR, reference.getFieldType());
+ assertEquals(TestClass.c, value.asCharValue().getCharValue());
+ } else {
+ assertNull(value.asCharValue());
+ }
+ if (isShort) {
+ assertEquals(Reference.SHORT, reference.getFieldType());
+ assertEquals(TestClass.s, value.asShortValue().getShortValue());
+ } else {
+ assertNull(value.asShortValue());
+ }
+ if (isInt) {
+ assertEquals(Reference.INT, reference.getFieldType());
+ assertEquals(TestClass.i, value.asIntValue().getIntValue());
+ } else {
+ assertNull(value.asIntValue());
+ }
+ if (isLong) {
+ assertEquals(Reference.LONG, reference.getFieldType());
+ assertEquals(TestClass.j, value.asLongValue().getLongValue());
+ } else {
+ assertNull(value.asLongValue());
+ }
+ if (isFloat) {
+ assertEquals(Reference.FLOAT, reference.getFieldType());
+ assertEquals(
+ Float.floatToRawIntBits(TestClass.f),
+ Float.floatToRawIntBits(value.asFloatValue().getFloatValue()));
+ } else {
+ assertNull(value.asFloatValue());
+ }
+ if (isDouble) {
+ assertEquals(Reference.DOUBLE, reference.getFieldType());
+ assertEquals(
+ Double.doubleToRawLongBits(TestClass.d),
+ Double.doubleToRawLongBits(value.asDoubleValue().getDoubleValue()));
+ } else {
+ assertNull(value.asDoubleValue());
+ }
+ if (isString) {
+ assertEquals(Reference.classFromClass(String.class), reference.getFieldType());
+ assertEquals(TestClass.str, value.asStringValue().getStringValue());
+ } else {
+ assertNull(value.asStringValue());
+ }
+ });
+ });
+ }
+
+ private void assertFound() {
+ assertEquals(9, foundFields);
+ }
+
+ static class TestClass {
+ public static final boolean z = true;
+ public static final byte b = 2;
+ public static final char c = 4;
+ public static final short s = 8;
+ public static final int i = 16;
+ public static final long j = 32L;
+ public static final float f = 64.1F;
+ public static final double d = 128.1D;
+ public static final String str = "foo";
+
+ public static void main(String[] args) {
+ System.out.println(z);
+ System.out.println(b);
+ System.out.println(c);
+ System.out.println(s);
+ System.out.println(i);
+ System.out.println(j);
+ System.out.println(f);
+ System.out.println(d);
+ System.out.println(str);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/inspection/InspectionApiTest.java b/src/test/java/com/android/tools/r8/inspection/InspectionApiTest.java
new file mode 100644
index 0000000..5c5a6e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/inspection/InspectionApiTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, 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.inspection;
+
+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.inspector.Inspector;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+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 InspectionApiTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InspectionApiTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClasses(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ return;
+ }
+ testForD8()
+ .addProgramClasses(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .apply(b -> b.getBuilder().addOutputInspection(this::inspection))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ assertFound();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepClassAndMembersRules(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .apply(b -> b.getBuilder().addOutputInspection(this::inspection))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ assertFound();
+ }
+
+ ClassReference foundClass = null;
+ FieldReference foundField = null;
+ MethodReference foundMethod = null;
+
+ private void inspection(Inspector inspector) {
+ inspector.forEachClass(
+ classInspector -> {
+ foundClass = classInspector.getClassReference();
+ classInspector.forEachField(
+ fieldInspector -> {
+ foundField = fieldInspector.getFieldReference();
+ });
+ classInspector.forEachMethod(
+ methodInspector -> {
+ // Ignore clinit (which is removed in R8).
+ if (!methodInspector.getMethodReference().getMethodName().equals("<clinit>")) {
+ foundMethod = methodInspector.getMethodReference();
+ }
+ });
+ });
+ }
+
+ private void assertFound() throws Exception {
+ assertEquals(Reference.classFromClass(TestClass.class), foundClass);
+ assertEquals(Reference.fieldFromField(TestClass.class.getDeclaredField("foo")), foundField);
+ assertEquals(
+ Reference.methodFromMethod(TestClass.class.getDeclaredMethod("main", String[].class)),
+ foundMethod);
+ }
+
+ static class TestClass {
+ public static int foo = 42;
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world");
+ }
+ }
+}