|  | // 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.jasmin; | 
|  |  | 
|  | import static org.junit.Assert.assertEquals; | 
|  | import static org.junit.Assert.assertFalse; | 
|  | import static org.junit.Assert.assertTrue; | 
|  |  | 
|  | import com.android.tools.r8.ToolHelper; | 
|  | import com.android.tools.r8.debuginfo.DebugInfoInspector; | 
|  | import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion; | 
|  | import com.android.tools.r8.naming.MemberNaming.MethodSignature; | 
|  | import com.android.tools.r8.utils.AndroidApp; | 
|  | import com.android.tools.r8.utils.StringUtils; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import org.junit.Test; | 
|  |  | 
|  | public class InvalidDebugInfoTests extends JasminTestBase { | 
|  |  | 
|  | // This is a regression test for invalid live-ranges of locals generated by some old Java | 
|  | // compilers. The issue is that a local slot may have been initialized outside the live-scope of | 
|  | // the variable and then the subsequent live-scope of the variable extends beyond its actual | 
|  | // liveness. In the example below the variable 'y' is initialized outside its range (it is thus | 
|  | // associated with the local 'x' (the SSA value is unaffected by the istore). Finally the 'return' | 
|  | // forces a read of all supposedly live variables before exiting. Here the attempt to read 'y' | 
|  | // will actually be a read of 'x'. | 
|  | @Test | 
|  | public void testInvalidInfoThrow() throws Exception { | 
|  | JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4); | 
|  | JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); | 
|  |  | 
|  | clazz.addStaticMethod("foo", ImmutableList.of("I"), "V", | 
|  | ".limit stack 3", | 
|  | ".limit locals 4", | 
|  | ".var 0 is x I from LabelInit to LabelExit", | 
|  | ".var 1 is y I from LabelLocalStart to LabelExit", | 
|  | ".var 2 is e Ljava/lang/Exception; from LabelCatchStart to LabelCatchEnd", | 
|  | // var 3 is the jsr address | 
|  | "LabelInit:", | 
|  | "LabelTryStart:", | 
|  | "  ldc 84", | 
|  | "  iload 0", | 
|  | "  dup", | 
|  | "  istore 1", // init local[1] to value of local[0] (eg, 'x' since 'y' is not live yet). | 
|  | "  idiv", | 
|  | "  istore 0", | 
|  | "LabelLocalStart:", | 
|  | "  jsr LabelPrint", | 
|  | "  goto LabelExit", | 
|  | "LabelTryEnd:", | 
|  | "LabelCatchStart:", | 
|  | "  astore 2", | 
|  | "  jsr LabelPrint", | 
|  | "  return", // y is not actually live here. | 
|  | "LabelCatchEnd:", | 
|  | "LabelPrint:", | 
|  | "  astore 3", | 
|  | "  getstatic java/lang/System/out Ljava/io/PrintStream;", | 
|  | "  iload 0", | 
|  | "  invokevirtual java/io/PrintStream/println(I)V", | 
|  | "  ret 3", | 
|  | "LabelExit:", | 
|  | "  return", | 
|  | ".catch java/lang/Exception from LabelTryStart to LabelTryEnd using LabelCatchStart" | 
|  | ); | 
|  |  | 
|  | clazz.addMainMethod( | 
|  | ".limit stack 1", | 
|  | ".limit locals 1", | 
|  | "  ldc 2", | 
|  | "  invokestatic Test/foo(I)V", | 
|  | "  ldc 0", | 
|  | "  invokestatic Test/foo(I)V", | 
|  | "  return"); | 
|  |  | 
|  | String expected = "42" + ToolHelper.LINE_SEPARATOR + "0" + ToolHelper.LINE_SEPARATOR; | 
|  | String javaResult = runOnJava(builder, clazz.name); | 
|  | assertEquals(expected, javaResult); | 
|  | String artResult = runOnArtD8(builder, clazz.name); | 
|  | assertEquals(expected, artResult); | 
|  | } | 
|  |  | 
|  | // Regression test to check that we properly add UninitializedLocal SSA values for methods that | 
|  | // have arguments without local info. To witness this bug, we also need "invalid" debug info, eg, | 
|  | // in this test the scope of "y" (local 2) spans the exceptional edge in which it is not live. | 
|  | @Test | 
|  | public void testInvalidInfoBug37722432() throws Exception { | 
|  | JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4); | 
|  | JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); | 
|  |  | 
|  | clazz.addStaticMethod("foo", ImmutableList.of("I", "I"), "V", | 
|  | ".limit stack 2", | 
|  | ".limit locals 3", | 
|  | ".var 0 is x I from LabelInit to LabelExit", | 
|  | // Synthesized arg (no local info) | 
|  | ".var 2 is y I from LabelLocalStart to LabelExit", | 
|  | ".catch java/lang/Exception from LabelInit to LabelCatch using LabelCatch", | 
|  | "LabelInit:", // Start of try block targets catch with a state excluding 'y'. | 
|  | "  ldc 84", | 
|  | "  iload 0", | 
|  | "  idiv", | 
|  | "  istore 2", | 
|  | "LabelLocalStart:", | 
|  | "  getstatic java/lang/System/out Ljava/io/PrintStream;", | 
|  | "  iload 2", | 
|  | "  invokevirtual java/io/PrintStream/println(I)V", | 
|  | "  return", | 
|  | "LabelCatch:", // Catch target appears to include 'y' but actually does not. | 
|  | "  pop", | 
|  | "  return", | 
|  | "LabelExit:" | 
|  | ); | 
|  |  | 
|  | clazz.addMainMethod( | 
|  | ".limit stack 2", | 
|  | ".limit locals 1", | 
|  | "  ldc 2", | 
|  | "  ldc 2", | 
|  | "  invokestatic Test/foo(II)V", | 
|  | "  ldc 0", | 
|  | "  ldc 0", | 
|  | "  invokestatic Test/foo(II)V", | 
|  | "  return"); | 
|  |  | 
|  | String expected = "42" + ToolHelper.LINE_SEPARATOR; | 
|  | String javaResult = runOnJava(builder, clazz.name); | 
|  | assertEquals(expected, javaResult); | 
|  | String artResult = runOnArtD8(builder, clazz.name); | 
|  | assertEquals(expected, artResult); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void invalidInfoBug63412730_onWrite() throws Throwable { | 
|  | JasminBuilder builder = new JasminBuilder(); | 
|  | JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); | 
|  | MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V", | 
|  | ".limit stack 3", | 
|  | ".limit locals 2", | 
|  | ".var 1 is i I from LI to End", | 
|  | ".var 0 is f F from LF to End", | 
|  | "Init:", | 
|  | "  ldc 42", | 
|  | "  istore 0", | 
|  | ".line 1", | 
|  | "LI:", | 
|  | "  ldc 7.5", | 
|  | "  fstore 1", | 
|  | ".line 2", | 
|  | "LF:", | 
|  | "  getstatic java/lang/System/out Ljava/io/PrintStream;", | 
|  | "  dup", | 
|  | "  iload 0", | 
|  | "  invokevirtual java/io/PrintStream/println(I)V", | 
|  | "  fload 1", | 
|  | "  invokevirtual java/io/PrintStream/println(F)V", | 
|  | ".line 3", | 
|  | "  return", | 
|  | "End:"); | 
|  |  | 
|  | clazz.addMainMethod( | 
|  | ".limit stack 1", | 
|  | ".limit locals 1", | 
|  | "  invokestatic Test/bar()V", | 
|  | "  return"); | 
|  |  | 
|  | String expected = StringUtils.lines("42", "7.5"); | 
|  | String javaResult = runOnJava(builder, clazz.name); | 
|  | assertEquals(expected, javaResult); | 
|  | AndroidApp app = compileWithD8(builder); | 
|  | String artResult = runOnArt(app, clazz.name); | 
|  | assertEquals(expected, artResult); | 
|  | DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method); | 
|  | assertFalse(info.hasLocalsInfo()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void invalidInfoBug63412730_onRead() throws Throwable { | 
|  | JasminBuilder builder = new JasminBuilder(); | 
|  | JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); | 
|  | MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V", | 
|  | ".limit stack 3", | 
|  | ".limit locals 2", | 
|  | ".var 1 is i I from Locals to End", | 
|  | ".var 0 is f F from Locals to End", | 
|  | "Init:", | 
|  | "  ldc 42", | 
|  | "  istore 0", | 
|  | ".line 1", | 
|  | "  ldc 7.5", | 
|  | "  fstore 1", | 
|  | ".line 2", | 
|  | "Locals:", | 
|  | "  getstatic java/lang/System/out Ljava/io/PrintStream;", | 
|  | "  dup", | 
|  | "  iload 0", | 
|  | "  invokevirtual java/io/PrintStream/println(I)V", | 
|  | "  fload 1", | 
|  | "  invokevirtual java/io/PrintStream/println(F)V", | 
|  | ".line 3", | 
|  | "  return", | 
|  | "End:"); | 
|  |  | 
|  | clazz.addMainMethod( | 
|  | ".limit stack 1", | 
|  | ".limit locals 1", | 
|  | "  invokestatic Test/bar()V", | 
|  | "  return"); | 
|  |  | 
|  | String expected = StringUtils.lines("42", "7.5"); | 
|  | String javaResult = runOnJava(builder, clazz.name); | 
|  | assertEquals(expected, javaResult); | 
|  | AndroidApp app = compileWithD8(builder); | 
|  | String artResult = runOnArt(app, clazz.name); | 
|  | assertEquals(expected, artResult); | 
|  | DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method); | 
|  | assertFalse(info.hasLocalsInfo()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void invalidInfoBug63412730_onMove() throws Throwable { | 
|  | JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4); | 
|  | JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); | 
|  | MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V", | 
|  | ".limit stack 3", | 
|  | ".limit locals 2", | 
|  | ".var 1 is i I from LI to End", | 
|  | ".var 0 is j F from LJ to End", | 
|  | "Init:", | 
|  | "  ldc 42", | 
|  | "  istore 0", | 
|  | ".line 1", | 
|  | "LI:", | 
|  | "  ldc 0", | 
|  | "  ldc 0", | 
|  | "  ifeq LZ", | 
|  | "  ldc 75", | 
|  | "  istore 1", | 
|  | ".line 2", | 
|  | "LJ:", | 
|  | "  getstatic java/lang/System/out Ljava/io/PrintStream;", | 
|  | "  iload 1", | 
|  | "  invokevirtual java/io/PrintStream/println(I)V", | 
|  | ".line 3", | 
|  | "  return", | 
|  | "LZ:", | 
|  | "  getstatic java/lang/System/out Ljava/io/PrintStream;", | 
|  | "  iload 0", | 
|  | "  invokevirtual java/io/PrintStream/println(I)V", | 
|  | ".line 4", | 
|  | "  return", | 
|  | "End:"); | 
|  |  | 
|  | clazz.addMainMethod( | 
|  | ".limit stack 1", | 
|  | ".limit locals 1", | 
|  | "  invokestatic Test/bar()V", | 
|  | "  return"); | 
|  |  | 
|  | String expected = StringUtils.lines("42"); | 
|  | String javaResult = runOnJava(builder, clazz.name); | 
|  | assertEquals(expected, javaResult); | 
|  | AndroidApp app = compileWithD8(builder); | 
|  | String artResult = runOnArt(app, clazz.name); | 
|  | assertEquals(expected, artResult); | 
|  | DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method); | 
|  | assertFalse(info.hasLocalsInfo()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void invalidInfoBug63412730_onPop() throws Throwable { | 
|  | JasminBuilder builder = new JasminBuilder(); | 
|  | JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); | 
|  | MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V", | 
|  | ".limit stack 3", | 
|  | ".limit locals 2", | 
|  | ".var 1 is a [Ljava/lang/Object; from Locals to End", | 
|  | ".var 0 is o LObject; from Locals to End", | 
|  | "Init:", | 
|  | "  ldc 1", | 
|  | "  anewarray java/lang/Object", | 
|  | "  astore 0", | 
|  | ".line 1", | 
|  | "  new java/lang/Integer", | 
|  | "  dup", | 
|  | "  ldc 42", | 
|  | "  invokespecial java/lang/Integer/<init>(I)V", | 
|  | "  astore 1", | 
|  | ".line 2", | 
|  | "Locals:", | 
|  | "  aload 0", | 
|  | "  ldc 0", | 
|  | "  aload 1", | 
|  | "  aastore", | 
|  | ".line 3", | 
|  | "  getstatic java/lang/System/out Ljava/io/PrintStream;", | 
|  | "  aload 0", | 
|  | "  ldc 0", | 
|  | "  aaload", | 
|  | "  invokevirtual java/io/PrintStream/println(Ljava/lang/Object;)V", | 
|  | ".line 4", | 
|  | "  return", | 
|  | "End:"); | 
|  |  | 
|  | clazz.addMainMethod( | 
|  | ".limit stack 1", | 
|  | ".limit locals 1", | 
|  | "  invokestatic Test/bar()V", | 
|  | "  return"); | 
|  |  | 
|  | String expected = StringUtils.lines("42"); | 
|  | String javaResult = runOnJava(builder, clazz.name); | 
|  | assertEquals(expected, javaResult); | 
|  | AndroidApp app = compileWithD8(builder); | 
|  | String artResult = runOnArt(app, clazz.name); | 
|  | assertEquals(expected, artResult); | 
|  | DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method); | 
|  | // Note: This code is actually invalid debug info, but we do not reject it because both types | 
|  | // are reference types. If we ever change that we should update this test. | 
|  | assertTrue(info.hasLocalsInfo()); | 
|  | } | 
|  | } |