现在的位置: 首页 > 综合 > 正文

混淆的艺术-(苍井空变凤姐)Proguard源码分析(二)Proguard参数解析

2017年12月06日 ⁄ 综合 ⁄ 共 4616字 ⁄ 字号 评论关闭

Proguard作为一个java的程序,它的入口在ProGuard.main()中

这章节我们讲Proguard的参数解析。实际上Proguard的处理无非分两步,解析参数和处理逻辑。解析参数这一步的主要类是:

 ConfigurationParser.java

它的解析结果将放置在:

  Configuration.java类中

我们打开Configuration 来看,发现Configuration类如其名,就是记录配置的一个Pojo类,虽然我这么说可能不合理,但是我们确实可以把它当成一个非常简单的类来看。既然我们这一章节的标题叫做参数解析,我们要重点分析的对象自然是:

ConfigurationParser

参数数据源->ConfigurationParser->Configuration对象,在分析ConfigurationParser之前我们先打开

ConfigurationConstants.java这个类,这个类记录了Proguard里面定义的关键字。有些常量用来模式匹配,有些常量是用来提供混淆范围,还有一些是用来配置Proguard的环境参数。这里我们只讲模式匹配。也就是下列这些参数:

public static final char OPEN_SYSTEM_PROPERTY = '<';

public static final char CLOSE_SYSTEM_PROPERTY = '>';

public static final String ANNOTATION_KEYWORD = "@";

public static final String NEGATOR_KEYWORD = "!";

public static final String CLASS_KEYWORD = "class";

public static final String ANY_CLASS_KEYWORD = "*";

public static final String ANY_TYPE_KEYWORD = "***";

public static final String IMPLEMENTS_KEYWORD = "implements";

public static final String EXTENDS_KEYWORD = "extends";

public static final String OPEN_KEYWORD = "{";

public static final String ANY_CLASS_MEMBER_KEYWORD = "*";

public static final String ANY_FIELD_KEYWORD = "<fields>";

public static final String ANY_METHOD_KEYWORD = "<methods>";

public static final String OPEN_ARGUMENTS_KEYWORD = "(";

public static final String ARGUMENT_SEPARATOR_KEYWORD = ",";

public static final String ANY_ARGUMENTS_KEYWORD = "...";

public static final String CLOSE_ARGUMENTS_KEYWORD = ")";

public static final String SEPARATOR_KEYWORD = ";";

public static final String CLOSE_KEYWORD = "}";


Proguard中的取词操作放在WordReader这个类中,为何我要顺带说一下这个类呢?是因为这个类被复用的功能极强如果你想要写一个词法解析器的话,不妨直接考虑这个东西。WordReader 本身是一个抽象类他的实现主要有

LineWordReader 和 ArgumentWordReader ,其中Proguard默认使用的是ArgumentWordReader.但是你如果使用的文件方式来配置proguard的话,那么它使用的是LineWordReader 这个类。我们就使用LineWordReader 这个类。当然我在我的工程下面建了一个目录resources和outs用来保存我的资源配置和输出文件。

我们看到WordReader这个类抽象的非常好,它抽象出了一个模板方法nextLine,用于抽象取行的方式(图1)。我们来看一下它取词的过程,在nextWord(boolean isFileName) 方法中:


                                                                    [图1]

我们将nextWord函数的源码分成几个部分:

第一:预先编译(过滤掉空白符号和注释)



while (currentLine == null || currentIndex == currentLineLength)

{

currentLine = nextLine();

lineNumber++;

if (currentLine == null)

{

return null;

}

currentLineLength = currentLine.length();

// Skip any leading whitespace.

currentIndex = 0;

while (currentIndex < currentLineLength &&

Character.isWhitespace(currentLine.charAt(currentIndex)))

{

currentIndex++;

}

// Remember any leading comments.
if (currentIndex < currentLineLength &&
isComment(currentLine.charAt(currentIndex)))
{
// Remember the comments.
String comment = currentLine.substring(currentIndex + 1);
currentComments = currentComments == null ?
comment :
currentComments + '\n' + comment;

// Skip the comments.
currentIndex = currentLineLength;

}

}

我们可以看到代码的红色那一部分,实际上对于过滤空格后的第一个字符,如果是"#"的话,那么它直接会将索引currentIndex 设置为字符串长度currentLineLength 那么也就过滤掉了注释的这一行,而如果到达结尾了~它将会再取得的下一行。然后重新解析,而你所得到的这些注释会拼成一个comment的长字符,用\n分割然后放在currentComments 变量中。接下来就是第二步:

取词:

int startIndex = currentIndex;

int endIndex;

char startChar = currentLine.charAt(startIndex);

if (isQuote(startChar))
{
// The next word is starting with a quote character.
// Skip the opening quote.
startIndex++;

// The next word is a quoted character string.
// Find the closing quote.
do
{
currentIndex++;

if (currentIndex == currentLineLength)
{
currentWord = currentLine.substring(startIndex-1, currentIndex);
throw new IOException("Missing closing quote for "+locationDescription());
}
}
while (currentLine.charAt(currentIndex) != startChar);

endIndex = currentIndex++;
}

else if (isFileName &&
!isOption(startChar))
{
// The next word is a (possibly optional) file name.
// Find the end of the line, the first path separator, the first
// option, or the first comment.
while (currentIndex < currentLineLength)
{
char currentCharacter = currentLine.charAt(currentIndex);
if (isFileDelimiter(currentCharacter) ||
((isOption(currentCharacter) ||
isComment(currentCharacter)) &&
Character.isWhitespace(currentLine.charAt(currentIndex-1)))) {
break;
}

currentIndex++;
}

endIndex = currentIndex;

// Trim any trailing whitespace.

while (endIndex > startIndex &&

Character.isWhitespace(currentLine.charAt(endIndex-1)))

{

endIndex--;

}

}
else if (isDelimiter(startChar))
{
// The next word is a single delimiting character.
endIndex = ++currentIndex;
}
else
{
// The next word is a simple character string.
// Find the end of the line, the first delimiter, or the first
// white space.
while (currentIndex < currentLineLength)
{
char currentCharacter = currentLine.charAt(currentIndex);
if (isDelimiter(currentCharacter) ||
Character.isWhitespace(currentCharacter) ||
isComment(currentCharacter)) {
break;
}

currentIndex++;
}

endIndex = currentIndex;
}

// Remember and return the parsed word.
currentWord = currentLine.substring(startIndex, endIndex);

return currentWord;

红色区域的代码代表引号这种成对出现的符号内的全部字母都作为nextword的一部分,当然包括空格。蓝色部分的代码是为了解决在文件读入的时候的文字匹配。我们以后再提。金黄色部分可以解决对于类似@xxx或者(XXX)这类,不知道大家是否也发现了~其实写成(xxx也是可以的.而对于以文件方式读入的参数将会携带@标识符作为函数的返回值。

                                                                                                                                                     
--非子墨
  


抱歉!评论已关闭.