Replace clinit static puts with initial values for static fields

when a static put in clinit is dominated by both the exit block, and all
other usages of the static field, then static put can be removed, and the
dex file data section (static_values_off) holding the initial value for
static fields can be used for setting the initial value.

Include <clinit> as a callee in the call graph when a class is mentioned.

Also fix a bug in the reading for initial values for static fields
from dex files.

Change-Id: I9d92c8de414d935eda8adff6a116d540de5d67dc
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index c94fc3c..1ba7755 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -15,7 +15,7 @@
   public final DexField field;
   public final DexAccessFlags accessFlags;
   public DexAnnotationSet annotations;
-  public final DexValue staticValue;
+  public DexValue staticValue;
 
   public DexEncodedField(DexField field, DexAccessFlags accessFlags, DexAnnotationSet annotations,
       DexValue staticValue) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index d53c577..1880668 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -48,6 +48,10 @@
     return outValue;
   }
 
+  public boolean getBooleanValue() {
+    return !isZero();
+  }
+
   public int getIntValue() {
     assert type == ConstType.INT || type == ConstType.INT_OR_FLOAT;
     return (int) value;
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index 9f28eea..c8a17dc2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -110,6 +110,39 @@
     };
   }
 
+  /**
+   * Returns an iterator over all dominator blocks of <code>dominated</code>.
+   *
+   * Iteration order is always the immediate dominator of the previously returned block. The
+   * iteration starts by returning <code>dominated</code>.
+   */
+  public Iterable<BasicBlock> dominatorBlocks(BasicBlock dominated) {
+    return () -> new Iterator<BasicBlock>() {
+      private BasicBlock current = dominated;
+
+      @Override
+      public boolean hasNext() {
+        return current != null;
+      }
+
+      @Override
+      public BasicBlock next() {
+        if (!hasNext()) {
+          return null;
+        } else {
+          BasicBlock result = current;
+          if (current.getNumber() == 0) {
+            current = null;
+          } else {
+            current = immediateDominator(current);
+            assert current != result;
+          }
+          return result;
+        }
+      }
+    };
+  }
+
   public BasicBlock[] getSortedBlocks() {
     return sorted;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 48c8eb0..dad58d8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -328,16 +327,57 @@
       this.graph = graph;
     }
 
