Sunday, October 14, 2012

Conversion to Dalvik format failed with error 1 (Dx local variable type mismatch)

Recently I got infamous "Conversion to Dalvik format failed with error 1" message while I was trying to import my own (obfuscated and optimized) JAR to one of my projects. The full message is quoted bellow:

EXCEPTION FROM SIMULATION:
[2012-10-09 18:50:04 - AnotherTest] Dx local variable type mismatch: attempt to set or access a value of type java.lang.Object using a local variable of type int. This is symptomatic of .class transformation tools that ignore local variable information.

[2012-10-09 18:50:04 - AnotherTest] Dx ...at bytecode offset 00000019
locals[0000]: Lbiz/binarysolutions/signature/CaptureBase;
locals[0001]: I
locals[0002]: Landroid/view/KeyEvent;
stack[0002]: Lbiz/binarysolutions/signature/CaptureBase;
stack[0001]: Lbiz/binarysolutions/signature/CaptureBase;
stack[top0]: Lbiz/binarysolutions/signature/CaptureBase;
...while working on block 0016
...while working on method onKeyDown:(ILandroid/view/KeyEvent;)Z
...while processing onKeyDown (ILandroid/view/KeyEvent;)Z
...while processing biz/binarysolutions/signature/CaptureBase.class

[2012-10-09 18:50:04 - AnotherTest] Dx 1 error; aborting
[2012-10-09 18:50:04 - AnotherTest] Conversion to Dalvik format failed with error 1
[2012-10-09 18:50:04 - AnotherTest] Dx 

Obviously, it was ProGuard's fault since importing the original JAR file went without any problems. Although the message (quoted part marked in bold) was cryptic at first, the clue that it happened in my onKeyDown method (also marked in bold) was enough to start digging.

So the original onKeyDown looked as following:
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
  if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
    if (signatureView.isModified()) {
 Toast.makeText(this, getToastMessageID(), Toast.LENGTH_LONG)
          .show();
 return true;
    }
  }
return super.onKeyDown(keyCode, event);
}

I took the problematic JAR file processed by ProGuard, decompiled the CaptureBase.class file and took a look at onKeyDown again:
public boolean onKeyDown(int keyCode, KeyEvent event)
  {
    if ((keyCode == 4) && (event.getRepeatCount() == 0) && 
      (this.a.isModified())) {
      keyCode = this; 
      Toast.makeText(this, getResources().getIdentifier(
        "biz_binarysolutions_signature_ToastMessage", "string", 
        keyCode.getPackageName()), 1).show();
      return true;
    }
    return super.onKeyDown(keyCode, event);
  }
Well, now it all makes sense - for some 'optimization' reason, ProGuard assigned this (android.app.Activity) to keyCode (local variable of type int) and used it in getPackageName() call. Dalvik does not like it.

The fix would be the following: you need to add !code/allocation/variable string to your -optimizations command in ProGuard config file. Mine looks like the following:

-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*,!code/allocation/variable

And that should be it!