Version 0.1.10.

This merges all <clinit> related fixes, so that D8 never removed empty
<clinit> methods, and ensures correct stepping in <clinit> code.

Merge: Fix more tests
CL: https://r8-review.googlesource.com/c/r8/+/5642

Merge: Update test
CL: https://r8-review.googlesource.com/c/r8/+/5640

Merge: Don't remove empty <clinit> methods in D8
CL: https://r8-review.googlesource.com/c/r8/+/5620

Merge (resolved conflicts): Test debug stepping in <clinit>
CL: https://r8-review.googlesource.com/c/r8/+/5401

Merge (resolved conflicts): Update the <clinit> rewriting
CL: https://r8-review.googlesource.com/c/r8/+/5360
Change-Id: Ia9140f95fe2f5549eb4c663bd8bc7587e40714e7
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index dda9a39..f7e65d9 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -55,7 +55,7 @@
  */
 public final class D8 {
 
-  private static final String VERSION = "v0.1.9";
+  private static final String VERSION = "v0.1.10";
   private static final int STATUS_ERROR = 1;
 
   private D8() {}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index a79a5cd..edfe01e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -71,7 +71,7 @@
 
 public class R8 {
 
-  private static final String VERSION = "v0.1.9";
+  private static final String VERSION = "v0.1.10";
   private final Timing timing = new Timing("R8");
   private final InternalOptions options;
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 8bd13f0..edac717 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -81,6 +81,17 @@
     this.debugInfo = debugInfo;
   }
 
+  public boolean hasDebugPositions() {
+    if (debugInfo != null) {
+      for (DexDebugEvent event : debugInfo.events) {
+        if (event instanceof DexDebugEvent.Default) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
   public DexDebugInfo debugInfoWithAdditionalFirstParameter(DexString name) {
     if (debugInfo == null) {
       return null;
@@ -142,7 +153,7 @@
     return false;
   }
 
-  boolean isEmptyVoidMethod() {
+  public boolean isEmptyVoidMethod() {
     return instructions.length == 1 && instructions[0] instanceof ReturnVoid;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index e66b984..493b56f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -206,6 +206,11 @@
     code = null;
   }
 
+  public boolean hasDebugPositions() {
+    assert code != null && code.isDexCode();
+    return code.asDexCode().hasDebugPositions();
+  }
+
   public String qualifiedName() {
     return method.qualifiedName();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 08c8a1c..73b3242 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -151,6 +151,10 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueByte(value);
     }
 
+    public byte getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeHeader(VALUE_BYTE, 0, dest);
@@ -196,6 +200,10 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueShort(value);
     }
 
+    public short getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeIntegerTo(VALUE_SHORT, value, Short.BYTES, dest);
@@ -240,6 +248,10 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueChar(value);
     }
 
+    public char getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       dest.forward(1);
@@ -288,6 +300,10 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueInt(value);
     }
 
+    public int getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeIntegerTo(VALUE_INT, value, Integer.BYTES, dest);
@@ -332,6 +348,10 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueLong(value);
     }
 
+    public long getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeIntegerTo(VALUE_LONG, value, Long.BYTES, dest);
@@ -376,6 +396,10 @@
       return Float.compare(value, DEFAULT.value) == 0 ? DEFAULT : new DexValueFloat(value);
     }
 
+    public float getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       dest.forward(1);
@@ -420,6 +444,10 @@
       return Double.compare(value, DEFAULT.value) == 0 ? DEFAULT : new DexValueDouble(value);
     }
 
+    public double getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       dest.forward(1);
@@ -508,6 +536,10 @@
       super(value);
     }
 
+    public DexString getValue() {
+      return value;
+    }
+
     @Override
     protected byte getValueKind() {
       return VALUE_STRING;
@@ -710,6 +742,10 @@
     private DexValueNull() {
     }
 
+    public Object getValue() {
+      return null;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeHeader(VALUE_NULL, 0, dest);
@@ -751,6 +787,10 @@
       return value ? TRUE : FALSE;
     }
 
+    public boolean getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeHeader(VALUE_BOOLEAN, value ? 1 : 0, dest);
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 fad378e..357c2ce 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
@@ -233,9 +233,6 @@
     }
 
     ThreadUtils.awaitFutures(futures);
