RuntimeException(Trying to cast reference type to a primitive) when repacking android app.
I'm trying instrumenting android apps. This feature is awesome. After digging into this exception, I think it's a bug that worth reporting.
Describe the bug
APK file: native_leak.zip. (rename it to native_leak.apk)
The related method is replace in android.support.constraint.solver.LinearEquation:
<android.support.constraint.solver.LinearEquation: void replace(android.support.constraint.solver.SolverVariable,android.support.constraint.solver.LinearEquation,java.util.ArrayList)>
Source produced by Jadx:
private void replace(SolverVariable solverVariable, LinearEquation linearEquation, ArrayList<EquationVariable> arrayList) {
EquationVariable find = find(solverVariable, arrayList);
if (find != null) {
arrayList.remove(find);
Amount amount = find.getAmount();
ArrayList<EquationVariable> arrayList2 = linearEquation.mRightSide;
int size = arrayList2.size();
for (int i = 0; i < size; i++) {
arrayList.add(new EquationVariable(amount, arrayList2.get(i)));
}
}
}
The important part is the int i in for loop. The following is the smali code in Jadx. It seems that p2(previously used to store 2nd argument) is reused as int i. But in Jimple, it's still recognized as a java.lang.Object. the const/4 p2, 0x0 (i=0) becomes $u5#9 = null;. i++becomes $u5#9 = $u5#9 + 1;.
Related smali code:
.line 487
iget-object p1, p2, Landroid/support/constraint/solver/LinearEquation;->mRightSide:Ljava/util/ArrayList;
const/4 p2, 0x0
.line 488
invoke-virtual {p1}, Ljava/util/ArrayList;->size()I
move-result v0
:goto_14
if-ge p2, v0, :cond_27
.line 489
invoke-virtual {p1, p2}, Ljava/util/ArrayList;->get(I)Ljava/lang/Object;
The full jimple for this method is:
private void replace(android.support.constraint.solver.SolverVariable, android.support.constraint.solver.LinearEquation, java.util.ArrayList)
{
android.support.constraint.solver.LinearEquation this, $u5;
java.util.ArrayList $u6, $u4#8;
android.support.constraint.solver.Amount $u-1;
android.support.constraint.solver.SolverVariable $u4;
int $u-1#10, $i0;
java.lang.Object $u5#9, $u-1#11;
android.support.constraint.solver.EquationVariable $u2, $u1, $u0;
this := @this: android.support.constraint.solver.LinearEquation;
$u4 := @parameter0: android.support.constraint.solver.SolverVariable;
$u5 := @parameter1: android.support.constraint.solver.LinearEquation;
$u6 := @parameter2: java.util.ArrayList;
$u2 = specialinvoke this.<android.support.constraint.solver.LinearEquation: android.support.constraint.solver.EquationVariable find(android.support.constraint.solver.SolverVariable,java.util.ArrayList)>($u4, $u6);
if $u2 == null goto label2;
virtualinvoke $u6.<java.util.ArrayList: boolean remove(java.lang.Object)>($u2);
$u-1 = virtualinvoke $u2.<android.support.constraint.solver.EquationVariable: android.support.constraint.solver.Amount getAmount()>();
$u4#8 = $u5.<android.support.constraint.solver.LinearEquation: java.util.ArrayList mRightSide>;
$u5#9 = null;
$u-1#10 = virtualinvoke $u4#8.<java.util.ArrayList: int size()>();
label1:
$i0 = (int) $u5#9;
if $i0 >= $u-1#10 goto label2;
$i0 = (int) $u5#9;
$u-1#11 = virtualinvoke $u4#8.<java.util.ArrayList: java.lang.Object get(int)>($i0);
$u1 = (android.support.constraint.solver.EquationVariable) $u-1#11;
$u0 = new android.support.constraint.solver.EquationVariable;
specialinvoke $u0.<android.support.constraint.solver.EquationVariable: void <init>(android.support.constraint.solver.Amount,android.support.constraint.solver.EquationVariable)>($u-1, $u1);
virtualinvoke $u6.<java.util.ArrayList: boolean add(java.lang.Object)>($u0);
$u5#9 = $u5#9 + 1;
goto label1;
label2:
return;
}
The statement that results in the exception is $i0 = (int) $u5#9;. The exception is thown when calling DexPrinter.add() on the statement.
Input file github says it doesn't support apk file, so I changed the extension to zip. native_leak.zip and rename it to native_leak.apk
To reproduce After switching to 4.3.0-SNAPSHOT in my pom.xml, the issue still exist. The java code is basically APK to Jimple and back to APK. For the ease of debugging, I directly call DexPrinter.add with the class. Related java code:
public class APKRepacker {
private static final Logger logger = LoggerFactory.getLogger(APKRepacker.class);
// public boolean forceAndroidJar;
public String apkFileLocation;
public String platformDir;
APKRepacker(String apkFileLocation, String platformDir) {
this.apkFileLocation = apkFileLocation;
this.platformDir = platformDir;
}
private String getClasspath() {
String classpath = Scene.v().getAndroidJarPath(platformDir, apkFileLocation);
logger.debug("soot classpath: " + classpath);
return classpath;
}
public void initializeSoot() {
logger.info("Initializing Soot...");
G.reset();
Options.v().set_no_bodies_for_excluded(true);
Options.v().set_allow_phantom_refs(true);
Options.v().set_output_format(Options.output_format_dex);
Options.v().set_whole_program(true);
Options.v().set_process_dir(Collections.singletonList(apkFileLocation));
Options.v().set_android_jars(platformDir);
Options.v().set_src_prec(Options.src_prec_apk_class_jimple);
Options.v().set_keep_offset(true);
Options.v().set_keep_line_number(true);
Options.v().set_throw_analysis(Options.throw_analysis_dalvik);
Options.v().set_process_multiple_dex(true);
// Options.v().set_ignore_resolution_errors(true);
// Set soot phase option if original names should be used
Options.v().setPhaseOption("jb", "use-original-names:true");
Options.v().set_soot_classpath(getClasspath());
Main.v().autoSetOptions();
// Load whatever we need
logger.info("Loading dex files...");
Scene.v().loadNecessaryClasses();
// Make sure that we have valid Jimple bodies
PackManager.v().getPack("wjpp").apply();
}
public static void outputAPK() {
Options.v().set_output_format(Options.output_format_dex);
SootClass c = Scene.v().getSootClassUnsafe("android.support.constraint.solver.LinearEquation");
DexPrinter dexPrinter = new DexPrinter();
dexPrinter.add(c);
// PackManager.v().writeOutput();
}
public static void main(String[] args) {
logger.info("args: " + args.toString());
APKRepacker r = new APKRepacker(args[0], args[1]);
r.initializeSoot();
APKRepacker.outputAPK();
}
}
Stacktrace
Exception in thread "main" soot.toDex.DexPrinterException: Error while processing method <android.support.constraint.solver.LinearEquation: void replace(android.support.constraint.solver.SolverVariable,android.support.constraint.solver.LinearEquation,java.util.ArrayList)>
at soot.toDex.DexPrinter.toMethods(DexPrinter.java:1079)
at soot.toDex.DexPrinter.addAsClassDefItem(DexPrinter.java:659)
at soot.toDex.DexPrinter.add(DexPrinter.java:1686)
at APKRepacker.outputAPK(APKRepacker.java:128)
at APKRepacker.main(APKRepacker.java:137)
Caused by: java.lang.RuntimeException: Trying to cast reference type java.lang.Object to a primitive
at soot.toDex.ExprVisitor.castPrimitive(ExprVisitor.java:684)
at soot.toDex.ExprVisitor.caseCastExpr(ExprVisitor.java:639)
at soot.jimple.internal.AbstractCastExpr.apply(AbstractCastExpr.java:136)
at soot.toDex.StmtVisitor.caseAssignStmt(StmtVisitor.java:498)
at soot.jimple.internal.JAssignStmt.apply(JAssignStmt.java:215)
at soot.toDex.DexPrinter.toInstructions(DexPrinter.java:1560)
at soot.toDex.DexPrinter.toMethodImplementation(DexPrinter.java:1192)
at soot.toDex.DexPrinter.toMethods(DexPrinter.java:1077)
at soot.toDex.DexPrinter.addAsClassDefItem(DexPrinter.java:659)
at soot.toDex.DexPrinter.add(DexPrinter.java:1686)