From c0639316b108ea4ba98d7c74187de4078858c660 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Tue, 24 Sep 2019 16:05:12 -0400 Subject: [PATCH] Don't try to access unsafe APIs on android 29+ (#913) * Don't try to access unsafe APIs on android 29+ Resolves #872. Not sure if we want to try caching some of these lookups * Checkstyle * Use correct inner class naming Co-Authored-By: Jake Wharton * Name variable ignored * Cache android check --- .../java/com/squareup/moshi/ClassFactory.java | 63 ++++++++++++------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/moshi/src/main/java/com/squareup/moshi/ClassFactory.java b/moshi/src/main/java/com/squareup/moshi/ClassFactory.java index b332039..87d21ae 100644 --- a/moshi/src/main/java/com/squareup/moshi/ClassFactory.java +++ b/moshi/src/main/java/com/squareup/moshi/ClassFactory.java @@ -34,6 +34,9 @@ abstract class ClassFactory { abstract T newInstance() throws InvocationTargetException, IllegalAccessException, InstantiationException; + private static volatile boolean androidChecked = false; + private static volatile boolean tryUnsafe = true; + public static ClassFactory get(final Class rawType) { // Try to find a no-args constructor. May be any visibility including private. try { @@ -54,29 +57,45 @@ abstract class ClassFactory { // No no-args constructor. Fall back to something more magical... } - // Try the JVM's Unsafe mechanism. - // public class Unsafe { - // public Object allocateInstance(Class type); - // } - try { - Class unsafeClass = Class.forName("sun.misc.Unsafe"); - Field f = unsafeClass.getDeclaredField("theUnsafe"); - f.setAccessible(true); - final Object unsafe = f.get(null); - final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class); - return new ClassFactory() { - @SuppressWarnings("unchecked") - @Override public T newInstance() throws InvocationTargetException, IllegalAccessException { - return (T) allocateInstance.invoke(unsafe, rawType); + if (!androidChecked) { + try { + Class androidBuildClass = Class.forName("android.os.Build$VERSION"); + Field sdkIntField = androidBuildClass.getDeclaredField("SDK_INT"); + int sdk = (int) sdkIntField.get(null); + if (sdk >= 29) { + tryUnsafe = false; } - @Override public String toString() { - return rawType.getName(); - } - }; - } catch (IllegalAccessException e) { - throw new AssertionError(); - } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException ignored) { - // Not the expected version of the Oracle Java library! + } catch (Exception ignored) { + } finally { + androidChecked = true; + } + } + if (tryUnsafe) { + // Try the JVM's Unsafe mechanism. + // public class Unsafe { + // public Object allocateInstance(Class type); + // } + try { + Class unsafeClass = Class.forName("sun.misc.Unsafe"); + Field f = unsafeClass.getDeclaredField("theUnsafe"); + f.setAccessible(true); + final Object unsafe = f.get(null); + final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class); + return new ClassFactory() { + @SuppressWarnings("unchecked") + @Override + public T newInstance() throws InvocationTargetException, IllegalAccessException { + return (T) allocateInstance.invoke(unsafe, rawType); + } + @Override public String toString() { + return rawType.getName(); + } + }; + } catch (IllegalAccessException e) { + throw new AssertionError(); + } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException ignored) { + // Not the expected version of the Oracle Java library! + } } // Try (post-Gingerbread) Dalvik/libcore's ObjectStreamClass mechanism.