-
-    // Get rid of <clinit> methods with no code.
-    removeEmptyClassInitializers();
   }
 
   private void convertMethodToDex(DexEncodedMethod method) {
@@ -281,9 +278,6 @@
     }, executorService);
     timing.end();
 
-    // Get rid of <clinit> methods with no code.
-    removeEmptyClassInitializers();
-
     // Build a new application with jumbo string info.
     Builder builder = new Builder(application);
     builder.setHighestSortingString(highestSortingString);
@@ -325,16 +319,6 @@
     return builder.build();
   }
 
-  private void removeEmptyClassInitializers() {
-    application.classes().forEach(this::removeEmptyClassInitializer);
-  }
-
-  private void removeEmptyClassInitializer(DexProgramClass clazz) {
-    if (clazz.hasTrivialClassInitializer()) {
-      clazz.removeStaticMethod(clazz.getClassInitializer());
-    }
-  }
-
   private void clearDexMethodCompilationState() {
     application.classes().forEach(this::clearDexMethodCompilationState);
   }
@@ -473,7 +457,9 @@
     codeRewriter.foldConstants(code);
     codeRewriter.rewriteSwitch(code);
     codeRewriter.simplifyIf(code);
-    codeRewriter.collectClassInitializerDefaults(method, code);
+    if (!options.debug) {
+      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 0c75ef3..ff350a6 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
@@ -68,8 +68,11 @@
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -660,7 +663,8 @@
     if (exit == null) {
       return;
     }
-    Map<DexField, StaticPut> puts = Maps.newIdentityHashMap();
+    Set<StaticPut> puts = Sets.newIdentityHashSet();
+    Map<DexField, StaticPut> dominatingPuts = Maps.newIdentityHashMap();
     for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
       InstructionListIterator iterator = block.listIterator(block.getInstructions().size());
       while (iterator.hasPrevious()) {
@@ -674,22 +678,27 @@
                   && put.inValue().getConstInstruction().isConstNumber() &&
                   put.inValue().getConstInstruction().asConstNumber().isZero()) {
                 // Collect put of zero as a potential default value.
-                puts.put(put.getField(), put);
+                dominatingPuts.putIfAbsent(put.getField(), put);
+                puts.add(put);
               } else if (field.type.isPrimitiveType() || field.type == dexItemFactory.stringType) {
                 // Collect put as a potential default value.
-                puts.put(put.getField(), put);
+                dominatingPuts.putIfAbsent(put.getField(), put);
+                puts.add(put);
               }
             } else if (isClassNameConstant(method, put)) {
               // Collect put of class name constant as a potential default value.
-              puts.put(put.getField(), put);
+              dominatingPuts.putIfAbsent(put.getField(), put);
+              puts.add(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());
+          DexField field = current.asStaticGet().getField();
+          if (dominatingPuts.containsKey(field)) {
+            dominatingPuts.remove(field);
+            Iterables.removeIf(puts, put -> put.getField() == field);
           }
         }
       }
