混淆的藝術-(蒼井空變鳳姐)Proguard源碼分析(三)Proguard配置解析~上

ConfigurationParser會將我們的 配置文件映射成爲Configuration對象。這個功能的主要處理邏輯放在ConfigurationParser的parser中:         

if (ConfigurationConstants.AT_DIRECTIVE.startsWith(nextWord)
|| ConfigurationConstants.INCLUDE_DIRECTIVE
.startsWith(nextWord))
configuration.lastModified = parseIncludeArgument(configuration.lastModified);

在調用parseIncludeArgument的時候會往wordReader裏面設置一個內置對象:
reader.includeWordReader(new FileWordReader(file));
這樣WordReader就成爲了FileWordReader的一個裝飾器。這樣我們就實現了多層嵌套代碼的功能
而這個功能所使用的標籤就是
ConfigurationConstants.AT_DIRECTIVE  -> @
ConfigurationConstants.INCLUDE_DIRECTIVE -> -include

if (ConfigurationConstants.BASE_DIRECTORY_DIRECTIVE
.startsWith(nextWord))
parseBaseDirectoryArgument();

這個條件的目的是爲了設置base路徑,它使用的標籤是:
ConfigurationConstants.BASE_DIRECTORY_DIRECTIVE->-basedirectory
當我們系統的來看下這個解析的時候,我們會發現,不論你採用那種keep方式的配置都會調用到parseKeepClassSpecificationArguments 這個方法。

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);

我們先不深究這個方法的具體的參數含義,而是一步步來探索這個重要的方法的參數解析實現。

private List parseKeepClassSpecificationArguments(
List keepClassSpecifications, boolean markClasses/*true*/,
boolean markConditionally/*false*/, boolean allowShrinking/*false*/)
throws ParseException, IOException {
// Create a new List if necessary.
if (keepClassSpecifications == null) {
keepClassSpecifications = new ArrayList();
}
boolean allowOptimization = false;
boolean allowObfuscation = false;
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());
}
}

// Read the class configuration.
ClassSpecification classSpecification = parseClassSpecificationArguments();

// Create and add the keep configuration.
keepClassSpecifications.add(new KeepClassSpecification(markClasses,
markConditionally, allowShrinking, allowOptimization,
allowObfuscation, classSpecification));
return keepClassSpecifications;
}

紅色部分我們可以加在keep後面作爲輔助的混淆標誌,這樣我們可以總結出proguard配置文件寫法的基本規律,一定要以keep*打頭,但是allowshrinking,allowoptimization,allowobfuscation 這些屬於跟keep一樣的指令,但是一定要放在keep指令後面通過","分割。這些參數將直接影響配置。我們接着來看綠色的部分,這裏我猜測KeepClassSpecification是一個裝飾器,我們來看它的繼承關係,發現它真的繼承於ClassSpecification。那麼爲何要這麼做呢?~其實我們在定義數據結構的時候,往往定義的是原始數據,一些非原始數據的東西我們可以通過裝飾或者代理來包裝。這樣不會影響到原始的數據。如果我們來反過來思考的話,其實非常好理解,allowshrinking,allowoptimization,allowobfuscation  這些是你的指令,而你的數據是定義在ClassSpecification。我們按照流程下來看的話,其實閱讀這些代碼並不費勁,可見我們在包裝完這種類的keep之後我們需要開始解析類數據。這種流程是面向過程的一種過程。這樣不知道各位是不是更好理解.其實我爲何願意看Proguard的源碼呢?因爲它的代碼結構很簡單,每個函數很少有超過200行代碼的。雖然一個類的代碼挺長,但是代碼結構都是一種模式.看起來還是頗爲愜意的一件事。(你可以試試Android的AMS源碼~淚奔)

public ClassSpecification parseClassSpecificationArguments()
throws ParseException, IOException {
// Clear the annotation type.
String annotationType = null;

// Clear the class access modifiers.
int requiredSetClassAccessFlags = 0;
int requiredUnsetClassAccessFlags = 0;

// Parse the class annotations and access modifiers until the class
// keyword.
while (!ConfigurationConstants.CLASS_KEYWORD.equals(nextWord)) {
// Strip the negating sign, if any.
boolean negated = nextWord
.startsWith(ConfigurationConstants.NEGATOR_KEYWORD);

String strippedWord = negated ? nextWord.substring(1) : nextWord;

// Parse the class access modifiers.
int accessFlag = strippedWord
.equals(ClassConstants.EXTERNAL_ACC_PUBLIC) ? ClassConstants.INTERNAL_ACC_PUBLIC
: strippedWord.equals(ClassConstants.EXTERNAL_ACC_FINAL) ? ClassConstants.INTERNAL_ACC_FINAL
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_INTERFACE) ? ClassConstants.INTERNAL_ACC_INTERFACE
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT) ? ClassConstants.INTERNAL_ACC_ABSTRACT
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_SYNTHETIC) ? ClassConstants.INTERNAL_ACC_SYNTHETIC
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_ANNOTATION) ? ClassConstants.INTERNAL_ACC_ANNOTATTION
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_ENUM) ? ClassConstants.INTERNAL_ACC_ENUM
: unknownAccessFlag();

