Merge "Replace clinit static puts with initial values for static fields"
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 03df65c..4cb6736 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -126,7 +126,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));
     }