Proguard源码分析(五) ConfigurationParser.keep参数
本章節我們繞回來講Keep參數,也就是ConfigurationParser 這個類。
ConfigurationParser這個類是非常重要的類,如果你已經開始看源碼,你會發現所有的類和功能都圍著它來轉,本章節我們來揭開它的地一層面紗。
else if (ConfigurationConstants.KEEP_OPTION.startsWith(nextWord))
??? ??? ??? ??? configuration.keep = parseKeepClassSpecificationArguments(
??? ??? ??? ??? ??? ??? configuration.keep, true, false, false);
??? ??? ??? else if (ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION
??? ??? ??? ??? ??? .startsWith(nextWord))
??? ??? ??? ??? configuration.keep = parseKeepClassSpecificationArguments(
??? ??? ??? ??? ??? ??? configuration.keep, false, false, false);
??? ??? ??? else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION
??? ??? ??? ??? ??? .startsWith(nextWord))
??? ??? ??? ??? configuration.keep = parseKeepClassSpecificationArguments(
??? ??? ??? ??? ??? ??? configuration.keep, false, true, false);
??? ??? ??? else if (ConfigurationConstants.KEEP_NAMES_OPTION
??? ??? ??? ??? ??? .startsWith(nextWord))
??? ??? ??? ??? configuration.keep = parseKeepClassSpecificationArguments(
??? ??? ??? ??? ??? ??? configuration.keep, true, false, true);
??? ??? ??? else if (ConfigurationConstants.KEEP_CLASS_MEMBER_NAMES_OPTION
??? ??? ??? ??? ??? .startsWith(nextWord))
??? ??? ??? ??? configuration.keep = parseKeepClassSpecificationArguments(
??? ??? ??? ??? ??? ??? configuration.keep, false, false, true);
??? ??? ??? else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION
??? ??? ??? ??? ??? .startsWith(nextWord))
??? ??? ??? ??? configuration.keep = parseKeepClassSpecificationArguments(
??? ??? ??? ??? ??? ??? configuration.keep, false, true, true);
可見,所有以keep打頭的參數都是調用的parseKeepClassSpecificationArguments
跟一下這個函數的邏輯
while (true) {
??? ??? ??? readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD
??? ??? ??? ??? ??? + "', '" + ClassConstants.EXTERNAL_ACC_INTERFACE
??? ??? ??? ??? ??? + "', or '" + ClassConstants.EXTERNAL_ACC_ENUM + "'",
??? ??? ??? ??? ??? false, true);
??? ??? ??? if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD
??? ??? ??? ??? ??? .equals(nextWord)) {
??? ??? ??? ??? // Not a comma. Stop parsing the keep modifiers.
??? ??? ??? ??? break;
??? ??? ??? }
??? ??? ??? readNextWord("keyword '"
??? ??? ??? ??? ??? + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION + "', '"
??? ??? ??? ??? ??? + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION
??? ??? ??? ??? ??? + "', or '"
??? ??? ??? ??? ??? + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION + "'");
??? ??? ??? if (ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION
??? ??? ??? ??? ??? .startsWith(nextWord)) {
??? ??? ??? ??? allowShrinking = true;
??? ??? ??? } else if (ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION
??? ??? ??? ??? ??? .startsWith(nextWord)) {
??? ??? ??? ??? allowOptimization = true;
??? ??? ??? } else if (ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION
??? ??? ??? ??? ??? .startsWith(nextWord)) {
??? ??? ??? ??? allowObfuscation = true;
??? ??? ??? } else {
??? ??? ??? ??? throw new ParseException("Expecting keyword '"
??? ??? ??? ??? ??? ??? + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION
??? ??? ??? ??? ??? ??? + "', '"
??? ??? ??? ??? ??? ??? + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION
??? ??? ??? ??? ??? ??? + "', or '"
??? ??? ??? ??? ??? ??? + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION
??? ??? ??? ??? ??? ??? + "' before " + reader.locationDescription());
??? ??? ??? }
??? ??? }
ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD
??? ??? ??? ??? ??? .equals(nextWord)代表以非ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD 作為終結符號,也就是說在keep之后可以跟一些參數,這些參數我們來看一下~
??? public static final String ALLOW_SHRINKING_SUBOPTION???????????? = "allowshrinking";
??? public static final String ALLOW_OPTIMIZATION_SUBOPTION????????? = "allowoptimization";
??? public static final String ALLOW_OBFUSCATION_SUBOPTION?????????? = "allowobfuscation";
也就是說你可以采用下面這種寫法:
-keep,allowshrinking,allowoptimization public class *;
我們來看一下這三個參數的影響:
if ((shrinking?? && !keepClassSpecification.allowShrinking)??? ||
??????????????????? (optimizing? && !keepClassSpecification.allowOptimization) ||
??????????????????? (obfuscating && !keepClassSpecification.allowObfuscation))
{
??????????????? ??? ClassPoolVisitor classPoolVisitor = createClassPoolVisitor(keepClassSpecification,
??????????????????????????? classVisitor,
??????????????????????????? memberVisitor);
??????????????????? multiClassPoolVisitor.addClassPoolVisitor(classPoolVisitor);
}
結果似乎并不是我想的那樣,要對這個類不做任何處理,必須保證這三個參數都為true.
在這之后會調用parseClassSpecificationArguments() 來生成一個ClassSpecification 的原始數據
???????????? classSpecification.requiredSetAccessFlags,
???????????? classSpecification.requiredUnsetAccessFlags,
???????????? classSpecification.annotationType,
???????????? classSpecification.className,
???????????? classSpecification.extendsAnnotationType,
???????????? classSpecification.extendsClassName,
???????????? classSpecification.fieldSpecifications,
???????????? classSpecification.methodSpecifications
requiredSetAccessFlags 和requiredUnsetAccessFlags 兩個是必須設置的
它是檢測是否加載該類的入口之一。他們的值是:
public static final int INTERNAL_ACC_PUBLIC?????? = 0x0001;
??? public static final int INTERNAL_ACC_PRIVATE????? = 0x0002;
??? public static final int INTERNAL_ACC_PROTECTED??? = 0x0004;
??? public static final int INTERNAL_ACC_STATIC?????? = 0x0008;
??? public static final int INTERNAL_ACC_FINAL??????? = 0x0010;
??? public static final int INTERNAL_ACC_SUPER??????? = 0x0020;
??? public static final int INTERNAL_ACC_SYNCHRONIZED = 0x0020;
??? public static final int INTERNAL_ACC_VOLATILE???? = 0x0040;
??? public static final int INTERNAL_ACC_TRANSIENT??? = 0x0080;
??? public static final int INTERNAL_ACC_BRIDGE?????? = 0x0040;
??? public static final int INTERNAL_ACC_VARARGS????? = 0x0080;
??? public static final int INTERNAL_ACC_NATIVE?????? = 0x0100;
??? public static final int INTERNAL_ACC_INTERFACE??? = 0x0200;
??? public static final int INTERNAL_ACC_ABSTRACT???? = 0x0400;
??? public static final int INTERNAL_ACC_STRICT?????? = 0x0800;
??? public static final int INTERNAL_ACC_SYNTHETIC??? = 0x1000;
??? public static final int INTERNAL_ACC_ANNOTATTION? = 0x2000;
??? public static final int INTERNAL_ACC_ENUM???????? = 0x4000;
parseClassSpecificationArguments() 方法中定義了class的寫法
當你的:
if (accessFlag == ClassConstants.INTERNAL_ACC_ANNOTATTION) {
??? ??? ??? ??? // Already read the next word.
??? ??? ??? ??? readNextWord("annotation type or keyword '"
??? ??? ??? ??? ??? ??? + ClassConstants.EXTERNAL_ACC_INTERFACE + "'", false,
??? ??? ??? ??? ??? ??? false);
??? ??? ??? ??? // Is the next word actually an annotation type?
??? ??? ??? ??? if (!nextWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE)
??? ??? ??? ??? ??? ??? && !nextWord.equals(ClassConstants.EXTERNAL_ACC_ENUM)
??? ??? ??? ??? ??? ??? && !nextWord.equals(ConfigurationConstants.CLASS_KEYWORD)) {
??? ??? ??? ??? ??? // Parse the annotation type.
??? ??? ??? ??? ??? annotationType = ListUtil.commaSeparatedString(
??? ??? ??? ??? ??? ??? ??? parseCommaSeparatedList("annotation type", false,
??? ??? ??? ??? ??? ??? ??? ??? ??? false, false, false, true, false, false,
??? ??? ??? ??? ??? ??? ??? ??? ??? true, null), false);
??? ??? ??? ??? ??? // Continue parsing the access modifier that we just read
??? ??? ??? ??? ??? // in the next cycle.
??? ??? ??? ??? ??? continue;
??? ??? ??? ??? }
??? ??? ??? ??? // Otherwise just handle the annotation modifier.
??? ??? ??? }
accessFlag 為注解符號的時候,大致寫法是這樣的:
-keep @com.test.TestAnno
public class * {
*;
}
-keepclassmembers class * {
??? @com.test.TestAnno <methods>;
}
也就是說完全按照java的語法標準來實現。
解析完注解之后直到解析class interface enum 這些關鍵字
if (strippedWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE)
??? ??? ??? ??? ??? || strippedWord.equals(ClassConstants.EXTERNAL_ACC_ENUM)
??? ??? ??? ??? ??? || strippedWord.equals(ConfigurationConstants.CLASS_KEYWORD)) {
??? ??? ??? ??? // The interface or enum keyword. Stop parsing the class flags.
??? ??? ??? ??? break;
??? ??? ??? }
得到externalClassName
之后調用
if (!configurationEnd()) {
??? ??? ??? // Parse 'implements ...' or 'extends ...' part, if any.
??? ??? ??? if (ConfigurationConstants.IMPLEMENTS_KEYWORD.equals(nextWord)
??? ??? ??? ??? ??? || ConfigurationConstants.EXTENDS_KEYWORD.equals(nextWord)) {
??? ??? ??? ??? readNextWord("class name or interface name", false, true);
??? ??? ??? ??? // Parse the annotation type, if any.
??? ??? ??? ??? LOG.log("start ");
??? ??? ??? ??? if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord)) {
??? ??? ??? ??? ??? extendsAnnotationType = ListUtil.commaSeparatedString(
??? ??? ??? ??? ??? ??? ??? parseCommaSeparatedList("annotation type", true,
??? ??? ??? ??? ??? ??? ??? ??? ??? false, false, false, true, false, false,
??? ??? ??? ??? ??? ??? ??? ??? ??? true, null), false);
??? ??? ??? ??? }
??? ??? ??? ??? String externalExtendsClassName = ListUtil
??? ??? ??? ??? ??? ??? .commaSeparatedString(
??? ??? ??? ??? ??? ??? ??? ??? parseCommaSeparatedList(
??? ??? ??? ??? ??? ??? ??? ??? ??? ??? "class name or interface name", false,
??? ??? ??? ??? ??? ??? ??? ??? ??? ??? false, false, false, true, false,
??? ??? ??? ??? ??? ??? ??? ??? ??? ??? false, false, null), false);
??? ??? ??? ??? extendsClassName = ConfigurationConstants.ANY_CLASS_KEYWORD
??? ??? ??? ??? ??? ??? .equals(externalExtendsClassName) ? null : ClassUtil
??? ??? ??? ??? ??? ??? .internalClassName(externalExtendsClassName);
??? ??? ??? }
??? ??? }
configurationEnd() 的結束條件是-和@,那么括號里面的又是干什么用的呢?
這是一種語法結構大致結構是這個樣子的:
-keep public class * extends @com.test.TestAnno * #here
{
*;
}
解析到here這個位置,代表保持這個這個標注注解類的子類
最后將定義個類的元數據:
ClassSpecification classSpecification = new ClassSpecification(
??? ??? ??? ??? lastComments, requiredSetClassAccessFlags,
??? ??? ??? ??? requiredUnsetClassAccessFlags, annotationType, className,
??? ??? ??? ??? extendsAnnotationType, extendsClassName);
進行下一次的匹配,
if (!configurationEnd()) {
??? ??? ??? // Check the class member opening part.
??? ??? ??? if (!ConfigurationConstants.OPEN_KEYWORD.equals(nextWord)) {
??? ??? ??? ??? throw new ParseException("Expecting opening '"
??? ??? ??? ??? ??? ??? + ConfigurationConstants.OPEN_KEYWORD + "' at "
??? ??? ??? ??? ??? ??? + reader.locationDescription());
??? ??? ??? }
??? ??? ??? // Parse all class members.
??? ??? ??? while (true) {
??? ??? ??? ??? readNextWord("class member description" + " or closing '"
??? ??? ??? ??? ??? ??? + ConfigurationConstants.CLOSE_KEYWORD + "'", false,
??? ??? ??? ??? ??? ??? true);
??? ??? ??? ??? if (nextWord.equals(ConfigurationConstants.CLOSE_KEYWORD)) {
??? ??? ??? ??? ??? // The closing brace. Stop parsing the class members.
??? ??? ??? ??? ??? readNextWord();
??? ??? ??? ??? ??? break;
??? ??? ??? ??? }
??? ??? ??? ??? parseMemberSpecificationArguments(externalClassName,
??? ??? ??? ??? ??? ??? classSpecification);
??? ??? ??? }
??? ??? }
這個匹配必須是非結束符號也就是不是 - 或者@
這就說明proguard的語法支持
-keep public class ...或者
-keep public class ...{...}
我們來看下第二種它是怎么做的:
if (nextWord.equals(ConfigurationConstants.CLOSE_KEYWORD)) {
??? ??? ??? ??? ??? // The closing brace. Stop parsing the class members.
??? ??? ??? ??? ??? readNextWord();
??? ??? ??? ??? ??? break;
?}
當讀入的字符不為}的時候將繼續讀入
解析成員通過方法parseMemberSpecificationArguments 來生成
這個方法跟類分析程序非常相似多了一些參數的條件,比如static native transient volatile final 這類用來形容方法或者變量的屬性當然在這之前有過注解驗證,也就是說支持:
{
@anno
static test
}
這種寫法
接下來會通過
if (??? ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord)
??? ??? ??? ??? || ConfigurationConstants.ANY_FIELD_KEYWORD.equals(nextWord)
??? ??? ??? ??? || ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord))
三個方法來對三種通配符號做處理,這三種通配符號分別是:*,<fields>,<methods>
匹配完成之后會生成叫做MemberSpecification 的對象來倒入到class的配置中
*可以看作是后面兩個東西的集合,所以在proguard處理的時候會同時調用
classSpecification.addField(new MemberSpecification(
??? ??? ??? ??? ??? ??? requiredSetMemberAccessFlags,
??? ??? ??? ??? ??? ??? requiredUnsetMemberAccessFlags, annotationType, null,
??? ??? ??? ??? ??? ??? null));
??? ??? ??? ??? classSpecification.addMethod(new MemberSpecification(
??? ??? ??? ??? ??? ??? requiredSetMemberAccessFlags,
??? ??? ??? ??? ??? ??? requiredUnsetMemberAccessFlags, annotationType, null,
??? ??? ??? ??? ??? ??? null));
這兩個方法。
如果你不采用通配符號的方式來寫的話,也就是說你默認會給出一個精確表達式,也有可能是一個模式匹配的表達式。我們來看一下Proguard對它的處理流程:
ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(name)
proguard會先檢測是否是攜帶參數:
這里它對構造器方法和一些錯誤的可能做的屏蔽處理:
if (!(type.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT)
??? ??? ??? ??? ??? ??? || type.equals(externalClassName) || type
??? ??? ??? ??? ??? ??? ??? .equals(ClassUtil
??? ??? ??? ??? ??? ??? ??? ??? ??? .externalShortClassName(externalClassName)))) {
??? ??? ??? ??? ??? throw new ParseException("Expecting type and name "
??? ??? ??? ??? ??? ??? ??? + "instead of just '" + type + "' before "
??? ??? ??? ??? ??? ??? ??? + reader.locationDescription());
??? ??? ??? ??? }
?原理很簡單,由于構造器是沒有返回值的,所以你之前期望得到的返回類型應該就是構造器的方法名<init>
if (ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) {
??? ??? ??? ??? // It's a field.
??? ??? ??? ??? checkFieldAccessFlags(requiredSetMemberAccessFlags,
??? ??? ??? ??? ??? ??? requiredUnsetMemberAccessFlags);
??? ??? ??? ??? // We already have a field descriptor.
??? ??? ??? ??? String descriptor = ClassUtil.internalType(type);
??? ??? ??? ??? // Add the field.
??? ??? ??? ??? classSpecification.addField(new MemberSpecification(
??? ??? ??? ??? ??? ??? requiredSetMemberAccessFlags,
??? ??? ??? ??? ??? ??? requiredUnsetMemberAccessFlags, annotationType, name,
??? ??? ??? ??? ??? ??? descriptor));
??? ??? ??? } else if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD
??? ??? ??? ??? ??? .equals(nextWord)) {
??? ??? ??? ??? // It's a method.
??? ??? ??? ??? checkMethodAccessFlags(requiredSetMemberAccessFlags,
??? ??? ??? ??? ??? ??? requiredUnsetMemberAccessFlags);
??? ??? ??? ??? // Parse the method arguments.
??? ??? ??? ??? String descriptor = ClassUtil.internalMethodDescriptor(
??? ??? ??? ??? ??? ??? type,
??? ??? ??? ??? ??? ??? parseCommaSeparatedList("argument", true, true, true,
??? ??? ??? ??? ??? ??? ??? ??? false, true, false, false, false, null));
??? ??? ??? ??? if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD
??? ??? ??? ??? ??? ??? .equals(nextWord)) {
??? ??? ??? ??? ??? throw new ParseException("Expecting separating '"
??? ??? ??? ??? ??? ??? ??? + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD
??? ??? ??? ??? ??? ??? ??? + "' or closing '"
??? ??? ??? ??? ??? ??? ??? + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD
??? ??? ??? ??? ??? ??? ??? + "' before " + reader.locationDescription());
??? ??? ??? ??? }
??? ??? ??? ??? // Read the separator after the closing parenthesis.
??? ??? ??? ??? readNextWord("separator '"
??? ??? ??? ??? ??? ??? + ConfigurationConstants.SEPARATOR_KEYWORD + "'");
??? ??? ??? ??? if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) {
??? ??? ??? ??? ??? throw new ParseException("Expecting separator '"
??? ??? ??? ??? ??? ??? ??? + ConfigurationConstants.SEPARATOR_KEYWORD
??? ??? ??? ??? ??? ??? ??? + "' before " + reader.locationDescription());
??? ??? ??? ??? }
??? ??? ??? ??? // Add the method.
??? ??? ??? ??? classSpecification.addMethod(new MemberSpecification(
??? ??? ??? ??? ??? ??? requiredSetMemberAccessFlags,
??? ??? ??? ??? ??? ??? requiredUnsetMemberAccessFlags, annotationType, name,
??? ??? ??? ??? ??? ??? descriptor));
??? ??? ??? } else {
??? ??? ??? ??? // It doesn't look like a field or a method.
??? ??? ??? ??? throw new ParseException("Expecting opening '"
??? ??? ??? ??? ??? ??? + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD
??? ??? ??? ??? ??? ??? + "' or separator '"
??? ??? ??? ??? ??? ??? + ConfigurationConstants.SEPARATOR_KEYWORD
??? ??? ??? ??? ??? ??? + "' before " + reader.locationDescription());
??? ??? ??? }
這段代碼也非常好理解,對于只有名字然后直接跟分號的話,它認為是成員變量參數,如果是(則是方法,對于方法來說最重要的就是方法的簽名,我們來關注一下方法是如何獲得簽名的.
方法簽名是通過String descriptor = ClassUtil.internalMethodDescriptor(
??? ??? ??? ??? ??? ??? type,
??? ??? ??? ??? ??? ??? parseCommaSeparatedList("argument", true, true, true,
??? ??? ??? ??? ??? ??? ??? ??? false, true, false, false, false, null));
來獲得,其中type就是你的返回值,我們不說詳細過程,只注重一些細節的結果parseCommaSeparatedList的參數列表最后得到相應的方法簽名例如:
(ILcom/test/Base;)V
第一個I代表的是INT ,如果你想關注這些,不妨看一下jvm匯編一類的知識,其實c++的方法簽名方式也大同小異。所以如果你之前從事過這方面的話,應該是不會陌生的。
轉載于:https://www.cnblogs.com/feizimo/p/3523832.html
總結
以上是生活随笔為你收集整理的Proguard源码分析(五) ConfigurationParser.keep参数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 架构设计(ASP.NET MVC+Kno
- 下一篇: 抗美援朝老兵家属考文职加分吗