-    private void processInvoke(DexEncodedMethod source, Invoke.Type type, DexMethod method) {
+    private void addClassInitializerTarget(DexClass clazz) {
+      assert clazz != null;
+      if (clazz.hasClassInitializer() && !clazz.isLibraryClass()) {
+        DexEncodedMethod possibleTarget = clazz.getClassInitializer();
+        addTarget(possibleTarget);
+      }
+    }
+
+    private void addClassInitializerTarget(DexType type) {
+      if (type.isArrayType()) {
+        type = type.toBaseType(appInfo.dexItemFactory);
+      }
+      DexClass clazz = appInfo.definitionFor(type);
+      if (clazz != null) {
+        addClassInitializerTarget(clazz);
+      }
+    }
+
+    private void addTarget(DexEncodedMethod target) {
+      Node callee = graph.ensureMethodNode(target);
+      graph.addCall(caller, callee);
+    }
+
+    private void addPossibleTarget(DexEncodedMethod possibleTarget) {
+      DexClass possibleTargetClass =
+          appInfo.definitionFor(possibleTarget.method.getHolder());
+      if (possibleTargetClass != null && !possibleTargetClass.isLibraryClass()) {
+        addTarget(possibleTarget);
+      }
+    }
+
+    private void addPossibleTargets(
+        DexEncodedMethod definition, Set<DexEncodedMethod> possibleTargets) {
+      for (DexEncodedMethod possibleTarget : possibleTargets) {
+        if (possibleTarget != definition) {
+          addPossibleTarget(possibleTarget);
+        }
+      }
+    }
+
+    private void processInvoke(Type type, DexMethod method) {
+      DexEncodedMethod source = caller.method;
       method = graphLense.lookupMethod(method, source);
       DexEncodedMethod definition = appInfo.lookup(type, method);
       if (definition != null) {
         assert !source.accessFlags.isBridge() || definition != caller.method;
-        DexType definitionHolder = definition.method.getHolder();
-        assert definitionHolder.isClassType();
-        if (!appInfo.definitionFor(definitionHolder).isLibraryClass()) {
-          Node callee = graph.ensureMethodNode(definition);
-          graph.addCall(caller, callee);
+        DexClass definitionHolder = appInfo.definitionFor(definition.method.getHolder());
+        assert definitionHolder != null;
+        if (!definitionHolder.isLibraryClass()) {
+          addClassInitializerTarget(definitionHolder);
+          addTarget(definition);
           // For virtual and interface calls add all potential targets that could be called.
           if (type == Type.VIRTUAL || type == Type.INTERFACE) {
             Set<DexEncodedMethod> possibleTargets;
@@ -346,73 +386,74 @@
             } else {
               possibleTargets = appInfo.lookupVirtualTargets(definition.method);
             }
-            for (DexEncodedMethod possibleTarget : possibleTargets) {
-              if (possibleTarget != definition) {
-                DexClass possibleTargetClass =
-                    appInfo.definitionFor(possibleTarget.method.getHolder());
-                if (possibleTargetClass != null && !possibleTargetClass.isLibraryClass()) {
-                  callee = graph.ensureMethodNode(possibleTarget);
-                  graph.addCall(caller, callee);
-                }
-              }
-            }
+            addPossibleTargets(definition, possibleTargets);
           }
         }
       }
     }
 
+    private void processFieldAccess(DexField field) {
+      // Any field access implicitly calls the class initializer.
+      addClassInitializerTarget(field.getHolder());
+    }
+
     @Override
     public boolean registerInvokeVirtual(DexMethod method) {
-      processInvoke(caller.method, Type.VIRTUAL, method);
+      processInvoke(Type.VIRTUAL, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeDirect(DexMethod method) {
-      processInvoke(caller.method, Type.DIRECT, method);
+      processInvoke(Type.DIRECT, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeStatic(DexMethod method) {
-      processInvoke(caller.method, Type.STATIC, method);
+      processInvoke(Type.STATIC, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeInterface(DexMethod method) {
-      processInvoke(caller.method, Type.INTERFACE, method);
+      processInvoke(Type.INTERFACE, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeSuper(DexMethod method) {
-      processInvoke(caller.method, Type.SUPER, method);
+      processInvoke(Type.SUPER, method);
       return false;
     }
 
     @Override
     public boolean registerInstanceFieldWrite(DexField field) {
+      processFieldAccess(field);
       return false;
     }
 
     @Override
     public boolean registerInstanceFieldRead(DexField field) {
+      processFieldAccess(field);
       return false;
     }
 
     @Override
     public boolean registerNewInstance(DexType type) {
+      addClassInitializerTarget(type);
       return false;
     }
 
     @Override
     public boolean registerStaticFieldRead(DexField field) {
+      processFieldAccess(field);
       return false;
     }
 
     @Override
     public boolean registerStaticFieldWrite(DexField field) {
+      processFieldAccess(field);
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 23b6b70..3cd40f7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -463,6 +463,7 @@
     codeRewriter.foldConstants(code);
     codeRewriter.rewriteSwitch(code);
     codeRewriter.simplifyIf(code);
+    codeRewriter.collectClassInitializerDefaults(method, code);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s",
           method.toSourceString(), code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 48a64b8..eab1dfa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -6,13 +6,26 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueBoolean;
+import com.android.tools.r8.graph.DexValue.DexValueByte;
+import com.android.tools.r8.graph.DexValue.DexValueChar;
+import com.android.tools.r8.graph.DexValue.DexValueDouble;
+import com.android.tools.r8.graph.DexValue.DexValueFloat;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueLong;
+import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.graph.DexValue.DexValueShort;
+import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.Binop;
@@ -55,6 +68,7 @@
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -605,6 +619,102 @@
     }
   }
 
+  public void collectClassInitializerDefaults(DexEncodedMethod method, IRCode code) {
+    if (!method.isClassInitializer()) {
+      return;
+    }
+
+    // Collect all static put which are dominated by the exit block, and not dominated by a
+    // static get.
+    // This does not check for instructions that can throw. However, as classes which throw in the
+    // class initializer are never visible to the program (see Java Virtual Machine Specification
+    // section 5.5, https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5), this
+    // does not matter (except maybe for removal of const-string instructions, but that is
+    // acceptable).
+    DominatorTree dominatorTree = new DominatorTree(code);
+    BasicBlock exit = code.getNormalExitBlock();
+    if (exit == null) {
+      return;
+    }
+    Map<DexField, StaticPut> puts = Maps.newIdentityHashMap();
+    for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+      InstructionListIterator iterator = block.listIterator(block.getInstructions().size());
+      while (iterator.hasPrevious()) {
+        Instruction current = iterator.previous();
+        if (current.isStaticPut()) {
+          StaticPut put = current.asStaticPut();
+          DexField field = put.getField();
+          if (!(field.type.isPrimitiveType()
+              || field.type == dexItemFactory.stringType)
+              || field.getHolder() != method.method.getHolder()) {
+            continue;
+          }
+          if (put.inValue().isConstant()) {
+            // Collect put as a potential default value.
+            puts.put(put.getField(), put);
+          }
+        }
+        if (current.isStaticGet()) {
+          // If a static field is read, any collected potential default value cannot be a
+          // default value.
+          if (puts.containsKey(current.asStaticGet().getField())) {
+            puts.remove(current.asStaticGet().getField());
+          }
+        }
+      }
+    }
+
+    if (!puts.isEmpty()) {
+      // Set initial values for static fields from the definitive static put instructions collected.
+      for (StaticPut put : puts.values()) {
+        DexField field = put.getField();
+        DexEncodedField encodedField = appInfo.definitionFor(field);
+        if (field.type == dexItemFactory.stringType) {
+          if (put.inValue().getConstInstruction().isConstNumber()) {
+            assert put.inValue().getConstInstruction().asConstNumber().isZero();
+            encodedField.staticValue = DexValueNull.NULL;
+          } else {
+            ConstString cnst = put.inValue().getConstInstruction().asConstString();
+            encodedField.staticValue = new DexValueString(cnst.getValue());
+          }
+        } else {
+          ConstNumber cnst = put.inValue().getConstInstruction().asConstNumber();
+          if (field.type == dexItemFactory.booleanType) {
+            encodedField.staticValue = DexValueBoolean.create(cnst.getBooleanValue());
+          } else if (field.type == dexItemFactory.byteType) {
+            encodedField.staticValue = DexValueByte.create((byte) cnst.getIntValue());
+          } else if (field.type == dexItemFactory.shortType) {
+            encodedField.staticValue = DexValueShort.create((short) cnst.getIntValue());
+          } else if (field.type == dexItemFactory.intType) {
+            encodedField.staticValue = DexValueInt.create(cnst.getIntValue());
+          } else if (field.type == dexItemFactory.longType) {
+            encodedField.staticValue = DexValueLong.create(cnst.getLongValue());
+          } else if (field.type == dexItemFactory.floatType) {
+            encodedField.staticValue = DexValueFloat.create(cnst.getFloatValue());
+          } else if (field.type == dexItemFactory.doubleType) {
+            encodedField.staticValue = DexValueDouble.create(cnst.getDoubleValue());
+          } else if (field.type == dexItemFactory.charType) {
+            encodedField.staticValue = DexValueChar.create((char) cnst.getIntValue());
+          } else {
+             throw new Unreachable("Unexpected field type.");
+          }
+        }
+      }
+
+      // Remove the static put instructions now replaced by static filed initial values.
+      for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+        InstructionListIterator iterator = block.listIterator();
+        while (iterator.hasNext()) {
+          Instruction current = iterator.next();
+          if (current.isStaticPut() && puts.values().contains(current.asStaticPut())) {
+            iterator.remove();
+          }
+        }
+      }
+
+    }
+  }
+
   /**
    * Due to inlining, we might see chains of casts on subtypes. It suffices to cast to the lowest
    * subtype, as that will fail if a cast on a supertype would have failed.
diff --git a/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java b/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
index 8903525..d6848a4 100644
--- a/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
@@ -13,10 +13,11 @@
     long result = 0;
     int shift = 0;
     for (int i = 1; i < numberOfBytes; i++) {
-      result |= ((long) ((file.get() & 0xFF))) << shift;
+      result |= ((long) (file.get() & 0xFF)) << shift;
       shift += 8;
     }
-    return result | (file.get() << shift);
+    // Let the last byte sign-extend into any remaining bytes.
+    return result | (((long) file.get()) << shift);
   }
 
   // Inspired by com.android.dex.EncodedValueCodec
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValues.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValues.java
new file mode 100644
index 0000000..085b069
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValues.java
@@ -0,0 +1,198 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.rewrite.staticvalues;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import org.junit.Test;
+
+public class StaticValues extends SmaliTestBase {
+
+  @Test
+  public void testAllTypes() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticField("booleanField", "Z");
+    builder.addStaticField("byteField", "B");
+    builder.addStaticField("shortField", "S");
+    builder.addStaticField("intField", "I");
+    builder.addStaticField("longField", "J");
+    builder.addStaticField("floatField", "F");
+    builder.addStaticField("doubleField", "D");
+    builder.addStaticField("charField", "C");
+    builder.addStaticField("stringField", "Ljava/lang/String;");
+
+    builder.addStaticInitializer(
+        2,
+        "const               v0, 1",
+        "sput-byte           v0, LTest;->booleanField:Z",
+        "sput-byte           v0, LTest;->byteField:B",
+        "const               v0, 2",
+        "sput-short          v0, LTest;->shortField:S",
+        "const               v0, 3",
+        "sput                v0, LTest;->intField:I",
+        "const-wide          v0, 4",
+        "sput-wide           v0, LTest;->longField:J",
+        "const               v0, 0x40a00000",  // 5.0.
+        "sput                v0, LTest;->floatField:F",
+        "const-wide          v0, 0x4018000000000000L",  // 6.0.
+        "sput-wide           v0, LTest;->doubleField:D",
+        "const               v0, 0x37",  // ASCII 7.
+        "sput-char           v0, LTest;->charField:C",
+        "const-string        v0, \"8\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "return-void"
+    );
+    builder.addMainMethod(
+        3,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget-boolean        v1, LTest;->booleanField:Z",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Z)V",
+        "sget-byte           v1, LTest;->byteField:B",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget-short          v1, LTest;->shortField:S",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget                v1, LTest;->intField:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget-wide           v1, LTest;->longField:J",
+        "invoke-virtual      { v0, v1, v2 }, Ljava/io/PrintStream;->println(J)V",
+        "sget                v1, LTest;->floatField:F",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(F)V",
+        "sget-wide           v1, LTest;->doubleField:D",
+        "invoke-virtual      { v0, v1, v2 }, Ljava/io/PrintStream;->println(D)V",
+        "sget-char           v1, LTest;->charField:C",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(C)V",
+        "sget-object         v1, LTest;->stringField:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "return-void"
+    );
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    MethodSubject clinit = inspector.clazz("Test").clinit();
+    // The const-string and return-void instructions are left.
+    assertEquals(2, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("true\n1\n2\n3\n4\n5.0\n6.0\n7\n8\n", result);
+  }
+
+  @Test
+  public void getBeforePut() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticField("field1", "I", "1");
+    builder.addStaticField("field2", "I", "2");
+
+    builder.addStaticInitializer(
+        1,
+        "sget                v0, LTest;->field1:I",
+        "sput                v0, LTest;->field2:I",
+        "const               v0, 0",
+        "sput                v0, LTest;->field1:I",
+        "return-void"
+    );
+    builder.addMainMethod(
+        2,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget                v1, LTest;->field1:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget                v1, LTest;->field2:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "return-void"
+    );
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    MethodSubject clinit = inspector.clazz("Test").clinit();
+    // Nothing changed in the class initializer.
+    assertEquals(5, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("0\n1\n", result);
+  }
+
+  @Test
+  public void nullString() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticField("stringField", "Ljava/lang/String;", "Hello");
+
+    builder.addStaticInitializer(
+        1,
+        "const               v0, 0",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "return-void"
+    );
+    builder.addMainMethod(
+        2,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget-object         v1, LTest;->stringField:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "return-void"
+    );
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    MethodSubject clinit = inspector.clazz("Test").clinit();
+    // The return-void instruction is left.
+    assertEquals(1, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("null\n", result);
+  }
+
+  @Test
+  public void fieldOnOtherClass() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticInitializer(
+        1,
+        "const               v0, 2",
+        "sput                v0, LOther;->field:I",
+        "return-void"
+    );
+    builder.addMainMethod(
+        2,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget                v1, LOther;->field:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "return-void"
+    );
+
+    builder.addClass("Other");
+    builder.addStaticField("field", "I", "1");
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    MethodSubject clinit = inspector.clazz("Test").clinit();
+    // Nothing changed in the class initializer.
+    assertEquals(3, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("2\n", result);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 89f0f3b..3138f4e 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -124,7 +124,9 @@
 
   private static void inspectShaking1(PrintUsageInspector inspector) {
     assertTrue(inspector.clazz("shaking1.Unused").isPresent());
-    assertFalse(inspector.clazz("shaking1.Used").isPresent());
+    assertTrue(inspector.clazz("shaking1.Used").isPresent());
+    ClassSubject used = inspector.clazz("shaking1.Used").get();
+    assertTrue(used.method("void", "<clinit>", ImmutableList.of()));
   }
 
   private static void inspectShaking2(PrintUsageInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index f070130..ab9bf96 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -71,6 +71,10 @@
       this.returnType = returnType;
       this.parameterTypes = parameterTypes;
     }
+
+    public static MethodSignature staticInitializer(String clazz) {
+      return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of());
+    }
   }
 
   public static class SmaliBuilder {
@@ -210,6 +214,29 @@
       );
     }
 
+    public void addStaticField(String name, String type, String defaultValue) {
+      StringBuilder builder = new StringBuilder();
+      builder.append(".field static ");
+      builder.append(name);
+      builder.append(":");
+      builder.append(type);
+      if (defaultValue != null) {
+        builder.append(" = ");
+        if (type.equals("Ljava/lang/String;")) {
+          builder.append('"');
+          builder.append(defaultValue);
+          builder.append('"');
+        } else {
+          builder.append(defaultValue);
+        }
+      }
+      getSource(currentClassName).add(builder.toString());
+    }
+
+    public void addStaticField(String name, String type) {
+      addStaticField(name, type, null);
+    }
+
     public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
         int locals, String... instructions) {
       StringBuilder builder = new StringBuilder();
@@ -222,8 +249,30 @@
 
     public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
         int locals, String code) {
+      return addStaticMethod("", returnType, name, parameters, locals, code);
+    }
+
+    public MethodSignature addStaticInitializer(int locals, String... instructions) {
+      StringBuilder builder = new StringBuilder();
+      for (String instruction : instructions) {
+        builder.append(instruction);
+        builder.append("\n");
+      }
+      return addStaticInitializer(locals, builder.toString());
+    }
+
+    public MethodSignature addStaticInitializer(int locals, String code) {
+      return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code);
+    }
+
+    private MethodSignature addStaticMethod(String flags, String returnType, String name,
+        List<String> parameters, int locals, String code) {
       StringBuilder builder = new StringBuilder();
       builder.append(".method public static ");
+      if (flags != null && flags.length() > 0) {
+        builder.append(flags);
+        builder.append(" ");
+      }
       builder.append(name);
       builder.append("(");
       for (String parameter : parameters) {
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 61c3c14..16c791d 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -267,6 +267,10 @@
 
     public abstract MethodSubject method(String returnType, String name, List<String> parameters);
 
+    public MethodSubject clinit() {
+      return method("void", "<clinit>", ImmutableList.of());
+    }
+
     public MethodSubject method(MethodSignature signature) {
       return method(signature.type, signature.name, ImmutableList.copyOf(signature.parameters));
     }