java.lang.VerifyError IllformedLocaleException
Asked Answered
R

2

14

I have the following parent method, that is used in all cases by various API levels:

public int setVoice (@NonNull final String language, @NonNull final String region){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        return setVoice21(language, region);
    } else {
        return setVoiceDeprecated(language, region);
    }
}

and setVoice21 does something like this:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public int setVoice21 ( @NonNull final String language, @NonNull final String region){

    try {
        // try some API 21 stuff
    } catch (final IllformedLocaleException e) {
        e.printStackTrace();
        return setVoiceDeprecated(language, region);
    }

setVoice21 contains other code that requires API 21+ specifically TextToSpeech.Voice and Locale.Builder

When I run this code on a device < API 21 I'm getting the following error:

W/dalvikvm: VFY: unable to resolve exception class 6232 (Ljava/util/IllformedLocaleException;) W/dalvikvm: VFY: rejecting opcode 0x0d at 0x0168 W/dalvikvm: VFY: rejected Lcom/myapp/android/speech/MyTextToSpeech;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I W/dalvikvm: Verifier rejected class Lcom/myapp/android/speech/MyTextToSpeech;

E/AndroidRuntime: FATAL EXCEPTION: main java.lang.VerifyError: com/myapp/android/speech/MyTextToSpeech

If I remove the IllformedLocaleException and just replace it with a standard Exception, the app runs fine, despite the many other references to methods > API21 within setVoice21

To confuse me yet further, setVoice21 invokes the following class

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private class TTSVoice {

    public void buildVoice() {

        try {
            // Do some API 21 stuff
        } catch (final IllformedLocaleException e) {
        }

    }
}

This class is only referenced from setVoice21, but I do not have to remove the reference to IllformedLocaleException here - I can leave it and the app runs fine.... Baffled.

Can anyone help me out as to why the IllformedLocaleException is causing this failure? Are Exceptions somehow handled differently?

I thank you in advance.

Note - I'm not sure that it is relevant, but I'm subclassing TextToSpeech in a standard way. I fear this may convolute the question, but just in case...

public class MyTextToSpeech extends TextToSpeech {

    public MyTextToSpeech(final Context context, final OnInitListener listener) {
        super(context, listener);
    }
}

EDIT - The workaround provided by razzledazzle below, does allow the app to run without crashing, but I still remain non-the-wiser as to why such a step is necessary. I've never had to take such measures before when dealing with API versioning.

Rubberneck answered 13/4, 2016 at 22:52 Comment(6)
You mean you got that error when you called setVoice21() directly?Cataphoresis
@Cataphoresis no. Only ever setVoice. Hence the questionRubberneck
This is interesting. Answered expecting a direct call. You might also try doing clean builds if you haven't.Cataphoresis
@Cataphoresis Cleaned and rebuilt many, many times.Rubberneck
Deleted that answer to avoid confusion.Cataphoresis
Appreciated, thank you.Rubberneck
B
4

TL;DR: Exceptions are exceptional. Can't catch an exception whose type is not known.

The following is most speculation based on my limited knowledge about Java/Dalvik and common sense. Take it with a grain of salt. I found the method that spits out the failing log line and confirmed most of my mentioned speculations, see added links below.

Your problem seems to be that the classes are loaded at once, either the whole class is loaded or none of it. Verification is done first I guess to prevent some runtime checks (remember Android is resource constrained).

I used the following code:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public int setVoice21(@NonNull final String language, @NonNull final String region) {
    try {
        // try some API 21 stuff
        new Locale.Builder().build().getDisplayVariant();
    } catch (final IllformedLocaleException ex) {
        ex.printStackTrace();
    }
    return 0;
}

When the system was trying to create an instance of the class containing this method the following happened:

E/dalvikvm: Could not find class 'java.util.Locale$Builder', referenced from method com.test.TestFragment.setVoice21

Loading the Locale.Builder class would be a ClassNotFoundException.

W/dalvikvm: VFY: unable to resolve new-instance 5241 (Ljava/util/Locale$Builder;) in Lcom/test/TestFragment;
D/dalvikvm: VFY: replacing opcode 0x22 at 0x0000

Then on that non-existent class it would try to call the <init> method which is prevented by replacing the OP_NEW_INSTANCE with a OP_NOP. I think this would have been survivable, as I see these all the time when using the support library. I think the assumption here is that if the class is not found then it must have been guarded with an SDK_INT check. Also if it went through dexing/proguard and other stuff it must've been intentional and a ClassNotFoundException is acceptable at runtime.

W/dalvikvm: VFY: unable to resolve exception class 5234 (Ljava/util/IllformedLocaleException;)

Another problematic class, notice this time it's an "exception class" which must be special. If you check the Java bytecode for this method via:

javap -verbose -l -private -c -s TestFragment.class > TestFragment.dis

public int setVoice21(java.lang.String, java.lang.String);
    ...
    Exception table:
       from    to  target type
           0    14    17   Class java/util/IllformedLocaleException
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
         18      11     3    ex   Ljava/util/IllformedLocaleException;
          0      31     0  this   Lcom/test/TestFragment;
          0      31     1 language   Ljava/lang/String;
          0      31     2 region   Ljava/lang/String;
    StackMapTable: number_of_entries = 2
      frame_type = 81 /* same_locals_1_stack_item */
        stack = [ class java/util/IllformedLocaleException ]
      frame_type = 11 /* same */

You can indeed see that the Exception table and StackMapTable and LocalVariableTable all contain the problematic class, but not Locale$Builder. This may be because the builder is not stored in a variable, but the point to take from here is that exceptions are handled specially and get more scrutiny than normal lines of code.

Using BakSmali on the APK via:

apktool.bat d -r -f -o .\disassembled "app-debug.apk"

.method public setVoice21(Ljava/lang/String;Ljava/lang/String;)I
.prologue
:try_start_0
new-instance v1, Ljava/util/Locale$Builder;
invoke-direct {v1}, Ljava/util/Locale$Builder;-><init>()V
...
:try_end_0
.catch Ljava/util/IllformedLocaleException; {:try_start_0 .. :try_end_0} :catch_0
...
:catch_0
move-exception v0
.local v0, "ex":Ljava/util/IllformedLocaleException;
invoke-virtual {v0}, Ljava/util/IllformedLocaleException;->printStackTrace()V

seems to reveal a similar pattern, here we can actually see the op-codes mentioned in the log. Notice that .catch seems to be a special instruction, not an operation because it's preceded by a dot. I think this reinforces the scrutiny mentioned above: it's not a runtime operation, but it is required for the class to load the code contained within the methods.

W/dalvikvm: VFY: unable to find exception handler at addr 0xe
W/dalvikvm: VFY: rejected Lcom/test/TestFragment;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I

I guess this means that it was not able to reconstruct when to call which catch block from the Exception table and StackMapTable because it couldn't find the class to determine the parent classes. This is confirmed in getCaughtExceptionType where "unable to resolve exception class" directly leads to "unable to find exception handler" because it finds no common super-class for a non-existent exception, something like } catch (? ex) { so it doesn't know what to catch.

W/dalvikvm: VFY: rejecting opcode 0x0d at 0x000e
W/dalvikvm: VFY: rejected Lcom/test/TestFragment;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I

I think at this point the verifier just gave up because it couldn't make sense of the OP_MOVE_EXCEPTION. This is confirmed as that the getCaughtExceptionType method is only used in one place, a switch. Breaking out of that we get "rejecting opcode" then it goto bails up the call stack to "rejected class". After bailing the error code was VERIFY_ERROR_GENERIC which is mapped to VerifyError. Couldn't find where the actual JNI Exception is thrown if it even works that way.

W/dalvikvm: Verifier rejected class Lcom/test/TestFragment;

Multiple rejections were filed against the setVoice21 method and hence the whole class must be rejected (this seems harsh to me, it's possible ART is different in this regard).

W/dalvikvm: Class init failed in newInstance call (Lcom/test/TestFragment;)
D/AndroidRuntime: Shutting down VM
W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x41869da0)
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.bumptech.glide.supportapp.v3, PID: 27649
java.lang.VerifyError: com/test/TestFragment

I guess this is similar to an ExceptionInInitializerError in desktop Java which is thrown when a static { } in class body or an static field initializer throws a RuntimeException/Error.

Why instanceof works

Using razzledazzle's workaround changes those tables to include java/lang/Exception, and moves the dependency to IllformedLocaleException into the code to be executed at runtime:

     0    14    17   Class java/lang/Exception
19: instanceof    #34                 // class java/util/IllformedLocaleException

and similarly the Smali:

.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
instance-of v1, v0, Ljava/util/IllformedLocaleException;

E/dalvikvm: Could not find class 'java.util.IllformedLocaleException', referenced from method com.test.TestFragment.setVoice21
W/dalvikvm: VFY: unable to resolve instanceof 5234 (Ljava/util/IllformedLocaleException;) in Lcom/test/TestFragment;

Now, it's the same complaint as for Locale$Builder above

D/dalvikvm: VFY: replacing opcode 0x20 at 0x000f

Replacing OP_INSTANCE_OF with ?something?, it doesn't say :)

Another possible workaround

If you look at android.support.v4.view.ViewCompat* classes you will notice that not all those classes are used on all versions. The correct one is chosen at runtime (search for static final ViewCompatImpl IMPL in ViewCompat.java) and only that is loaded. This ensures that even at class load time there won't be any weirdness due to missing classes and is performant. You can do a similar architecture to prevent that method from loading on earlier API levels.

Broz answered 23/4, 2016 at 17:33 Comment(3)
So it maintains a collection of catch blocks and for which it checks against the exceptions assigned/mapped to them. And if the exception class can't be resolved, it throws VerifyError, is this correct? Some of the part of the code is still vague for me, such as the handler that it mentions.Cataphoresis
@Cataphoresis added my findings into the text, look for "This is confirmed".Broz
Thank you for such an in-depth answer. Marked it as correct for discovering the cause. I have however awarded the bounty to @Cataphoresis as he provided a workaround which saved me from production crashes. It was the best compromise I could find for two answers that have both helped me greatly.Rubberneck
C
7

Resolved the issue by removing the IllformedLocaleException class from the catch argument. This will still allow you to check for IllformedLocaleException.

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public int setVoice21 (@NonNull final String language, @NonNull final String region) {
    try {
        // try some API 21 stuff
        ...
    } catch (final Exception e) {
        e.printStackTrace();
        if (e instanceof IllformedLocaleException) {
            ...
        }
    }

    ...
}
Cataphoresis answered 17/4, 2016 at 4:5 Comment(5)
This works, thank you! Tested on < & > 21 and it runs on < 21 and fails and catches correctly on > 21. If only I knew why this occurred in the first place!Rubberneck
Exact reason is still a mystery to me. Thank you for this question, I learned something new myself.Cataphoresis
@Cataphoresis I'm not fully sure myself, but there are more details in https://mcmap.net/q/129156/-java-lang-verifyerror-illformedlocaleexceptionBroz
Thank you for adding an answer with so much detail. Analyzing the bytecode is definitely the way to go. And yes, it really seems to be that the Dalvik VM does some analysis when it loads classes and all where catch blocks handled strictly. Before I doubted about catch blocks, I also expected methods residing in Activity to be handled exceptionally which was quickly concluded by moving the method away as a static utility method. And the only remaining suspect was the catch block. There also have been answers that suggest reflection as a workaround, and instanceof would be one of them.Cataphoresis
Thanks again for solving my production crashes with this workaround, I've awarded you the bounty for this discovery. I have marked @Broz answer as correct for finding the reason. The best compromise I could find for you both for two great answers.Rubberneck
B
4

TL;DR: Exceptions are exceptional. Can't catch an exception whose type is not known.

The following is most speculation based on my limited knowledge about Java/Dalvik and common sense. Take it with a grain of salt. I found the method that spits out the failing log line and confirmed most of my mentioned speculations, see added links below.

Your problem seems to be that the classes are loaded at once, either the whole class is loaded or none of it. Verification is done first I guess to prevent some runtime checks (remember Android is resource constrained).

I used the following code:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public int setVoice21(@NonNull final String language, @NonNull final String region) {
    try {
        // try some API 21 stuff
        new Locale.Builder().build().getDisplayVariant();
    } catch (final IllformedLocaleException ex) {
        ex.printStackTrace();
    }
    return 0;
}

When the system was trying to create an instance of the class containing this method the following happened:

E/dalvikvm: Could not find class 'java.util.Locale$Builder', referenced from method com.test.TestFragment.setVoice21

Loading the Locale.Builder class would be a ClassNotFoundException.

W/dalvikvm: VFY: unable to resolve new-instance 5241 (Ljava/util/Locale$Builder;) in Lcom/test/TestFragment;
D/dalvikvm: VFY: replacing opcode 0x22 at 0x0000

Then on that non-existent class it would try to call the <init> method which is prevented by replacing the OP_NEW_INSTANCE with a OP_NOP. I think this would have been survivable, as I see these all the time when using the support library. I think the assumption here is that if the class is not found then it must have been guarded with an SDK_INT check. Also if it went through dexing/proguard and other stuff it must've been intentional and a ClassNotFoundException is acceptable at runtime.

W/dalvikvm: VFY: unable to resolve exception class 5234 (Ljava/util/IllformedLocaleException;)

Another problematic class, notice this time it's an "exception class" which must be special. If you check the Java bytecode for this method via:

javap -verbose -l -private -c -s TestFragment.class > TestFragment.dis

public int setVoice21(java.lang.String, java.lang.String);
    ...
    Exception table:
       from    to  target type
           0    14    17   Class java/util/IllformedLocaleException
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
         18      11     3    ex   Ljava/util/IllformedLocaleException;
          0      31     0  this   Lcom/test/TestFragment;
          0      31     1 language   Ljava/lang/String;
          0      31     2 region   Ljava/lang/String;
    StackMapTable: number_of_entries = 2
      frame_type = 81 /* same_locals_1_stack_item */
        stack = [ class java/util/IllformedLocaleException ]
      frame_type = 11 /* same */

You can indeed see that the Exception table and StackMapTable and LocalVariableTable all contain the problematic class, but not Locale$Builder. This may be because the builder is not stored in a variable, but the point to take from here is that exceptions are handled specially and get more scrutiny than normal lines of code.

Using BakSmali on the APK via:

apktool.bat d -r -f -o .\disassembled "app-debug.apk"

.method public setVoice21(Ljava/lang/String;Ljava/lang/String;)I
.prologue
:try_start_0
new-instance v1, Ljava/util/Locale$Builder;
invoke-direct {v1}, Ljava/util/Locale$Builder;-><init>()V
...
:try_end_0
.catch Ljava/util/IllformedLocaleException; {:try_start_0 .. :try_end_0} :catch_0
...
:catch_0
move-exception v0
.local v0, "ex":Ljava/util/IllformedLocaleException;
invoke-virtual {v0}, Ljava/util/IllformedLocaleException;->printStackTrace()V

seems to reveal a similar pattern, here we can actually see the op-codes mentioned in the log. Notice that .catch seems to be a special instruction, not an operation because it's preceded by a dot. I think this reinforces the scrutiny mentioned above: it's not a runtime operation, but it is required for the class to load the code contained within the methods.

W/dalvikvm: VFY: unable to find exception handler at addr 0xe
W/dalvikvm: VFY: rejected Lcom/test/TestFragment;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I

I guess this means that it was not able to reconstruct when to call which catch block from the Exception table and StackMapTable because it couldn't find the class to determine the parent classes. This is confirmed in getCaughtExceptionType where "unable to resolve exception class" directly leads to "unable to find exception handler" because it finds no common super-class for a non-existent exception, something like } catch (? ex) { so it doesn't know what to catch.

W/dalvikvm: VFY: rejecting opcode 0x0d at 0x000e
W/dalvikvm: VFY: rejected Lcom/test/TestFragment;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I

I think at this point the verifier just gave up because it couldn't make sense of the OP_MOVE_EXCEPTION. This is confirmed as that the getCaughtExceptionType method is only used in one place, a switch. Breaking out of that we get "rejecting opcode" then it goto bails up the call stack to "rejected class". After bailing the error code was VERIFY_ERROR_GENERIC which is mapped to VerifyError. Couldn't find where the actual JNI Exception is thrown if it even works that way.

W/dalvikvm: Verifier rejected class Lcom/test/TestFragment;

Multiple rejections were filed against the setVoice21 method and hence the whole class must be rejected (this seems harsh to me, it's possible ART is different in this regard).

W/dalvikvm: Class init failed in newInstance call (Lcom/test/TestFragment;)
D/AndroidRuntime: Shutting down VM
W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x41869da0)
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.bumptech.glide.supportapp.v3, PID: 27649
java.lang.VerifyError: com/test/TestFragment

I guess this is similar to an ExceptionInInitializerError in desktop Java which is thrown when a static { } in class body or an static field initializer throws a RuntimeException/Error.

Why instanceof works

Using razzledazzle's workaround changes those tables to include java/lang/Exception, and moves the dependency to IllformedLocaleException into the code to be executed at runtime:

     0    14    17   Class java/lang/Exception
19: instanceof    #34                 // class java/util/IllformedLocaleException

and similarly the Smali:

.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
instance-of v1, v0, Ljava/util/IllformedLocaleException;

E/dalvikvm: Could not find class 'java.util.IllformedLocaleException', referenced from method com.test.TestFragment.setVoice21
W/dalvikvm: VFY: unable to resolve instanceof 5234 (Ljava/util/IllformedLocaleException;) in Lcom/test/TestFragment;

Now, it's the same complaint as for Locale$Builder above

D/dalvikvm: VFY: replacing opcode 0x20 at 0x000f

Replacing OP_INSTANCE_OF with ?something?, it doesn't say :)

Another possible workaround

If you look at android.support.v4.view.ViewCompat* classes you will notice that not all those classes are used on all versions. The correct one is chosen at runtime (search for static final ViewCompatImpl IMPL in ViewCompat.java) and only that is loaded. This ensures that even at class load time there won't be any weirdness due to missing classes and is performant. You can do a similar architecture to prevent that method from loading on earlier API levels.

Broz answered 23/4, 2016 at 17:33 Comment(3)
So it maintains a collection of catch blocks and for which it checks against the exceptions assigned/mapped to them. And if the exception class can't be resolved, it throws VerifyError, is this correct? Some of the part of the code is still vague for me, such as the handler that it mentions.Cataphoresis
@Cataphoresis added my findings into the text, look for "This is confirmed".Broz
Thank you for such an in-depth answer. Marked it as correct for discovering the cause. I have however awarded the bounty to @Cataphoresis as he provided a workaround which saved me from production crashes. It was the best compromise I could find for two answers that have both helped me greatly.Rubberneck

© 2022 - 2024 — McMap. All rights reserved.