| // 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.ir.optimize; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.ToolHelper.ProcessResult; |
| import com.android.tools.r8.code.AgetObject; |
| import com.android.tools.r8.code.AputObject; |
| import com.android.tools.r8.code.CheckCast; |
| import com.android.tools.r8.code.Const4; |
| import com.android.tools.r8.code.ConstString; |
| import com.android.tools.r8.code.IgetObject; |
| import com.android.tools.r8.code.InvokeDirect; |
| import com.android.tools.r8.code.InvokeVirtual; |
| import com.android.tools.r8.code.NewArray; |
| import com.android.tools.r8.code.NewInstance; |
| import com.android.tools.r8.code.ReturnVoid; |
| import com.android.tools.r8.graph.DexCode; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.jasmin.JasminBuilder; |
| import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder; |
| import com.android.tools.r8.jasmin.JasminTestBase; |
| import com.android.tools.r8.naming.MemberNaming.MethodSignature; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.google.common.collect.ImmutableList; |
| import java.util.List; |
| import org.junit.Test; |
| |
| public class CheckCastRemovalTest extends JasminTestBase { |
| private final String CLASS_NAME = "Example"; |
| |
| @Test |
| public void exactMatch() throws Exception { |
| JasminBuilder builder = new JasminBuilder(); |
| ClassBuilder classBuilder = builder.addClass(CLASS_NAME); |
| classBuilder.addDefaultConstructor(); |
| MethodSignature main = classBuilder.addMainMethod( |
| ".limit stack 3", |
| ".limit locals 1", |
| "new Example", |
| "dup", |
| "invokespecial Example/<init>()V", |
| "checkcast Example", // Gone |
| "return"); |
| |
| List<String> pgConfigs = ImmutableList.of( |
| "-keep class " + CLASS_NAME + " { *; }", |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, o -> o.enableClassInlining = false); |
| |
| DexEncodedMethod method = getMethod(app, CLASS_NAME, main); |
| assertNotNull(method); |
| |
| checkInstructions( |
| method.getCode().asDexCode(), |
| ImmutableList.of(NewInstance.class, InvokeDirect.class, ReturnVoid.class)); |
| |
| checkRuntime(builder, app, CLASS_NAME); |
| } |
| |
| @Test |
| public void upCasts() throws Exception { |
| JasminBuilder builder = new JasminBuilder(); |
| // A < B < C |
| ClassBuilder c = builder.addClass("C"); |
| c.addDefaultConstructor(); |
| ClassBuilder b = builder.addClass("B", "C"); |
| b.addDefaultConstructor(); |
| ClassBuilder a = builder.addClass("A", "B"); |
| a.addDefaultConstructor(); |
| ClassBuilder classBuilder = builder.addClass(CLASS_NAME); |
| MethodSignature main = classBuilder.addMainMethod( |
| ".limit stack 3", |
| ".limit locals 1", |
| "new A", |
| "dup", |
| "invokespecial A/<init>()V", |
| "checkcast B", // Gone |
| "checkcast C", // Gone |
| "return"); |
| |
| List<String> pgConfigs = ImmutableList.of( |
| "-keep class " + CLASS_NAME + " { *; }", |
| "-keep class A { *; }", |
| "-keep class B { *; }", |
| "-keep class C { *; }", |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, opts -> opts.enableClassInlining = false); |
| |
| DexEncodedMethod method = getMethod(app, CLASS_NAME, main); |
| assertNotNull(method); |
| |
| checkInstructions( |
| method.getCode().asDexCode(), |
| ImmutableList.of(NewInstance.class, InvokeDirect.class, ReturnVoid.class)); |
| |
| checkRuntime(builder, app, CLASS_NAME); |
| } |
| |
| @Test |
| public void downCasts() throws Exception { |
| JasminBuilder builder = new JasminBuilder(); |
| // C < B < A |
| ClassBuilder a = builder.addClass("A"); |
| a.addDefaultConstructor(); |
| ClassBuilder b = builder.addClass("B", "A"); |
| b.addDefaultConstructor(); |
| ClassBuilder c = builder.addClass("C", "B"); |
| c.addDefaultConstructor(); |
| ClassBuilder classBuilder = builder.addClass(CLASS_NAME); |
| MethodSignature main = classBuilder.addMainMethod( |
| ".limit stack 3", |
| ".limit locals 1", |
| "new A", |
| "dup", |
| "invokespecial A/<init>()V", |
| "checkcast B", // Gone |
| "checkcast C", // Should be kept |
| "return"); |
| |
| List<String> pgConfigs = ImmutableList.of( |
| "-keep class " + CLASS_NAME + " { *; }", |
| "-keep class A { *; }", |
| "-keep class B { *; }", |
| "-keep class C { *; }", |
| "-dontoptimize", |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, null); |
| |
| DexEncodedMethod method = getMethod(app, CLASS_NAME, main); |
| assertNotNull(method); |
| |
| DexCode code = method.getCode().asDexCode(); |
| checkInstructions( |
| code, |
| ImmutableList.of(NewInstance.class, InvokeDirect.class, CheckCast.class, ReturnVoid.class)); |
| CheckCast cast = (CheckCast) code.instructions[2]; |
| assertEquals("C", cast.getType().toString()); |
| |
| checkRuntimeException(builder, app, CLASS_NAME, "ClassCastException"); |
| } |
| |
| @Test |
| public void bothUpAndDowncast() throws Exception { |
| JasminBuilder builder = new JasminBuilder(); |
| ClassBuilder classBuilder = builder.addClass(CLASS_NAME); |
| MethodSignature main = classBuilder.addMainMethod( |
| ".limit stack 4", |
| ".limit locals 1", |
| "iconst_1", |
| "anewarray java/lang/String", // args parameter |
| "dup", |
| "iconst_0", |
| "ldc \"a string\"", |
| "aastore", |
| "checkcast [Ljava/lang/Object;", // This upcast can be removed. |
| "iconst_0", |
| "aaload", |
| "checkcast java/lang/String", // Then, this downcast can be removed, too. |
| "invokevirtual java/lang/String/length()I", |
| "return"); |
| // That is, both checkcasts should be removed together or kept together. |
| |
| List<String> pgConfigs = ImmutableList.of( |
| "-keep class " + CLASS_NAME + " { *; }", |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, null); |
| |
| DexEncodedMethod method = getMethod(app, CLASS_NAME, main); |
| assertNotNull(method); |
| |
| DexCode code = method.getCode().asDexCode(); |
| checkInstructions( |
| code, |
| ImmutableList.of( |
| Const4.class, |
| NewArray.class, |
| ConstString.class, |
| Const4.class, |
| AputObject.class, |
| AgetObject.class, |
| InvokeVirtual.class, |
| ReturnVoid.class)); |
| |
| checkRuntime(builder, app, CLASS_NAME); |
| } |
| |
| @Test |
| public void nullCast() throws Exception { |
| JasminBuilder builder = new JasminBuilder(); |
| ClassBuilder classBuilder = builder.addClass(CLASS_NAME); |
| classBuilder.addField("public", "fld", "Ljava/lang/String;", null); |
| MethodSignature main = classBuilder.addMainMethod( |
| ".limit stack 3", |
| ".limit locals 1", |
| "aconst_null", |
| "checkcast Example", // Should be kept |
| "getfield Example.fld Ljava/lang/String;", |
| "return"); |
| |
| List<String> pgConfigs = ImmutableList.of( |
| "-keep class " + CLASS_NAME + " { *; }", |
| "-dontshrink"); |
| AndroidApp app = compileWithR8(builder, pgConfigs, null); |
| |
| DexEncodedMethod method = getMethod(app, CLASS_NAME, main); |
| assertNotNull(method); |
| |
| checkInstructions(method.getCode().asDexCode(), ImmutableList.of( |
| Const4.class, |
| CheckCast.class, |
| IgetObject.class, |
| ReturnVoid.class)); |
| |
| checkRuntimeException(builder, app, CLASS_NAME, "NullPointerException"); |
| } |
| |
| private void checkRuntime(JasminBuilder builder, AndroidApp app, String className) |
| throws Exception { |
| String normalOutput = runOnJava(builder, className); |
| String dexOptimizedOutput = runOnArt(app, className); |
| assertEquals(normalOutput, dexOptimizedOutput); |
| } |
| |
| private void checkRuntimeException( |
| JasminBuilder builder, AndroidApp app, String className, String exceptionName) |
| throws Exception { |
| ProcessResult javaOutput = runOnJavaRaw(builder, className); |
| assertEquals(1, javaOutput.exitCode); |
| assertTrue(javaOutput.stderr.contains(exceptionName)); |
| |
| ProcessResult artOutput = runOnArtRaw(app, className); |
| assertEquals(1, artOutput.exitCode); |
| assertTrue(artOutput.stderr.contains(exceptionName)); |
| } |
| } |