| // Copyright (c) 2016, 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.smali; |
| |
| import static org.junit.Assert.assertEquals; |
| |
| import com.android.tools.r8.graph.SmaliWriter; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.Smali; |
| import com.google.common.collect.ImmutableList; |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.concurrent.ExecutionException; |
| import org.antlr.runtime.RecognitionException; |
| import org.junit.Test; |
| |
| public class SmaliDisassembleTest extends SmaliTestBase { |
| |
| // Run the provided smali through R8 smali disassembler and expect the exact same output. |
| void roundTripRawSmali(String smali) { |
| try { |
| AndroidApp application = |
| AndroidApp.builder().addDexProgramData(Smali.compile(smali), Origin.unknown()).build(); |
| assertEquals(smali, SmaliWriter.smali(application, new InternalOptions())); |
| } catch (IOException | RecognitionException | ExecutionException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Test |
| public void simpleSmokeTest() { |
| AndroidApp application = singleMethodApplication( |
| "int", Collections.singletonList("int"), |
| 4, |
| " const/4 v0, 1 ", |
| " const/16 v1, 2 ", |
| " const v2, 3 ", |
| " const-wide v3, -1 ", |
| " add-int/2addr v1, v0 ", |
| " mul-int/2addr v2, v1 ", |
| " div-int/2addr v3, v2 ", |
| " return v3\n " |
| ); |
| |
| String expected = |
| ".class public LTest;\n" + |
| "\n" + |
| ".super Ljava/lang/Object;\n" + |
| "\n" + |
| ".method public static method(I)I\n" + |
| " .registers 5\n" + |
| "\n" + |
| " const/4 v0, 0x01 # 1\n" + |
| " const/16 v1, 0x0002 # 2\n" + |
| " const v2, 0x00000003 # 3\n" + |
| " const-wide v3, 0xffffffffffffffffL # -1\n" + |
| " add-int/2addr v1, v0\n" + |
| " mul-int/2addr v2, v1\n" + |
| " div-int/2addr v3, v2\n" + |
| " return v3\n" + |
| ".end method\n" + |
| "\n" + |
| "# End of class LTest;\n"; |
| |
| assertEquals(expected, SmaliWriter.smali(application, new InternalOptions())); |
| |
| roundTripRawSmali(expected); |
| } |
| |
| @Test |
| public void sparseSwitchTest() { |
| AndroidApp application = singleMethodApplication( |
| "int", Collections.singletonList("int"), |
| 0, |
| " sparse-switch v0, :sparse_switch_data", |
| " const/4 v0, 0x0", |
| " goto :return", |
| " :case_1", |
| " const/4 v0, 0x1", |
| " goto :return", |
| " :case_2", |
| " const/4 v0, 0x2", |
| " :return", |
| " return v0", |
| " :sparse_switch_data ", |
| " .sparse-switch ", |
| " 0x1 -> :case_1 ", |
| " 0x2 -> :case_2 ", |
| " .end sparse-switch " |
| |
| ); |
| |
| String expected = |
| ".class public LTest;\n" + |
| "\n" + |
| ".super Ljava/lang/Object;\n" + |
| "\n" + |
| ".method public static method(I)I\n" + |
| " .registers 1\n" + |
| "\n" + |
| " sparse-switch v0, :label_10\n" + |
| " const/4 v0, 0x00 # 0\n" + |
| " goto :label_8\n" + |
| " :label_5\n" + |
| " const/4 v0, 0x01 # 1\n" + |
| " goto :label_8\n" + |
| " :label_7\n" + |
| " const/4 v0, 0x02 # 2\n" + |
| " :label_8\n" + |
| " return v0\n" + |
| " nop\n" + |
| " :label_10\n" + |
| " .sparse-switch\n" + |
| " 0x00000001 -> :label_5 # 1\n" + |
| " 0x00000002 -> :label_7 # 2\n" + |
| " .end sparse-switch\n" + |
| ".end method\n" + |
| "\n" + |
| "# End of class LTest;\n"; |
| |
| assertEquals(expected, SmaliWriter.smali(application, new InternalOptions())); |
| |
| roundTripRawSmali(expected); |
| } |
| |
| @Test |
| public void packedSwitchTest() { |
| AndroidApp application = singleMethodApplication( |
| "int", Collections.singletonList("int"), |
| 0, |
| " packed-switch v0, :packed_switch_data", |
| " const/4 v0, 0x0 ", |
| " goto :return ", |
| " :case_1 ", |
| " const/4 v0, 0x1 ", |
| " goto :return ", |
| " :case_2 ", |
| " const/4 v0, 0x2 ", |
| " :return ", |
| " return v0 ", |
| " :packed_switch_data ", |
| " .packed-switch 0x1 ", |
| " :case_1 ", |
| " :case_2 ", |
| " .end packed-switch " |
| |
| ); |
| |
| String expected = |
| ".class public LTest;\n" + |
| "\n" + |
| ".super Ljava/lang/Object;\n" + |
| "\n" + |
| ".method public static method(I)I\n" + |
| " .registers 1\n" + |
| "\n" + |
| " packed-switch v0, :label_10\n" + |
| " const/4 v0, 0x00 # 0\n" + |
| " goto :label_8\n" + |
| " :label_5\n" + |
| " const/4 v0, 0x01 # 1\n" + |
| " goto :label_8\n" + |
| " :label_7\n" + |
| " const/4 v0, 0x02 # 2\n" + |
| " :label_8\n" + |
| " return v0\n" + |
| " nop\n" + |
| " :label_10\n" + |
| " .packed-switch 0x00000001 # 1\n" + |
| " :label_5\n" + |
| " :label_7\n" + |
| " .end packed-switch\n" + |
| ".end method\n" + |
| "\n" + |
| "# End of class LTest;\n"; |
| |
| assertEquals(expected, SmaliWriter.smali(application, new InternalOptions())); |
| |
| roundTripRawSmali(expected); |
| } |
| |
| @Test |
| public void fillArrayDataTest8Bit() { |
| AndroidApp application = singleMethodApplication( |
| "int[]", ImmutableList.of(), |
| 2, |
| " const/4 v1, 3", |
| " new-array v0, v1, [I", |
| " fill-array-data v0, :array_data", |
| " return-object v0", |
| " :array_data", |
| " .array-data 1", |
| " 1 2 255", |
| " .end array-data" |
| ); |
| |
| String expected = |
| ".class public LTest;\n" + |
| "\n" + |
| ".super Ljava/lang/Object;\n" + |
| "\n" + |
| ".method public static method()[I\n" + |
| " .registers 2\n" + |
| "\n" + |
| " const/4 v1, 0x03 # 3\n" + |
| " new-array v0, v1, [I\n" + |
| " fill-array-data v0, :label_8\n" + |
| " return-object v0\n" + |
| " nop\n" + |
| " :label_8\n" + |
| " .array-data 0x1 # 1\n" + |
| " 0x01 # 1\n" + |
| " 0x02 # 2\n" + |
| " 0xff # 255\n" + |
| " .end array-data\n" + |
| ".end method\n" + |
| "\n" + |
| "# End of class LTest;\n"; |
| |
| assertEquals(expected, SmaliWriter.smali(application, new InternalOptions())); |
| |
| roundTripRawSmali(expected); |
| } |
| |
| @Test |
| public void fillArrayDataTest16Bit() { |
| AndroidApp application = singleMethodApplication( |
| "int[]", ImmutableList.of(), |
| 2, |
| " const/4 v1, 3", |
| " new-array v0, v1, [I", |
| " fill-array-data v0, :array_data", |
| " return-object v0", |
| " :array_data", |
| " .array-data 2", |
| " 1 2 65535", |
| " .end array-data" |
| ); |
| |
| String expected = |
| ".class public LTest;\n" + |
| "\n" + |
| ".super Ljava/lang/Object;\n" + |
| "\n" + |
| ".method public static method()[I\n" + |
| " .registers 2\n" + |
| "\n" + |
| " const/4 v1, 0x03 # 3\n" + |
| " new-array v0, v1, [I\n" + |
| " fill-array-data v0, :label_8\n" + |
| " return-object v0\n" + |
| " nop\n" + |
| " :label_8\n" + |
| " .array-data 0x2 # 2\n" + |
| " 0x0001 # 1\n" + |
| " 0x0002 # 2\n" + |
| " 0xffff # 65535\n" + |
| " .end array-data\n" + |
| ".end method\n" + |
| "\n" + |
| "# End of class LTest;\n"; |
| |
| assertEquals(expected, SmaliWriter.smali(application, new InternalOptions())); |
| |
| roundTripRawSmali(expected); |
| } |
| |
| @Test |
| public void fillArrayDataTest32Bit() { |
| AndroidApp application = singleMethodApplication( |
| "int[]", ImmutableList.of(), |
| 2, |
| " const/4 v1, 3", |
| " new-array v0, v1, [I", |
| " fill-array-data v0, :array_data", |
| " return-object v0", |
| " :array_data", |
| " .array-data 4", |
| " 1 2 4294967295", |
| " .end array-data" |
| ); |
| |
| String expected = |
| ".class public LTest;\n" + |
| "\n" + |
| ".super Ljava/lang/Object;\n" + |
| "\n" + |
| ".method public static method()[I\n" + |
| " .registers 2\n" + |
| "\n" + |
| " const/4 v1, 0x03 # 3\n" + |
| " new-array v0, v1, [I\n" + |
| " fill-array-data v0, :label_8\n" + |
| " return-object v0\n" + |
| " nop\n" + |
| " :label_8\n" + |
| " .array-data 0x4 # 4\n" + |
| " 0x00000001 # 1\n" + |
| " 0x00000002 # 2\n" + |
| " 0xffffffff # 4294967295\n" + |
| " .end array-data\n" + |
| ".end method\n" + |
| "\n" + |
| "# End of class LTest;\n"; |
| |
| assertEquals(expected, SmaliWriter.smali(application, new InternalOptions())); |
| |
| roundTripRawSmali(expected); |
| } |
| |
| @Test |
| public void fillArrayDataTest64Bit() { |
| AndroidApp application = singleMethodApplication( |
| "int[]", ImmutableList.of(), |
| 2, |
| " const/4 v1, 3", |
| " new-array v0, v1, [I", |
| " fill-array-data v0, :array_data", |
| " return-object v0", |
| " :array_data", |
| " .array-data 8", |
| " 1 2 -1", |
| " .end array-data" |
| ); |
| |
| String expected = |
| ".class public LTest;\n" + |
| "\n" + |
| ".super Ljava/lang/Object;\n" + |
| "\n" + |
| ".method public static method()[I\n" + |
| " .registers 2\n" + |
| "\n" + |
| " const/4 v1, 0x03 # 3\n" + |
| " new-array v0, v1, [I\n" + |
| " fill-array-data v0, :label_8\n" + |
| " return-object v0\n" + |
| " nop\n" + |
| " :label_8\n" + |
| " .array-data 0x8 # 8\n" + |
| " 0x0000000000000001 # 1\n" + |
| " 0x0000000000000002 # 2\n" + |
| " 0xffffffffffffffff # -1\n" + |
| " .end array-data\n" + |
| ".end method\n" + |
| "\n" + |
| "# End of class LTest;\n"; |
| |
| assertEquals(expected, SmaliWriter.smali(application, new InternalOptions())); |
| |
| roundTripRawSmali(expected); |
| } |
| |
| @Test |
| public void interfaceClass() { |
| SmaliBuilder builder = new SmaliBuilder(); |
| builder.addInterface("Test"); |
| builder.addAbstractMethod("int", "test", ImmutableList.of()); |
| AndroidApp application = buildApplication(builder); |
| |
| String expected = |
| ".class public interface abstract LTest;\n" + |
| "\n" + |
| ".super Ljava/lang/Object;\n" + |
| "\n" + |
| ".method public abstract test()I\n" + |
| ".end method\n" + |
| "\n" + |
| "# End of class LTest;\n"; |
| |
| assertEquals(expected, SmaliWriter.smali(application, new InternalOptions())); |
| |
| roundTripRawSmali(expected); |
| } |
| |
| @Test |
| public void implementsInterface() { |
| SmaliBuilder builder = new SmaliBuilder(); |
| builder.addClass("Test", "java.lang.Object", ImmutableList.of("java.util.List")); |
| builder.addAbstractMethod("int", "test", ImmutableList.of()); |
| AndroidApp application = buildApplication(builder); |
| |
| String expected = |
| ".class public LTest;\n" + |
| "\n" + |
| ".super Ljava/lang/Object;\n" + |
| ".implements Ljava/util/List;\n" + |
| "\n" + |
| ".method public abstract test()I\n" + |
| ".end method\n" + |
| "\n" + |
| "# End of class LTest;\n"; |
| |
| assertEquals(expected, SmaliWriter.smali(application, new InternalOptions())); |
| |
| roundTripRawSmali(expected); |
| } |
| } |