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");
+    }
+  }
+}