In developing an application recently, I ran into a very odd error that happened in the release build but not the debug build. The stack trace looked like this:
Permission Denial: get/set setting for user asks to run as user -2 but is calling from user 0; this requires android.permission.INTERACT_ACROSS_USERS_FULL java.lang.NullPointerException at java.lang.Enum$1.create(Enum.java:43) at java.lang.Enum$1.create(Enum.java:35) at libcore.util.BasicLruCache.get(BasicLruCache.java:54) at java.lang.Enum.getSharedConstants(Enum.java:209) at java.lang.Enum.valueOf(Enum.java:189) at com.my.app.package.b.c.a(Unknown Source) at com.my.app.package.b.a.onCreate(Unknown Source) at android.support.v4.app.FragmentManagerImpl.moveToState(Unknown Source) at android.support.v4.app.FragmentManagerImpl.moveToState(Unknown Source) at android.support.v4.app.BackStackRecord.run(Unknown Source) at android.support.v4.app.FragmentManagerImpl.execPendingActions(Unknown Source) at android.support.v4.app.FragmentManagerImpl$1.run(Unknown Source) at android.os.Handler.handleCallback(Handler.java:730) at android.os.Handler.dispatchMessage(Handler.java:92) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:5455) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:525) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1187) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003) at dalvik.system.NativeStart.main(Native Method)
After a bunch of head-banging, it turns out that the android.permission.INTERACT_ACROSS_USERS_FULL
part was a complete red herring. The real key in this stack trace is line 7 – the call to java.lang.Enum.valueOf
. I have no explanation why the NullPointerException
that was thrown should have been turned into this error by Android. If you run into a similar error, consider the possibility that the issue is an exception that has nothing to do with cross-user “stuff.”
Having dispensed with that minor piece of nonsense, let’s figure out what actually happened.
Now, any time you get something like this in a release build and the code works perfectly in the debug build, your first thought should be that the culprit is ProGuard. (Assuming you’re using it.) In my case, this is exactly what happened. A little de-obfuscation showed that line 8 above was a call to the valueOf(String)
method on an enum in my code. By default, ProGuard will strip out the values
and valueOf(String)
methods from enum
s, and so the system was blowing up when something that was being called wasn’t found.
First, the solution to the problem: Include the following lines in your ProGuard configuration file:
-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }
This will prevent ProGuard from stripping the enum
methods. Note that both methods are required – more on that in a minute. If you want to get picky, you can do this just on enum
s for which you use these methods, but I figure the few extra bytes in my app wasn’t going to hurt.
Now for the slightly more interesting questions:
- So why was it that ProGuard didn’t detect the fact that I was calling
MyEnum.valueOf(String)
and preserve it? - Why did I have to preserve
values()
as well asvalueOf(String)
?
The answers are:
- Actually, ProGuard did preserve
valueOf(String)
– theNullPointerException
was caused byvalues()
having been stripped. - Because it turns out that
valueOf(String)
is implemented in terms ofvalues()
in many Java implementations, but there’s some reflection involved which keeps ProGuard from seeing this.
Let’s create a simply little enum
class:
public enum TestEnum { A,B }
If you compile this:
javac -classpath . TestEnum.java
and then dump the byte code
javap -c TestEnum.class
you’ll see something like this:
public final class TestEnum extends java.lang.Enum<TestEnum> { public static final TestEnum A; public static final TestEnum B; public static TestEnum[] values(); Code: 0: getstatic #1 // Field $VALUES:[LTestEnum; 3: invokevirtual #2 // Method "[LTestEnum;".clone:()Ljava/lang/Object; 6: checkcast #3 // class "[LTestEnum;" 9: areturn public static TestEnum valueOf(java.lang.String); Code: 0: ldc_w #4 // class TestEnum 3: aload_0 4: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 7: checkcast #4 // class TestEnum 10: areturn static {}; Code: 0: new #4 // class TestEnum 3: dup 4: ldc #7 // String A 6: iconst_0 7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V 10: putstatic #9 // Field A:LTestEnum; 13: new #4 // class TestEnum 16: dup 17: ldc #10 // String B 19: iconst_1 20: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V 23: putstatic #11 // Field B:LTestEnum; 26: iconst_2 27: anewarray #4 // class TestEnum 30: dup 31: iconst_0 32: getstatic #9 // Field A:LTestEnum; 35: aastore 36: dup 37: iconst_1 38: getstatic #11 // Field B:LTestEnum; 41: aastore 42: putstatic #1 // Field $VALUES:[LTestEnum; 45: return }
Now, reading bytecode makes my hair hurt, but it turns out that this is essentially equivalent to the following Java code:
public final class TestEnum extends java.lang.Enum<TestEnum> { public static final TestEnum A; public static final TestEnum B; private static final TestEnum[] VALUES; public static TestEnum[] values() { return VALUES; } public static TestEnum valueOf(java.lang.String s) { return Enum.valueOf(TestEnum.class, s); } static { A = new TestEnum("A", 0); B = new TestEnum("B", 0); VALUES = new TestEnum[2]; VALUES[0] = A; VALUES[1] = B; } }
The thing to notice is that valueOf(String)
is actually implemented by code in the Enum
class. It’s done this way, as far as I can tell, in order to be both efficient and also “lazy,” since many enum
s will never have valueOf(String)
called. Looking at the OpenJDK source code for example, you’ll find the following:
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { T result = enumType.enumConstantDirectory().get(name); if (result != null) { return result; } if (name == null) { throw new NullPointerException("Name is null"); } throw new IllegalArgumentException("No enum const " + enumType +"." + name); }
This, in turn, relies on some code in the Class
class:
Map<String, T> enumConstantDirectory() { if (enumConstantDirectory == null) { T[] universe = getEnumConstantsShared(); if (universe == null) { throw new IllegalArgumentException(getName() + " is not an enum type"); } Map<String, T> m = new HashMap<String, T>(2 * universe.length); for (T constant : universe) { m.put(((Enum)constant).name(), constant); } enumConstantDirectory = m; } return enumConstantDirectory; } private volatile transient Map<String, T> enumConstantDirectory = null; T[] getEnumConstantsShared() { if (enumConstants == null) { if (!isEnum()) return null; try { final Method values = getMethod("values"); java.security.AccessController.doPrivileged (new java.security.PrivilegedAction() { public Object run() { values.setAccessible(true); return null; } }); enumConstants = (T[])values.invoke(null); } catch (InvocationTargetException ex) { return null; } catch (NoSuchMethodException ex) { return null; } catch (IllegalAccessException ex) { return null; } } return enumConstants; } private volatile transient T[] enumConstants = null;
The enumConstantDirectory
provides the String
-to-enum
mapping for the individual enum
class, but it is created lazily based on an array of the individual enum
values returned by getEnumConstantsShared()
. That method, in turn, obtains the array by calling (via reflection) the values()
method on the enum
class.
Twisty. But it does mean, at the end, that (assuming things are implemented this way in your JVM) that valueOf(String)
won’t work if values()
isn’t there. Under normal circumstances this doesn’t pose a problem, since values()
is part of the guaranteed public interface of an enum
class – it’s only when something like ProGuard starts running through your classes with hobnailed boots that a problem like this can occur.
So why doesn’t ProGuard just include values()
any time it sees valueOf(String)
called? My suspicion is that the dependency between these two methods is a JVM implementation choice, not part of the Java specification. Note that the member functions above are not public
, so there’s probably no hard requirement that this functionality be implemented this way. Enum.valueOf(Class,String)
is probably free to use other methods to implement valueOf(String)
, so ProGuard presumably doesn’t (and shouldn’t) presume that it understands enough to link the two functions.
At least, that’s my guess…
This post was written by Kevin Hunter, and originally appeared on Silver Bay Tech’s blog.