簡介:
先看一張Javc編譯成class文件的時候流程圖
至于什么是token流,語法樹相關可以參考我之前的兩篇帖子。
http://www.lxweimin.com/p/7a9476c65672
http://www.lxweimin.com/p/49ca10345080
如何下載JavaC源碼?
直接使用openJdk進行下載,下載地址為 https://hg.openjdk.java.net/jdk8/jdk8/langtools/
也可以直接在我的在我的項目庫內部進行下載,地址見下文。
Javac主要有四個模塊,分別是詞法分析器,語法分析器,語義分析器和代碼生成器。
JavaC JavaParser
用途:
把Java源碼轉換成 JavaParser定義的Statement對象,
也就是將java源碼解析成一顆語法樹,然后基于這棵樹對java代碼進行分析和修改的工具。
在javac 編譯時候使用到
重點類介紹
語法樹簡介:
在學習之前需要知道什么是語法樹,很簡單的代碼如下,在解析的時候主要分為兩步
“1 + 2 * 3”
第一步: +, 1,(2 * 3)
第二步: +, 1,(*, 2, 3)
com.sun.tools.java.tree.JCTree
用于表示最終的語法樹,結構,里面有很多內部類。
比如com.sun.tools.java.tree.JCTree$JCCompilationUnit,在AST中,你可以把這個類看成是AST的根節點
插入式注解器簡介
Jdk1.5后引入注解功能,注解是一種應用字節碼 屬性中類的元數據進行操作的一種編程機制。
處理表形成后 會自動檢測是否有注解器需要執行,若有則執行注解處理器。注解處理器實現了在可插入式的編譯期改變編譯過程的功能。
其本質就是 再次修改 處理表中的語法樹。 一旦語法樹被修改,則將再次進行 詞法,語法分析并填充符號表的過程,直到所有插件對語法樹進行修改完為止。
初始化過程入口是 initPorcessAnnotations()
執行過程入口是 processAnnotations()
該方法判斷是否有新的注解處理器需要執行,若有的,則通過com.sun.tools.方法生成新的JavaCompiler對象對編譯的后續步驟進行處理
JavaC 詞法分析
詞法分析主要是將代碼轉換成token
詞法分析過程主要是在的JavacParser.parseCompilationUnit()中完成的
入口是parseFiles()
詞法分析實現類是com.sun.tools.javac.parser.Scanner類
語法分析實現類是com.sun.tools.javac.parser.Parser類
出口由com.sun.tools.javac.tree.JCTree類表示
重點介紹下面幾個類,是構成詞法分析的關鍵
先介紹詞法分析“大管家”
com.sun.tools.javac.parser.ParserFactory
存放著很多的解析器比如token,源文件,名字注解之類的。
通過靜態方法單例進行初始化
//獲取實類的單例方法
public static ParserFactory instance(Context context) {
ParserFactory instance = context.get(parserFactoryKey);
if (instance == null) {
instance = new ParserFactory(context);
}
return instance;
}
//創建的時候在構造方法內部,將詞法分析需要的一些類進行初始化
//在編譯的時候全部的解析器包括,生成器,各種工具類,都是全局唯一的單例模式
protected ParserFactory(Context context) {
super();
context.put(parserFactoryKey, this);
this.F = TreeMaker.instance(context);
this.docTreeMaker = DocTreeMaker.instance(context);
this.log = Log.instance(context);
this.names = Names.instance(context);
this.tokens = Tokens.instance(context);
this.source = Source.instance(context);
this.options = Options.instance(context);
this.scannerFactory = ScannerFactory.instance(context);
this.locale = context.get(Locale.class);
}
com.sun.tools.javac.parser.JavacParser
規定哪些詞符合Java語言規范,具體讀取和歸類不同詞法的操作由scanner完成
/**
* Skip forward until a suitable stop token is found.
* 核心方法,判斷是否是我們需要的token
*/
private void skip(boolean stopAtImport, boolean stopAtMemberDecl, boolean stopAtIdentifier, boolean stopAtStatement) {
com.sun.tools.javac.parser.Scanner
負責逐個讀取源代碼的單個字符,然后解析符合Java語言規范的Token序列,調用一次nextToken()都構造一個Token
內部有個List 儲存讀取到的token信息
/**
* Buffer of saved tokens (used during lookahead)
* token的一個緩沖區
*/
private List<Token> savedTokens = new ArrayList<Token>();
com.sun.tools.javac.parser.Tokens$TokenKind
里面包含了所有token的類型,譬如BOOLEAN,BREAK,BYTE,CASE。用的是枚舉的方式進行保存
類似如下:
public enum TokenKind implements Formattable, Filter<TokenKind> {
EOF(),
ERROR(),
IDENTIFIER(Tag.NAMED),
ABSTRACT("abstract"),
ASSERT("assert", Tag.NAMED),
BOOLEAN("boolean", Tag.NAMED),
BREAK("break"),
BYTE("byte", Tag.NAMED),
CASE("case"),
CATCH("catch"),
CHAR("char", Tag.NAMED),
CLASS("class"),
CONST("const"),
...
com.sun.tools.javac.parser.Tokens
存放全部的定義token
/**
* The names of all tokens.
* 存放全部token的數組
*/
private Name[] tokenName = new Name[TokenKind.values().length];
com.sun.tools.javac.util.Names
用來存儲和表示解析后的詞法,每個字符集合都會是一個Name對象,所有的對象都存儲在Name.Table這個內部類中。
com.sun.tools.javac.parser.KeyWords
負責將字符集合對應到token集合中,如,package zxy.demo.com; Token.PACKAGE = package, Token.IDENTIFIER = zxy.demo.com,(這部分又分為讀取第一個token,為zxy,判斷下一個token是否為“.”,是的話接著讀取下一個Token.IDENTIFIER類型的token,反復直至下一個token不是”.”,也就是說下一個不是Token.IDENIFIER類型的token,Token.SEMI = ;即這個TIDENTIFIER類型的token的Name讀完),KeyWords類負責此任務。
package compile;
public class Cifa {
int a;
int c = a + 1;
}
轉換的token流如下
JavaC 語法分析
將token流轉換成句子
com.sun.tools.javac.tree.TreeMaker
所有語法節點都是由它生成的,根據Name對象構建一個語法節點
com.sun.tools.javac.tree.JCTree$JCIf
所有的節點都會繼承jctree和實現**tree,譬如 JCIf extends JCTree.JCStatement implements IfTree
com.sun.tools.javac.tree.JCTree
重點介紹的三個屬性
- Tree tag:每個語法節點都會以整數的形式表示,下一個節點在上一個節點上加1;
- pos:也是一個整數,它存儲的是這個語法節點在源代碼中的起始位置,一個文件的位置是0,而-1表示不存在
- type:它代表的是這個節點是什么java類型,如int,float,還是string等
例子:
package compile;
public class Yufa {
int a;
private int c = a + 1;
//getter
public int getC() {
return c;
}
//setter
public void setC(int c) {
this.c = c;
}
}
說明:
- 每一個包package下的所有類都會放在一個JCCompilationUnit節點下,在該節點下包含:package語法樹(作為pid)、各個類的語法樹
- 每一個從JCClassDecl發出的分支都是一個完整的代碼塊,上述是四個分支,對應我們代碼中的兩行屬性操作語句和兩個方法塊代碼塊,這樣其實就完成了語法分析器的作用:將一個個Token單詞組成了一句句話(或者說成一句句代碼塊)
- 在上述的語法樹部分,對于屬性操作部分是完整的,但是對于兩個方法塊,省略了一些語法節點,例如:方法修飾符public、方法返回類型、方法參數。
JavaC語義分析器
流程:
添加默認的無參構造器(在沒有指定任何有參構造器的情況下),把引用其他類的方法或者變量,抑或是繼承實現來的變量和方法等輸入到類自身的符號表中
處理注解
-
標注:檢查語義合法性、進行邏輯判斷
- 檢查語法樹中的變量類型是否匹配(eg.String s = 1 + 2;//這樣"="兩端的類型就不匹配)
- 檢查變量、方法或者類的訪問是否合法(eg.一個類無法訪問另一個類的private方法)
- 變量在使用前是否已經聲明、是否初始化
- 常量折疊(eg.代碼中:String s = "hello" + "world",語義分析后String s = "helloworld")
- 推導泛型方法的參數類型
-
數據流分析
- 變量的確定性賦值(eg.有返回值的方法必須確定有返回值)
- final變量只能賦一次值,在編譯的時候再賦值的話會報錯
- 所有的檢查型異常是否拋出或捕獲
- 所有的語句都要被執行到(return后邊的語句就不會被執行到,除了finally塊兒)
-
進一步語義分析
- 去掉永假代碼(eg.if(false))
- 變量自動轉換(eg.int和Integer)自動裝箱拆箱
- 去掉語法糖(eg.foreach轉化為for循環,assert轉化為if,內部類解析成一個與外部類相關聯的外部類)
最后,將經過上述處理的語法樹轉化為最后的注解語法樹
入口是attribute()
實現類是com.sun.tools.javac.comp.Attr類和com.sun.tools.javac.comp.Check類
源碼關鍵:
com.sun.tools.javac.comp.Enter
將java類中的符號輸入到符號表中,主要是兩個步驟:
- 將所有類中出現的符號輸入到類自身的符號表中,所有類符號、類的參數類型符號(泛型參數類型)、超類符號和繼承的接口類型符號等都存儲到一個未處理的列表中。
- 將這個未處理的列表中所有的類都解析到各自的類符號列表中,這個操作是在MemberEnter.complete()中完成(默認構造器也是在這里完成的)。
com.sun.tools.javac.processing.JavacProcessingEnvironment
處理注解
com.sun.tools.javac.comp.Attr
檢查語義的合理性并進行邏輯判斷,類型是否匹配,是否初始化,泛型是否可推導,字符串常量合并
com.sun.tools.javac.comp.Check
協助attr,變量類型是否正確
com.sun.tools.javac.comp.Resolve
協助attr,變量方法類的訪問是否合法,是否是靜態變量
com.sun.tools.javac.comp.ConstFold
協助attr,常量折疊
com.sun.tools.javac.comp.Infer
協助attr,推導泛型
com.sun.tools.javac.comp.Flow
數據流分析和替換等價源代碼的分析(即上面的進一步語義分析)
字節碼生成:
進行了少量的代碼添加和轉換工作
把生成的信息(語法樹、符號表)轉化成字節碼寫到磁盤
“寫到磁盤”由com.sun.tools.輸出字節碼,生成最終Class文件
入口generate()
實現類com.sun.tools.javac.jvm.Gen類
項目地址:
https://github.com/w296488320/OllJavaC
參考:
https://zhuanlan.zhihu.com/p/93939780
https://www.cnblogs.com/wade-luffy/p/5925728.html
https://blog.csdn.net/crabstew/article/details/89547472
https://www.sohu.com/a/212579385_100063030
http://blog.sina.com.cn/s/blog_17c534c120102xwv9.html
安卓逆向百級教程+全網最新js逆向視頻+永久小蜜圈+永久售后群=1299
視頻下載網盤
?http://nas.alienhe.cn:5008/home.html?
下載視頻賬號密碼:
賬號guest 密碼world
Js試看:
http://oss.alienhe.cn/JS%E9%80%86%E5%90%91%E5%85%A5%E9%97%A8-%E5%B8%A6%E6%B0%B4%E5%8D%B0.mp4