// Is it an annotation modifier?
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.
}

if (!negated) {
requiredSetClassAccessFlags |= accessFlag;
} else {
requiredUnsetClassAccessFlags |= accessFlag;
}

if ((requiredSetClassAccessFlags & requiredUnsetClassAccessFlags) != 0) {
throw new ParseException(
"Conflicting class access modifiers for '"
+ strippedWord + "' before "
+ reader.locationDescription());
}

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;
}

// Should we read the next word?
if (accessFlag != ClassConstants.INTERNAL_ACC_ANNOTATTION) {
readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD
+ "', '" + ClassConstants.EXTERNAL_ACC_INTERFACE
+ "', or '" + ClassConstants.EXTERNAL_ACC_ENUM + "'",
false, true);
}
}

// Parse the class name part.
String externalClassName = ListUtil.commaSeparatedString(
parseCommaSeparatedList("class name or interface name", true,
false, false, false, true, false, false, false, null),
false);

// For backward compatibility, allow a single "*" wildcard to match any
// class.
String className = ConfigurationConstants.ANY_CLASS_KEYWORD
.equals(externalClassName) ? null : ClassUtil
.internalClassName(externalClassName);

// Clear the annotation type and the class name of the extends part.
String extendsAnnotationType = null;
String extendsClassName = null;

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.
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);
}
}

// !new class meta
ClassSpecification classSpecification = new ClassSpecification(
lastComments, requiredSetClassAccessFlags,
requiredUnsetClassAccessFlags, annotationType, className,
extendsAnnotationType, extendsClassName);

// Now add any class members to this class specification.
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);
}
}

return classSpecification;
}

這個代碼的結構就是爲了解決類結構的問題。我們知道按照java的規範,類定義的前面可以有一定數量的修飾語,可能用來修飾作用域,可能用來修飾類的類型。這段代碼自然對這些做了兼容。在class文件中,通過位標誌來解釋類中的各種參數。Proguard沿用了這種做法。紅色部分就是爲了生成這些類的元數據.只不過例外的事情是可以在這些東西前面增加“!”標籤,代表“非”的操作。這裏我們說明一下,Proguard對於一些錯誤是保持沉默的,或者說是支持一些無關的寫法。比如對於註解你可以寫成@com.test.anno 這種具體的類,或者是@class @interface @anum都是可以的,而對於@class @interface @anum 的處理邏輯都是一樣的,簡單一點就是跳過(紫色代碼段)。我們看到中間部分的綠色部分代碼,這個時候 keep ![public,private,protected][class interface @anno anum] name就解析完成。 實際上這個已經是一個完整的keep配置了(類似-keep  class com.test.Class2);這裏提一下,每個配置項的結束是通過@符號或者-符號作爲標誌的。我們可以在classname之後再增加implement和extends這類的關鍵字,Proguard還是一如既往的不做區分。好了到了這一部,我們足夠數據定義一個完整的類的元數據。接下來就是藍色部分的參數解析;拋開一些無關緊要的代碼我們直接看重點:

private void parseMemberSpecificationArguments(String externalClassName,
ClassSpecification classSpecification) throws ParseException,
IOException {
// Clear the annotation name.
String annotationType = null;

// Parse the class member access modifiers, if any.
int requiredSetMemberAccessFlags = 0;
int requiredUnsetMemberAccessFlags = 0;

while (!configurationEnd(true)) {
// Parse the annotation type, if any.
if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord)) {
annotationType = ListUtil.commaSeparatedString(
parseCommaSeparatedList("annotation type", true, false,
false, false, true, false, false, true, null),
false);
continue;
}

String strippedWord = nextWord.startsWith("!") ? nextWord
.substring(1) : nextWord;

// Parse the class member access modifiers.
int accessFlag = strippedWord
.equals(ClassConstants.EXTERNAL_ACC_PUBLIC) ? ClassConstants.INTERNAL_ACC_PUBLIC
: strippedWord.equals(ClassConstants.EXTERNAL_ACC_PRIVATE) ? ClassConstants.INTERNAL_ACC_PRIVATE
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_PROTECTED) ? ClassConstants.INTERNAL_ACC_PROTECTED
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_STATIC) ? ClassConstants.INTERNAL_ACC_STATIC
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_FINAL) ? ClassConstants.INTERNAL_ACC_FINAL
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_SYNCHRONIZED) ? ClassConstants.INTERNAL_ACC_SYNCHRONIZED
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_VOLATILE) ? ClassConstants.INTERNAL_ACC_VOLATILE
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_TRANSIENT) ? ClassConstants.INTERNAL_ACC_TRANSIENT
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_BRIDGE) ? ClassConstants.INTERNAL_ACC_BRIDGE
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_VARARGS) ? ClassConstants.INTERNAL_ACC_VARARGS
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_NATIVE) ? ClassConstants.INTERNAL_ACC_NATIVE
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT) ? ClassConstants.INTERNAL_ACC_ABSTRACT
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_STRICT) ? ClassConstants.INTERNAL_ACC_STRICT
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_SYNTHETIC) ? ClassConstants.INTERNAL_ACC_SYNTHETIC
: 0;
if (accessFlag == 0) {
// Not a class member access modifier. Stop parsing them.
break;
}

