| // Copyright (c) 2018, 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 com.android.tools.r8.jasmin.JasminBuilder; | |
| import com.google.common.collect.ImmutableList; | |
| import java.nio.file.Path; | |
| import java.util.List; | |
| import org.junit.Test; | |
| /** | |
| * Regression test to ensure that an incomplete phi creation cannot introduce a local before the | |
| * locals actual start. The recursive read-register in IRBuilder did not previously account for the | |
| * change in blocks when determining the local information of the incomplete phi. | |
| */ | |
| public class NoLocalForPhiDebugTest extends DebugTestBase { | |
| private static final String className = "NoLocalForPhi"; | |
| private static final String sourcefile = className + ".j"; | |
| private static final String methodName = "test"; | |
| @Test | |
| public void testCf() throws Throwable { | |
| JasminBuilder builder = getBuilderForUselessCheckcast(className, methodName); | |
| Path outdir = temp.newFolder().toPath(); | |
| builder.writeClassFiles(outdir); | |
| CfDebugTestConfig config = new CfDebugTestConfig(); | |
| config.addPaths(outdir); | |
| run(config); | |
| } | |
| @Test | |
| public void testD8() throws Throwable { | |
| JasminBuilder builder = getBuilderForUselessCheckcast(className, methodName); | |
| List<Path> outputs = builder.writeClassFiles(temp.newFolder().toPath()); | |
| run(new D8DebugTestConfig().compileAndAdd(temp, outputs)); | |
| } | |
| private void run(DebugTestConfig config) throws Throwable { | |
| runDebugTest( | |
| config, | |
| className, | |
| breakpoint(className, methodName), | |
| run(), | |
| checkLine(sourcefile, 1), | |
| checkLocal("arg"), | |
| checkNoLocal("local"), | |
| stepOver(), | |
| checkLine(sourcefile, 2), | |
| checkLocal("arg"), | |
| checkNoLocal("local"), | |
| stepOver(), | |
| checkLine(sourcefile, 3), | |
| checkLocal("arg"), | |
| checkNoLocal("local"), | |
| stepOver(), | |
| checkLine(sourcefile, 4), | |
| checkLocal("arg"), | |
| checkLocal("local"), | |
| // local == 0 => branch to line 5 | |
| stepOver(), | |
| checkLine(sourcefile, 5), | |
| checkLocal("arg"), | |
| checkNoLocal("local"), | |
| stepOver(), | |
| checkLine(sourcefile, 2), | |
| checkLocal("arg"), | |
| checkNoLocal("local"), | |
| stepOver(), | |
| checkLine(sourcefile, 3), | |
| checkLocal("arg"), | |
| checkNoLocal("local"), | |
| stepOver(), | |
| checkLine(sourcefile, 4), | |
| checkLocal("arg"), | |
| checkLocal("local"), | |
| // local == -1 => branch to line 6 then 7 | |
| stepOver(), | |
| checkLine(sourcefile, 6), | |
| checkLocal("arg"), | |
| checkNoLocal("local"), | |
| stepOver(), | |
| checkLine(sourcefile, 7), | |
| checkLocal("arg"), | |
| checkNoLocal("local"), | |
| stepOver(), | |
| checkLine(sourcefile, 2), | |
| checkLocal("arg"), | |
| checkNoLocal("local"), | |
| stepOver(), | |
| checkLine(sourcefile, 3), | |
| checkLocal("arg"), | |
| checkNoLocal("local"), | |
| stepOver(), | |
| checkLine(sourcefile, 4), | |
| checkLocal("arg"), | |
| checkLocal("local"), | |
| // local == 1 => branch to line 6 then 8 | |
| stepOver(), | |
| checkLine(sourcefile, 6), | |
| checkLocal("arg"), | |
| checkNoLocal("local"), | |
| stepOver(), | |
| checkLine(sourcefile, 8), | |
| checkLocal("arg"), | |
| checkNoLocal("local"), | |
| run()); | |
| } | |
| private JasminBuilder getBuilderForUselessCheckcast(String testClassName, String testMethodName) { | |
| JasminBuilder builder = new JasminBuilder(); | |
| JasminBuilder.ClassBuilder clazz = builder.addClass(testClassName); | |
| clazz.addStaticMethod(testMethodName, ImmutableList.of("Ljava/lang/Object;"), "V", | |
| ".limit stack 2", | |
| ".limit locals 2", | |
| ".var 0 is arg Ljava/lang/Object; from L_init to L_end", | |
| ".var 1 is local I from L_local_start to L_local_end", | |
| "L_init:", | |
| ".line 1", | |
| " ldc 0", | |
| " istore 1", | |
| "L_join:", // A phi flowing to 'local' starts here. | |
| ".line 2", | |
| " getstatic java/lang/System/out Ljava/io/PrintStream;", | |
| ".line 3", // An intermediate instruction to ensure the phi did not start the local. | |
| " nop", | |
| "L_local_start:", // Delayed start of local. | |
| ".line 4", | |
| " iload 1", | |
| " invokevirtual java/io/PrintStream/println(I)V", | |
| " iload 1", | |
| " ifne L_b2", | |
| "L_local_end:", // End of local (could also just end at exit). | |
| "L_b1:", | |
| ".line 5", | |
| " ldc -1", | |
| " istore 1", | |
| " goto L_join", // Back branch to ensure an incomplete phi is created in L_join. | |
| "L_b2:", | |
| ".line 6", | |
| " iload 1", | |
| " ifgt L_b3", | |
| ".line 7", | |
| " ldc 1", | |
| " istore 1", | |
| " goto L_join", // Back branch to ensure an incomplete phi is created in L_join. | |
| "L_b3:", | |
| ".line 8", | |
| "return", | |
| "L_end:"); | |
| clazz.addMainMethod( | |
| ".limit stack 2", | |
| ".limit locals 1", | |
| "aconst_null", | |
| "invokestatic " + testClassName + "/" + testMethodName + "(Ljava/lang/Object;)V", | |
| "return"); | |
| return builder; | |
| } | |
| } |