@@ -697,7 +706,7 @@
 
     if (!puts.isEmpty()) {
       // Set initial values for static fields from the definitive static put instructions collected.
-      for (StaticPut put : puts.values()) {
+      for (StaticPut put : dominatingPuts.values()) {
         DexField field = put.getField();
         DexEncodedField encodedField = appInfo.definitionFor(field);
         if (field.type == dexItemFactory.stringType) {
@@ -758,7 +767,7 @@
         InstructionListIterator iterator = block.listIterator();
         while (iterator.hasNext()) {
           Instruction current = iterator.next();
-          if (current.isStaticPut() && puts.values().contains(current.asStaticPut())) {
+          if (current.isStaticPut() && puts.contains(current.asStaticPut())) {
             iterator.remove();
             // Collect, for removal, the instruction that created the value for the static put,
             // if all users are gone. This is done even if these instructions can throw as for
diff --git a/src/test/debugTestResources/ClassInitializerAssignmentInitialization.java b/src/test/debugTestResources/ClassInitializerAssignmentInitialization.java
new file mode 100644
index 0000000..fa6078a
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerAssignmentInitialization.java
@@ -0,0 +1,16 @@
+// 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.
+
+public class ClassInitializerAssignmentInitialization {
+
+  static int x = 1;
+  static int y;
+
+  static int z = 2;
+
+  public static void main(String[] args) {
+    System.out.println("x=" + x);
+    System.out.println("y=" + y);
+  }
+}
diff --git a/src/test/debugTestResources/ClassInitializerEmpty.java b/src/test/debugTestResources/ClassInitializerEmpty.java
new file mode 100644
index 0000000..9062d62
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerEmpty.java
@@ -0,0 +1,12 @@
+// 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.
+
+public class ClassInitializerEmpty {
+
+  static {
+  }
+
+  public static void main(String[] args) {
+  }
+}
diff --git a/src/test/debugTestResources/ClassInitializerMixedInitialization.java b/src/test/debugTestResources/ClassInitializerMixedInitialization.java
new file mode 100644
index 0000000..504db6c
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerMixedInitialization.java
@@ -0,0 +1,25 @@
+// 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.
+
+public class ClassInitializerMixedInitialization {
+
+  static boolean b;
+  static int x = 1;
+  static int y;
+
+  static {
+    x = 2;
+    if (b) {
+      y = 1;
+    } else {
+      y = 2;
+    }
+    x = 3;
+  }
+
+  public static void main(String[] args) {
+    System.out.println("x=" + x);
+    System.out.println("y=" + y);
+  }
+}
diff --git a/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java b/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java
new file mode 100644
index 0000000..78fe9a0
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java
@@ -0,0 +1,26 @@
+// 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.
+
+public class ClassInitializerStaticBlockInitialization {
+
+  static boolean b;
+  static int x;
+  static int y;
+
+  static {
+    x = 1;
+    x = 2;
+    if (b) {
+      y = 1;
+    } else {
+      y = 2;
+    }
+    x = 3;
+  }
+
+  public static void main(String[] args) {
+    System.out.println("x=" + x);
+    System.out.println("y=" + y);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index eee820f..8b83058 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -14,15 +14,11 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
-import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
-import com.android.tools.r8.shaking.RootSetBuilder;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -93,6 +89,10 @@
       return compareTo(other) > 0;
     }
 
+    public boolean isOlderThanOrEqual(DexVm other) {
+      return compareTo(other) <= 0;
+    }
+
     private DexVm(String shortName) {
       this.shortName = shortName;
     }
diff --git a/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java b/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java
new file mode 100644
index 0000000..e90bca7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java
@@ -0,0 +1,110 @@
+// 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.debug;
+
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Test;
+
+public class ClassInitializationTest extends DebugTestBase {
+
+  @Test
+  public void testStaticAssingmentInitialization() throws Throwable {
+    final String SOURCE_FILE = "ClassInitializerAssignmentInitialization.java";
+    final String CLASS = "ClassInitializerAssignmentInitialization";
+
+    runDebugTest(CLASS,
+        breakpoint(CLASS, "<clinit>"),
+        run(),
+        checkLine(SOURCE_FILE, 7),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+        checkStaticFieldClinitSafe(CLASS, "z", null, Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 10),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+        checkStaticFieldClinitSafe(CLASS, "z", null, Value.createInt(0)),
+        breakpoint(CLASS, "main"),
+        run(),
+        checkStaticField(CLASS, "x", null, Value.createInt(1)),
+        checkStaticField(CLASS, "y", null, Value.createInt(0)),
+        checkStaticField(CLASS, "z", null, Value.createInt(2)),
+        run());
+  }
+
+  @Test
+  public void testBreakpointInEmptyClassInitializer() throws Throwable {
+    final String SOURCE_FILE = "ClassInitializerEmpty.java";
+    final String CLASS = "ClassInitializerEmpty";
+
+    runDebugTest(CLASS,
+        breakpoint(CLASS, "<clinit>"),
+        run(),
+        checkLine(SOURCE_FILE, 8),
+        run());
+  }
+
+  @Test
+  public void testStaticBlockInitialization() throws Throwable {
+    final String SOURCE_FILE = "ClassInitializerStaticBlockInitialization.java";
+    final String CLASS = "ClassInitializerStaticBlockInitialization";
+
+    runDebugTest(CLASS,
+        breakpoint(CLASS, "<clinit>"),
+        run(),
+        checkLine(SOURCE_FILE, 12),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 13),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 14),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 17),
+        stepOver(),
+        checkLine(SOURCE_FILE, 19),
+        breakpoint(CLASS, "main"),
+        run(),
+        checkLine(SOURCE_FILE, 23),
+        checkStaticField(CLASS, "x", null, Value.createInt(3)),
+        run());
+  }
+
+  @Test
+  public void testStaticMixedInitialization() throws Throwable {
+    final String SOURCE_FILE = "ClassInitializerMixedInitialization.java";
+    final String CLASS = "ClassInitializerMixedInitialization";
+
+    runDebugTest(CLASS,
+        breakpoint(CLASS, "<clinit>"),
+        run(),
+        checkLine(SOURCE_FILE, 8),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 12),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 13),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 16),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 18),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(2)),
+        breakpoint(CLASS, "main"),
+        run(),
+        checkLine(SOURCE_FILE, 22),
+        checkStaticField(CLASS, "x", null, Value.createInt(3)),
+        checkStaticField(CLASS, "y", null, Value.createInt(2)),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 6340728..106aab4 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -10,9 +10,12 @@
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OffOrAuto;
 import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.longs.LongList;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -62,8 +65,10 @@
 import org.junit.rules.TestName;
 
 /**
+ * Base class for debugging tests.
  *
- * Base class for debugging tests
+ * The protocol messages are described here:
+ * https://docs.oracle.com/javase/8/docs/platform/jpda/jdwp/jdwp-protocol.html
  */
 public abstract class DebugTestBase {
 
@@ -347,6 +352,27 @@
     });
   }
 
+  protected final JUnit3Wrapper.Command checkStaticFieldClinitSafe(
+      String className, String fieldName, String fieldSignature, Value expectedValue) {
+    return inspect(t -> {
+      // TODO(65148874): The current Art from AOSP master hangs when requesting static fields
+      // when breaking in <clinit>. Last known good version is 7.0.0.
+      Assume.assumeTrue(
+          "Skipping test " + testName.getMethodName() + " because ART version is not supported",
+          isRunningJava() || ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_7_0_0));
+      checkStaticField(className, fieldName, fieldSignature, expectedValue);
+    });
+  }
+
+  protected final JUnit3Wrapper.Command checkStaticField(
+      String className, String fieldName, String fieldSignature, Value expectedValue) {
+    return inspect(t -> {
+      Value value = t.getStaticField(className, fieldName, fieldSignature);
+      Assert.assertEquals("Incorrect value for static '" + className + "." + fieldName + "'",
+          expectedValue, value);
+    });
+  }
+
   protected final JUnit3Wrapper.Command inspect(Consumer<JUnit3Wrapper.DebuggeeState> inspector) {
     return t -> inspector.accept(t.debuggeeState);
   }
