blob: 939499595a3cd2cddcbba719cd5a4802d5d6ae55 [file] [log] [blame]
// 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());
}
}