| /* |
| * ProGuard -- shrinking, optimization, obfuscation, and preverification |
| * of Java bytecode. |
| * |
| * Copyright (c) 2002-2019 Guardsquare NV |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| package proguard.configuration; |
| |
| |
| import java.io.*; |
| import java.lang.reflect.*; |
| import java.util.*; |
| |
| /** |
| * This class can be injected in applications to log information about reflection |
| * being used in the application code, and suggest appropriate ProGuard rules for |
| * keeping the reflected classes, methods and/or fields. |
| * |
| * @author Johan Leys |
| */ |
| public class ConfigurationLogger implements Runnable |
| { |
| public static final boolean LOG_ONCE = true; |
| |
| private static final String LOG_TAG = "ProGuard"; |
| |
| public static final String CLASS_MAP_FILENAME = "classmap.txt"; |
| |
| private static final String EMPTY_LINE = "\u00a0\n"; |
| |
| // Set with missing class names. |
| private static final Set<String> missingClasses = new HashSet<String>(); |
| |
| // Map from class name to missing constructors. |
| private static final Map<String, Set<MethodSignature>> missingConstructors = new HashMap<String, Set<MethodSignature>>(); |
| // Set of classes on which getConstructors or getDeclaredConstructors is invoked. |
| private static final Set<String> constructorListingClasses = new HashSet<String>(); |
| |
| // Map from class name to missing method signatures. |
| private static final Map<String, Set<MethodSignature>> missingMethods = new HashMap<String, Set<MethodSignature>>(); |
| // Set of classes on which getMethods or getDeclaredMethods is invoked. |
| private static final Set<String> methodListingClasses = new HashSet<String>(); |
| |
| // Map from class name to missing field names. |
| private static final Map<String, Set<String>> missingFields = new HashMap<String, Set<String>>(); |
| // Set of classes on which getFields or getDeclaredFields is invoked. |
| private static final Set<String> fieldListingCLasses = new HashSet<String>(); |
| |
| // Map from obfuscated class name to original class name. |
| private static Map<String, String> classNameMap; |
| |
| // Set of classes that have renamed or removed methods. |
| private static Set<String> classesWithObfuscatedMethods; |
| |
| // Set of classes that have renamed or removed fields. |
| private static Set<String> classesWithObfuscatedFields; |
| |
| private static Method logMethod; |
| |
| // Try to find the Android logging class. |
| static |
| { |
| try |
| { |
| Class<?> logClass = Class.forName("android.util.Log"); |
| logMethod = logClass.getMethod("w", String.class, String. class); |
| } |
| catch (Exception e) {} |
| } |
| |
| // Classes. |
| |
| /** |
| * Log a failed call to Class.forName(). |
| * |
| * @param callingClassName |
| * @param missingClassName |
| */ |
| public static void logForName(String callingClassName, |
| String missingClassName) |
| { |
| logMissingClass(callingClassName, "Class", "forName", missingClassName); |
| } |
| |
| /** |
| * Log a failed call to ClassLoader.loadClass(). |
| * |
| * @param callingClassName |
| * @param missingClassName |
| */ |
| public static void logLoadClass(String callingClassName, |
| String missingClassName) |
| { |
| logMissingClass(callingClassName, "ClassLoader", "loadClass", missingClassName); |
| } |
| |
| |
| /** |
| * Log a failed call to Class.forName(). |
| * |
| * @param callingClassName |
| * @param missingClassName |
| */ |
| public static void logMissingClass(String callingClassName, |
| String invokedClassName, |
| String invokedMethodName, |
| String missingClassName) |
| { |
| if (!LOG_ONCE || !missingClasses.contains(missingClassName)) |
| { |
| missingClasses.add(missingClassName); |
| log( |
| "The class '" + originalClassName(callingClassName) + "' is calling " + invokedClassName + "." + invokedMethodName + " to retrieve\n" + |
| "the class '" + missingClassName + "', but the latter could not be found.\n" + |
| "It may have been obfuscated or shrunk.\n" + |
| "You should consider preserving the class with its original name,\n" + |
| "with a setting like:\n" + |
| EMPTY_LINE + |
| keepClassRule(missingClassName) + "\n" + |
| EMPTY_LINE); |
| } |
| } |
| |
| |
| // Constructors. |
| |
| |
| /** |
| * Log a failed call to Class.getDeclaredConstructor(). |
| * |
| * @param invokingClassName |
| * @param reflectedClass |
| * @param constructorParameters |
| */ |
| public static void logGetDeclaredConstructor(String invokingClassName, |
| Class reflectedClass, |
| Class[] constructorParameters) |
| { |
| logGetConstructor(invokingClassName, "getDeclaredConstructor", reflectedClass, constructorParameters); |
| } |
| |
| |
| /** |
| * Log a failed call to Class.getConstructor(). |
| * |
| * @param invokingClassName |
| * @param reflectedClass |
| * @param constructorParameters |
| */ |
| public static void logGetConstructor(String invokingClassName, |
| Class reflectedClass, |
| Class[] constructorParameters) |
| { |
| logGetConstructor(invokingClassName, "getConstructor", reflectedClass, constructorParameters); |
| } |
| |
| |
| /** |
| * Log a failed call to one of the constructor retrieving methods on Class. |
| * |
| * @param invokingClassName |
| * @param invokedMethodName |
| * @param reflectedClass |
| * @param constructorParameters |
| */ |
| public static void logGetConstructor(String invokingClassName, |
| String invokedMethodName, |
| Class reflectedClass, |
| Class[] constructorParameters) |
| { |
| MethodSignature signature = new MethodSignature("<init>", constructorParameters); |
| |
| Set<MethodSignature> constructors = missingConstructors.get(reflectedClass.getName()); |
| if (constructors == null) |
| { |
| constructors = new HashSet<MethodSignature>(); |
| missingConstructors.put(reflectedClass.getName(), constructors); |
| } |
| |
| if ((!LOG_ONCE || !constructors.contains(signature)) && !isLibraryClass(reflectedClass)) |
| { |
| constructors.add(signature); |
| log( |
| "The class '" + originalClassName(invokingClassName) + "' is calling Class." + invokedMethodName + "\n" + |
| "on class '" + originalClassName(reflectedClass) + "' to retrieve\n" + |
| "the constructor with signature (" + originalSignature(signature) + "), but the latter could not be found.\n" + |
| "It may have been obfuscated or shrunk.\n" + |
| "You should consider preserving the constructor, with a setting like:\n" + |
| EMPTY_LINE + |
| keepConstructorRule(reflectedClass.getName(), signature) + "\n" + |
| EMPTY_LINE); |
| } |
| } |
| |
| |
| /** |
| * Log a call to Class.getDeclaredConstructors(). |
| * |
| * @param invokingClassName |
| * @param reflectedClass |
| */ |
| public static void logGetDeclaredConstructors(String invokingClassName, |
| Class reflectedClass ) |
| { |
| logGetConstructors(invokingClassName, reflectedClass, "getDeclaredConstructors"); |
| } |
| |
| |
| /** |
| * Log a call to Class.getConstructors(). |
| * |
| * @param invokingClassName |
| * @param reflectedClass |
| */ |
| public static void logGetConstructors(String invokingClassName, |
| Class reflectedClass ) |
| { |
| logGetConstructors(invokingClassName, reflectedClass, "getConstructors"); |
| } |
| |
| |
| /** |
| * Log a call to one of the constructor listing methods on Class. |
| * |
| * @param invokingClassName |
| * @param reflectedClass |
| * @param reflectedMethodName |
| */ |
| private static void logGetConstructors(String invokingClassName, |
| Class reflectedClass, |
| String reflectedMethodName) |
| { |
| initializeMappings(); |
| if (classesWithObfuscatedMethods.contains(reflectedClass.getName()) && |
| !constructorListingClasses.contains(reflectedClass.getName()) && |
| !isLibraryClass(reflectedClass)) |
| { |
| constructorListingClasses.add(reflectedClass.getName()); |
| log( |
| "The class '" + originalClassName(invokingClassName) + "' is calling Class." + reflectedMethodName + "\n" + |
| "on class '" + originalClassName(reflectedClass) + "' to retrieve its constructors.\n" + |
| "You might consider preserving all constructors with their original names,\n" + |
| "with a setting like:\n" + |
| EMPTY_LINE + |
| keepAllConstructorsRule(reflectedClass) + "\n" + |
| EMPTY_LINE); |
| } |
| } |
| |
| |
| // Methods. |
| |
| |
| /** |
| * Log a failed call to Class.getDeclaredMethod(). |
| * |
| * @param invokingClassName |
| * @param reflectedClass |
| * @param reflectedMethodName |
| * @param methodParameters |
| */ |
| public static void logGetDeclaredMethod(String invokingClassName, |
| Class reflectedClass, |
| String reflectedMethodName, |
| Class[] methodParameters ) |
| { |
| logGetMethod(invokingClassName, "getDeclaredMethod", reflectedClass, reflectedMethodName, methodParameters); |
| } |
| |
| |
| /** |
| * Log a failed call to Class.getMethod(). |
| * |
| * @param invokingClassName |
| * @param reflectedClass |
| * @param reflectedMethodName |
| * @param methodParameters |
| */ |
| public static void logGetMethod(String invokingClassName, |
| Class reflectedClass, |
| String reflectedMethodName, |
| Class[] methodParameters ) |
| { |
| logGetMethod(invokingClassName, "getMethod", reflectedClass, reflectedMethodName, methodParameters); |
| } |
| |
| |
| /** |
| * Log a failed call to one of the method retrieving methods on Class. |
| * @param invokingClassName |
| * @param invokedReflectionMethodName |
| * @param reflectedClass |
| * @param reflectedMethodName |
| * @param methodParameters |
| */ |
| private static void logGetMethod(String invokingClassName, |
| String invokedReflectionMethodName, |
| Class reflectedClass, |
| String reflectedMethodName, |
| Class[] methodParameters ) |
| { |
| Set<MethodSignature> methods = missingMethods.get(reflectedClass.getName()); |
| if (methods == null) |
| { |
| methods = new HashSet<MethodSignature>(); |
| missingMethods.put(reflectedClass.getName(), methods); |
| } |
| |
| MethodSignature signature = new MethodSignature(reflectedMethodName, methodParameters); |
| if (!methods.contains(signature) && !isLibraryClass(reflectedClass)) |
| { |
| methods.add(signature); |
| log( |
| "The class '" + originalClassName(invokingClassName) + |
| "' is calling Class." + invokedReflectionMethodName + "\n" + |
| "on class '" + originalClassName(reflectedClass) + |
| "' to retrieve the method\n" + |
| reflectedMethodName + "(" + originalSignature(signature) + "),\n" + |
| "but the latter could not be found. It may have been obfuscated or shrunk.\n" + |
| "You should consider preserving the method with its original name,\n" + |
| "with a setting like:\n" + |
| EMPTY_LINE + |
| keepMethodRule(reflectedClass.getName(), reflectedMethodName, signature) + "\n" + |
| EMPTY_LINE); |
| } |
| } |
| |
| |
| /** |
| * Log a call to Class.getDeclaredMethods(). |
| * |
| * @param invokingClassName |
| * @param reflectedClass |
| */ |
| public static void logGetDeclaredMethods(String invokingClassName, |
| Class reflectedClass ) |
| { |
| logGetMethods(invokingClassName, "getDeclaredMethods", reflectedClass); |
| } |
| |
| |
| /** |
| * Log a call to Class.getMethods(). |
| * |
| * @param invokingClassName |
| * @param reflectedClass |
| */ |
| public static void logGetMethods(String invokingClassName, |
| Class reflectedClass ) |
| { |
| logGetMethods(invokingClassName, "getMethods", reflectedClass); |
| } |
| |
| |
| /** |
| * Log a call to one of the method listing methods on Class. |
| * |
| * @param invokingClassName |
| * @param invokedReflectionMethodName |
| * @param reflectedClass |
| */ |
| private static void logGetMethods(String invokingClassName, |
| String invokedReflectionMethodName, |
| Class reflectedClass ) |
| { |
| initializeMappings(); |
| if (classesWithObfuscatedMethods.contains(reflectedClass.getName()) && |
| !methodListingClasses.contains(reflectedClass.getName()) && |
| !isLibraryClass(reflectedClass)) |
| { |
| methodListingClasses.add(reflectedClass.getName()); |
| log( |
| "The class '" + originalClassName(invokingClassName) + |
| "' is calling Class." + invokedReflectionMethodName + "\n" + |
| "on class '" + originalClassName(reflectedClass) + |
| "' to retrieve its methods.\n" + |
| "You might consider preserving all methods with their original names,\n" + |
| "with a setting like:\n" + |
| EMPTY_LINE + |
| keepAllMethodsRule(reflectedClass) + "\n" + |
| EMPTY_LINE); |
| } |
| } |
| |
| |
| // Fields. |
| |
| |
| /** |
| * Log a failed call to Class.getField(). |
| * |
| * @param invokingClassName |
| * @param reflectedClass |
| * @param reflectedFieldName |
| */ |
| public static void logGetField(String invokingClassName, |
| Class reflectedClass, |
| String reflectedFieldName) |
| { |
| logGetField(invokingClassName, "getField", reflectedClass, reflectedFieldName); |
| } |
| |
| |
| /** |
| * Log a failed call to Class.getDeclaredField(). |
| * |
| * @param invokingClassName |
| * @param reflectedClass |
| * @param reflectedFieldName |
| */ |
| public static void logGetDeclaredField(String invokingClassName, |
| Class reflectedClass, |
| String reflectedFieldName) |
| { |
| logGetField(invokingClassName, "getDeclaredField", reflectedClass, reflectedFieldName); |
| } |
| |
| |
| /** |
| * Log a failed call to one of the field retrieving methods of Class. |
| * |
| * @param invokingClassName |
| * @param invokedReflectionMethodName |
| * @param reflectedClass |
| * @param reflectedFieldName |
| */ |
| private static void logGetField(String invokingClassName, |
| String invokedReflectionMethodName, |
| Class reflectedClass, |
| String reflectedFieldName ) |
| { |
| Set<String> fields = missingFields.get(reflectedClass.getName()); |
| if (fields == null) |
| { |
| fields = new HashSet<String>(); |
| missingFields.put(reflectedClass.getName(), fields); |
| } |
| |
| if ((!LOG_ONCE || !fields.contains(reflectedFieldName)) && |
| !isLibraryClass(reflectedClass)) |
| { |
| fields.add(reflectedFieldName); |
| log( |
| "The class '" + originalClassName(invokingClassName) + |
| "' is calling Class." + invokedReflectionMethodName + "\n" + |
| "on class '" + originalClassName(reflectedClass) + |
| "' to retrieve the field '" + reflectedFieldName + "',\n" + |
| "but the latter could not be found. It may have been obfuscated or shrunk.\n" + |
| "You should consider preserving the field with its original name,\n" + |
| "with a setting like:\n" + |
| EMPTY_LINE + |
| keepFieldRule(reflectedClass.getName(), reflectedFieldName) + "\n" + |
| EMPTY_LINE); |
| } |
| } |
| |
| |
| /** |
| * Log a call to Class.getDeclaredFields(). |
| * |
| * @param invokingClassName |
| * @param reflectedClass |
| */ |
| public static void logGetDeclaredFields(String invokingClassName, |
| Class reflectedClass ) |
| { |
| logGetFields(invokingClassName, "getDeclaredFields", reflectedClass); |
| } |
| |
| |
| /** |
| * Log a call to Class.getFields(). |
| * |
| * @param invokingClassName |
| * @param reflectedClass |
| */ |
| public static void logGetFields(String invokingClassName, |
| Class reflectedClass ) |
| { |
| logGetFields(invokingClassName, "getFields", reflectedClass); |
| } |
| |
| |
| /** |
| * Log a call to one of the field listing methods on Class. |
| * |
| * @param invokingClassName |
| * @param invokedReflectionMethodName |
| * @param reflectedClass |
| */ |
| private static void logGetFields(String invokingClassName, |
| String invokedReflectionMethodName, |
| Class reflectedClass ) |
| { |
| initializeMappings(); |
| if (classesWithObfuscatedFields.contains(reflectedClass.getName()) && |
| !fieldListingCLasses.contains(reflectedClass.getName()) && |
| !isLibraryClass(reflectedClass)) |
| { |
| fieldListingCLasses.add(reflectedClass.getName()); |
| log( |
| "The class '" + originalClassName(invokingClassName) + |
| "' is calling Class." + invokedReflectionMethodName + "\n" + |
| "on class '" + originalClassName(reflectedClass) + |
| "' to retrieve its fields.\n" + |
| "You might consider preserving all fields with their original names,\n" + |
| "with a setting like:\n" + |
| EMPTY_LINE + |
| keepAllFieldsRule(reflectedClass) + "\n" + |
| EMPTY_LINE); |
| } |
| } |
| |
| |
| // Implementations for Runnable. |
| |
| public void run() |
| { |
| printConfiguration(); |
| } |
| |
| |
| private static void printConfiguration() |
| { |
| log("The following settings may help solving issues related to\n" + |
| "missing classes, methods and/or fields:\n"); |
| |
| for (String clazz : missingClasses) |
| { |
| log(keepClassRule(clazz) + "\n"); |
| } |
| |
| for (String clazz : missingConstructors.keySet()) |
| { |
| for (MethodSignature constructor : missingConstructors.get(clazz)) |
| { |
| log(keepConstructorRule(clazz, constructor) + "\n"); |
| } |
| } |
| |
| for (String clazz : missingMethods.keySet()) |
| { |
| for (MethodSignature method : missingMethods.get(clazz)) |
| { |
| log(keepMethodRule(clazz, method.name, method) + "\n"); |
| } |
| } |
| |
| for (String clazz : missingFields.keySet()) |
| { |
| for (String field : missingFields.get(clazz)) |
| { |
| log(keepFieldRule(clazz, field) + "\n"); |
| } |
| } |
| } |
| |
| |
| // ProGuard rules. |
| |
| private static String keepClassRule(String className) |
| { |
| return "-keep class " + className; |
| } |
| |
| |
| private static String keepConstructorRule(String className, |
| MethodSignature constructorParameters) |
| { |
| return "-keepclassmembers class " + originalClassName(className) + " {\n" + |
| " public <init>(" + originalSignature(constructorParameters) + ");\n" + |
| "}"; |
| } |
| |
| |
| private static String keepMethodRule(String className, |
| String methodName, |
| MethodSignature constructorParameters) |
| { |
| return "-keepclassmembers class " + originalClassName(className) + " {\n" + |
| " *** " + methodName + "(" + originalSignature(constructorParameters) + ");\n" + |
| "}"; |
| } |
| |
| |
| private static String keepFieldRule(String className, |
| String fieldName) |
| { |
| return "-keepclassmembers class " + originalClassName(className) + " {\n" + |
| " *** " + fieldName + ";\n" + |
| "}"; |
| } |
| |
| |
| private static String keepAllConstructorsRule(Class className) |
| { |
| return "-keepclassmembers class " + originalClassName(className) + " {\n" + |
| " <init>(...);\n" + |
| "}"; |
| } |
| |
| |
| private static String keepAllMethodsRule(Class className) |
| { |
| return "-keepclassmembers class " + originalClassName(className) + " {\n" + |
| " <methods>;\n" + |
| "}"; |
| } |
| |
| |
| private static String keepAllFieldsRule(Class className) |
| { |
| return "-keepclassmembers class " + originalClassName(className) + " {\n" + |
| " <fields>;\n" + |
| "}"; |
| } |
| |
| |
| private static String originalClassName(Class className) |
| { |
| return originalClassName(className.getName()); |
| } |
| |
| |
| private static String originalClassName(String className) |
| { |
| initializeMappings(); |
| String originalClassName = classNameMap.get(className); |
| return originalClassName != null ? originalClassName : className; |
| } |
| |
| |
| /** |
| * Simple heuristic to see if the given class is a library class or not. |
| * |
| * @param clazz |
| * @return |
| */ |
| private static boolean isLibraryClass(Class clazz) |
| { |
| return clazz.getClassLoader() == String.class.getClassLoader(); |
| } |
| |
| |
| /** |
| * Log a message, either on the Android Logcat, if available, or on the |
| * Standard error outputstream otherwise. |
| * |
| * @param message the message to be logged. |
| */ |
| private static void log(String message) |
| { |
| if (logMethod != null) |
| { |
| try |
| { |
| logMethod.invoke(null, LOG_TAG, message); |
| } |
| catch (Exception e) |
| { |
| System.err.println(message); |
| } |
| } |
| else |
| { |
| System.err.println(message); |
| } |
| } |
| |
| |
| private static void initializeMappings() |
| { |
| if (classNameMap == null) |
| { |
| classNameMap = new HashMap<String, String> (); |
| classesWithObfuscatedMethods = new HashSet<String> (); |
| classesWithObfuscatedFields = new HashSet<String> (); |
| |
| String line; |
| try |
| { |
| BufferedReader reader = |
| new BufferedReader( |
| new InputStreamReader( |
| ConfigurationLogger.class.getClassLoader().getResourceAsStream(CLASS_MAP_FILENAME))); |
| |
| while ((line = reader.readLine()) != null) |
| { |
| StringTokenizer tokenizer = new StringTokenizer(line, ","); |
| String originalClassName = tokenizer.nextToken(); |
| String obfuscatedClassName = tokenizer.nextToken(); |
| boolean hasObfuscatedMethods = tokenizer.nextToken().equals("1"); |
| boolean hasObfuscatedFields = tokenizer.nextToken().equals("1"); |
| |
| classNameMap.put(obfuscatedClassName, originalClassName); |
| |
| if (hasObfuscatedMethods) |
| { |
| classesWithObfuscatedMethods.add(obfuscatedClassName); |
| } |
| |
| if (hasObfuscatedFields) |
| { |
| classesWithObfuscatedFields.add(obfuscatedClassName); |
| } |
| } |
| reader.close(); |
| } |
| catch (IOException e) |
| { |
| e.printStackTrace(); |
| } |
| } |
| } |
| |
| |
| private static String originalSignature(MethodSignature signature) |
| { |
| StringBuilder stringBuilder = new StringBuilder(); |
| boolean first = true; |
| for (String clazz : signature.parameters) |
| { |
| if (first) |
| { |
| first = false; |
| } |
| else |
| { |
| stringBuilder.append(","); |
| } |
| stringBuilder.append(originalClassName(clazz)); |
| } |
| return stringBuilder.toString(); |
| } |
| |
| |
| public static class MethodSignature |
| { |
| private String name; |
| private String[] parameters; |
| |
| |
| public MethodSignature(String name, Class[] parameters) |
| { |
| this.name = name; |
| this.parameters = new String[parameters.length]; |
| for (int i = 0; i < parameters.length; i++) |
| { |
| this.parameters[i] = parameters[i].getName(); |
| } |
| } |
| |
| |
| // Implementations for Object. |
| |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| MethodSignature that = (MethodSignature)o; |
| |
| if (!name.equals(that.name)) return false; |
| return Arrays.equals(parameters, that.parameters); |
| } |
| |
| |
| public int hashCode() |
| { |
| int result = name.hashCode(); |
| result = 31 * result + Arrays.hashCode(parameters); |
| return result; |
| } |
| } |
| } |