if (strippedWord.equals(nextWord)) {
requiredSetMemberAccessFlags |= accessFlag;
} else {
requiredUnsetMemberAccessFlags |= accessFlag;
}

// Make sure the user doesn't try to set and unset the same
// access flags simultaneously.
if ((requiredSetMemberAccessFlags & requiredUnsetMemberAccessFlags) != 0) {
throw new ParseException(
"Conflicting class member access modifiers for "
+ reader.locationDescription());
}

readNextWord("class member description");
}

// Parse the class member type and name part.

// Did we get a special wildcard?
if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord)
|| ConfigurationConstants.ANY_FIELD_KEYWORD.equals(nextWord)
|| ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord)) {
// Act according to the type of wildcard..
if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD
.equals(nextWord)) {
checkFieldAccessFlags(requiredSetMemberAccessFlags,
requiredUnsetMemberAccessFlags);
checkMethodAccessFlags(requiredSetMemberAccessFlags,
requiredUnsetMemberAccessFlags);

classSpecification.addField(new MemberSpecification(
requiredSetMemberAccessFlags,
requiredUnsetMemberAccessFlags, annotationType, null,
null));
classSpecification.addMethod(new MemberSpecification(
requiredSetMemberAccessFlags,
requiredUnsetMemberAccessFlags, annotationType, null,
null));
} else if (ConfigurationConstants.ANY_FIELD_KEYWORD
.equals(nextWord)) {
checkFieldAccessFlags(requiredSetMemberAccessFlags,
requiredUnsetMemberAccessFlags);

classSpecification.addField(new MemberSpecification(
requiredSetMemberAccessFlags,
requiredUnsetMemberAccessFlags, annotationType, null,
null));
} else if (ConfigurationConstants.ANY_METHOD_KEYWORD
.equals(nextWord)) {
checkMethodAccessFlags(requiredSetMemberAccessFlags,
requiredUnsetMemberAccessFlags);

classSpecification.addMethod(new MemberSpecification(
requiredSetMemberAccessFlags,
requiredUnsetMemberAccessFlags, annotationType, null,
null));
}

// We still have to read the closing separator.
readNextWord("separator '"
+ ConfigurationConstants.SEPARATOR_KEYWORD + "'");

if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) {
throw new ParseException("Expecting separator '"
+ ConfigurationConstants.SEPARATOR_KEYWORD
+ "' before " + reader.locationDescription());
}
} else {
// Make sure we have a proper type.
checkJavaIdentifier("java type");
String type = nextWord;

readNextWord("class member name");
String name = nextWord;

// Did we get just one word before the opening parenthesis?
if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(name)) {
// This must be a constructor then.
// Make sure the type is a proper constructor name.
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());
}

// Assign the fixed constructor type and name.
type = ClassConstants.EXTERNAL_TYPE_VOID;
name = ClassConstants.INTERNAL_METHOD_NAME_INIT;
} else {
// It's not a constructor.
// Make sure we have a proper name.
checkJavaIdentifier("class member name");

// Read the opening parenthesis or the separating
// semi-colon.
readNextWord("opening '"
+ ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD
+ "' or separator '"
+ ConfigurationConstants.SEPARATOR_KEYWORD + "'");
}

// Are we looking at a field, a method, or something else?
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());
}
}
}

我們發現解析屬性和方法的代碼跟解析類元數據的方法非常相似,其實在java中定義類成員和定義類的方式本身就很相似。但這裏對Any的定義增加了兩種:

public static final String ANY_CLASS_MEMBER_KEYWORD = "*";
public static final String ANY_FIELD_KEYWORD = "<fields>";
public static final String ANY_METHOD_KEYWORD = "<methods>";


可以看到實際上就是比類定義的數據結構多了屬性的任意匹配和方法匹配,其實還多了屬性的修飾符。比如private final 註解這一類,除了比較傳統的public static private protected 這種的關鍵字說明意外,還增加了“!”用來表示非的邏輯關係。這裏要特別說明的一點是由於作用域和static說明是採用位標誌的方式存在,因此可以不在意順序但是!一定要放在條件的開頭部分。而且在屬性定義中,也支持了分號爲結尾的定義方式。紅色代碼部分是不採用通配符的方式來定義方法或者屬性說明,我們看到,對於方法而言,Proguard將爲其生成一個方法的描述符號,用來唯一標識該方法。

待續。。。

                                                                                                                                                      --非子墨

發佈了30 篇原創文章 · 獲贊 4 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章