@@ -561,6 +587,7 @@
     public static class DebuggeeState implements FrameInspector {
 
       private class DebuggeeFrame implements FrameInspector {
+
         private final long frameId;
         private final Location location;
 
@@ -819,6 +846,77 @@
       public String getMethodSignature() {
         return getTopFrame().getMethodSignature();
       }
+
+      public Value getStaticField(String className, String fieldName, String fieldSignature) {
+        String classSignature = DescriptorUtils.javaTypeToDescriptor(className);
+        byte typeTag = TypeTag.CLASS;
+        long classId = getMirror().getClassID(classSignature);
+        Assert.assertFalse("No class named " + className + " found", classId == -1);
+
+        // The class is available, lookup and read the field.
+        long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
+        return getField(getMirror(), classId, fieldId);
+      }
+
+      private long findField(VmMirror mirror, long classId, String fieldName,
+          String fieldSignature) {
+
+        boolean withGenericSignature = true;
+        CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+            ReferenceTypeCommandSet.FieldsWithGenericCommand);
+        commandPacket.setNextValueAsReferenceTypeID(classId);
+        ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+        if (replyPacket.getErrorCode() != Error.NONE) {
+          // Retry with older command ReferenceType.Fields.
+          withGenericSignature = false;
+          commandPacket.setCommand(ReferenceTypeCommandSet.FieldsCommand);
+          replyPacket = mirror.performCommand(commandPacket);
+          assert replyPacket.getErrorCode() == Error.NONE;
+        }
+
+        int fieldsCount = replyPacket.getNextValueAsInt();
+        LongList matchingFieldIds = new LongArrayList();
+        for (int i = 0; i < fieldsCount; ++i) {
+          long currentFieldId = replyPacket.getNextValueAsFieldID();
+          String currentFieldName = replyPacket.getNextValueAsString();
+          String currentFieldSignature = replyPacket.getNextValueAsString();
+          if (withGenericSignature) {
+            replyPacket.getNextValueAsString(); // Skip generic signature.
+          }
+          replyPacket.getNextValueAsInt(); // Skip modifiers.
+
+          // Filter fields based on name (and signature if there is).
+          if (fieldName.equals(currentFieldName)) {
+            if (fieldSignature == null || fieldSignature.equals(currentFieldSignature)) {
+              matchingFieldIds.add(currentFieldId);
+            }
+          }
+        }
+        Assert.assertTrue(replyPacket.isAllDataRead());
+
+        Assert.assertFalse("No field named " + fieldName + " found", matchingFieldIds.isEmpty());
+        // There must be only one matching field.
+        Assert.assertEquals("More than 1 field found: please specify a signature", 1,
+            matchingFieldIds.size());
+        return matchingFieldIds.getLong(0);
+      }
+
+      private Value getField(VmMirror mirror, long classId, long fieldId) {
+
+        CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+            ReferenceTypeCommandSet.GetValuesCommand);
+        commandPacket.setNextValueAsReferenceTypeID(classId);
+        commandPacket.setNextValueAsInt(1);
+        commandPacket.setNextValueAsFieldID(fieldId);
+        ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+        assert replyPacket.getErrorCode() == Error.NONE;
+
+        int fieldsCount = replyPacket.getNextValueAsInt();
+        assert fieldsCount == 1;
+        Value result = replyPacket.getNextValueAsValue();
+        Assert.assertTrue(replyPacket.isAllDataRead());
+        return result;
+      }
     }
 
     private static boolean inScope(long index, Variable var) {
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
index 195c3aa..58d2b77 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -7,7 +7,21 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.Sput;
+import com.android.tools.r8.code.SputObject;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexValue;
+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.DexValueShort;
+import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
@@ -80,7 +94,55 @@
     DexApplication processedApplication = processApplication(originalApplication, options);
 
     DexInspector inspector = new DexInspector(processedApplication);
-    assertFalse(inspector.clazz("Test").clinit().isPresent());
+    // Test is running without tree-shaking, so the empty <clinit> is not removed.
+    assertTrue(
+        inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
+
+    DexValue value;
+    assertTrue(inspector.clazz("Test").field("boolean", "booleanField").hasStaticValue());
+    value = inspector.clazz("Test").field("boolean", "booleanField").getStaticValue();
+    assertTrue(value instanceof DexValueBoolean);
+    assertEquals(true, ((DexValueBoolean) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("byte", "byteField").hasStaticValue());
+    value = inspector.clazz("Test").field("byte", "byteField").getStaticValue();
+    assertTrue(value instanceof DexValueByte);
+    assertEquals(1, ((DexValueByte) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("short", "shortField").hasStaticValue());
+    value = inspector.clazz("Test").field("short", "shortField").getStaticValue();
+    assertTrue(value instanceof DexValueShort);
+    assertEquals(2, ((DexValueShort) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+    value = inspector.clazz("Test").field("int", "intField").getStaticValue();
+    assertTrue(value instanceof DexValueInt);
+    assertEquals(3, ((DexValueInt) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("long", "longField").hasStaticValue());
+    value = inspector.clazz("Test").field("long", "longField").getStaticValue();
+    assertTrue(value instanceof DexValueLong);
+    assertEquals(4, ((DexValueLong) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("float", "floatField").hasStaticValue());
+    value = inspector.clazz("Test").field("float", "floatField").getStaticValue();
+    assertTrue(value instanceof DexValueFloat);
+    assertEquals(5.0f, ((DexValueFloat) value).getValue(), 0.0);
+
+    assertTrue(inspector.clazz("Test").field("double", "doubleField").hasStaticValue());
+    value = inspector.clazz("Test").field("double", "doubleField").getStaticValue();
+    assertTrue(value instanceof DexValueDouble);
+    assertEquals(6.0f, ((DexValueDouble) value).getValue(), 0.0);
+
+    assertTrue(inspector.clazz("Test").field("char", "charField").hasStaticValue());
+    value = inspector.clazz("Test").field("char", "charField").getStaticValue();
+    assertTrue(value instanceof DexValueChar);
+    assertEquals(0x30 + 7, ((DexValueChar) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+    value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
+    assertTrue(value instanceof DexValueString);
+    assertEquals(("8"), ((DexValueString) value).getValue().toString());
 
     String result = runArt(processedApplication, options);
 
@@ -159,7 +221,9 @@
     DexApplication processedApplication = processApplication(originalApplication, options);
 
     DexInspector inspector = new DexInspector(processedApplication);
-    assertFalse(inspector.clazz("Test").clinit().isPresent());
+    // Test is running without tree-shaking, so the empty <clinit> is not removed.
+    assertTrue(
+        inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
 
     String result = runArt(processedApplication, options);
 
@@ -200,7 +264,9 @@
     DexApplication processedApplication = processApplication(originalApplication, options);
 
     DexInspector inspector = new DexInspector(processedApplication);
-    assertFalse(inspector.clazz("Test").clinit().isPresent());
+    // Test is running without tree-shaking, so the empty <clinit> is not removed.
+    assertTrue(
+        inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
 
     String result = runArt(processedApplication, options);
 
@@ -208,6 +274,152 @@
   }
 
   @Test
+  public void testMultiplePuts() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticField("intField", "I");
+    builder.addStaticField("stringField", "Ljava/lang/String;");
+
+    builder.addStaticInitializer(
+        1,
+        "const               v0, 0",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"4\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "const               v0, 1",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"5\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "const               v0, 2",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"6\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "const               v0, 3",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"7\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "return-void"
+    );
+    builder.addMainMethod(
+        2,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget                v1, LTest;->intField:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)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);
+    // Test is running without tree-shaking, so the empty <clinit> is not removed.
+    assertTrue(
+        inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
+
+    DexValue value;
+    assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+    value = inspector.clazz("Test").field("int", "intField").getStaticValue();
+    assertTrue(value instanceof DexValueInt);
+    assertEquals(3, ((DexValueInt) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+    value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
+    assertTrue(value instanceof DexValueString);
+    assertEquals(("7"), ((DexValueString) value).getValue().toString());
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("3\n7\n", result);
+  }
+
+
+  @Test
+  public void testMultiplePutsWithControlFlow() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticField("booleanField", "Z");
+    builder.addStaticField("intField", "I");
+    builder.addStaticField("intField2", "I");
+    builder.addStaticField("stringField", "Ljava/lang/String;");
+
+    builder.addStaticInitializer(
+        1,
+        "const               v0, 0",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"4\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "const               v0, 1",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"5\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "sget-boolean        v0, LTest;->booleanField:Z",
+        "if-eqz              v0, :label_1",
+        "const               v0, 8",
+        "sput                v0, LTest;->intField2:I",
+        ":label_1",
+        "const               v0, 9",
+        "sput                v0, LTest;->intField2:I",
+        "goto                :label_2",
+        ":label_2",
+        "const               v0, 2",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"6\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "const               v0, 3",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"7\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "return-void"
+    );
+    builder.addMainMethod(
+        2,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget                v1, LTest;->intField:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)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);
+    assertTrue(inspector.clazz("Test").clinit().isPresent());
+
+    DexValue value;
+    assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+    value = inspector.clazz("Test").field("int", "intField").getStaticValue();
+    assertTrue(value instanceof DexValueInt);
+    assertEquals(3, ((DexValueInt) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+    value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
+    assertTrue(value instanceof DexValueString);
+    assertEquals(("7"), ((DexValueString) value).getValue().toString());
+
+    DexCode code = inspector.clazz("Test").clinit().getMethod().getCode().asDexCode();
+    for (Instruction instruction : code.instructions) {
+      if (instruction instanceof Sput) {
+        Sput put = (Sput) instruction;
+        // Only int put ot intField2.
+        assertEquals(put.getField().name.toString(), "intField2");
+      } else {
+        // No Object (String) puts.
+        assertFalse(instruction instanceof SputObject);
+      }
+    }
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("3\n7\n", result);
+  }
+
+  @Test
   public void testInitializationToOwnClassName() {
     String className = "org.example.Test";
     SmaliBuilder builder = new SmaliBuilder(className);
@@ -266,7 +478,9 @@
 
     DexInspector inspector = new DexInspector(processedApplication);
     assertTrue(inspector.clazz(className).isPresent());
-    assertFalse(inspector.clazz(className).clinit().isPresent());
+    // Test is running without tree-shaking, so the empty <clinit> is not removed.
+    assertTrue(
+        inspector.clazz(className).clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
 
     String result = runArt(processedApplication, options, className);
 
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/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 51b6ab5..ba16e33 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -150,6 +150,9 @@
         used.method("java.lang.String", "aMethodThatIsNotUsedButKept", Collections.emptyList())
             .isPresent());
     Assert.assertTrue(used.field("int", "aStaticFieldThatIsNotUsedButKept").isPresent());
+    // Rewriting of <clinit> moves the initialization of aStaticFieldThatIsNotUsedButKept
+    // from <clinit> code into statics value section of the dex file.
+    Assert.assertFalse(used.clinit().isPresent());
   }
 
   public static void shaking1IsCorrectlyRepackaged(DexInspector inspector) {
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 0ad240c..26de96c 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -56,6 +56,7 @@
 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;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNaming;
 import com.android.tools.r8.naming.MemberNaming;
@@ -691,8 +692,11 @@
   }
 
   public abstract class FieldSubject extends MemberSubject {
+    public abstract boolean hasStaticValue();
 
     public abstract DexEncodedField getField();
+
+    public abstract DexValue getStaticValue();
   }
 
   public class AbsentFieldSubject extends FieldSubject {
@@ -733,6 +737,16 @@
     }
 
     @Override
+    public boolean hasStaticValue() {
+      return false;
+    }
+
+    @Override
+    public DexValue getStaticValue() {
+      return null;
+    }
+
+    @Override
     public DexEncodedField getField() {
       return null;
     }
@@ -791,6 +805,16 @@
     }
 
     @Override
+    public boolean hasStaticValue() {
+      return dexField.staticValue != null;
+    }
+
+    @Override
+    public DexValue getStaticValue() {
+      return dexField.staticValue;
+    }
+
+    @Override
     public DexEncodedField getField() {
       return dexField;
     }