一、前言
通過前面三篇文章已經初步實現了將Lua原始碼檔案讀取解析成語法樹,現在就可以通過得到的語法樹進行指定規則的程式碼掃描檢查。下圖簡單列舉了一下單個Lua檔案內部的語法關係情況(注意並非真正的類圖,也沒有列舉完全部的節點型別)。
二、變數作用域
1 function main()
2 local value = g_total + 1
3 print("value:", value)
4 end
上面的簡單程式碼裡有一個g_total的全域性變數,它可能來自前面程式碼塊的定義,也有可能來自底層匯出的符號,或者是在其他檔案中定義的全域性變數。因此在判斷一個變數是否存在首先需要建立一個全域性的變數列表或白名單列表。
1 function demo(params)
2 local value1 = 1
3 if value1 == params then
4 local var_b = 2
5 local var_c = var_b + value1
6 end
7 end
上面程式碼也是一個函式宣告,它有自己的私有變數列表(包括引數params和內部的變數value1);而var_b、var_c則屬於是if內部的私有變數列表,在if之外是無法訪問的。因此在程式碼掃描之前需要先建立不同引數的作用域。
先對所有檔案的語法樹掃描一遍,把裡面的所有變數都記錄下來,存入全域性變數列表、當前檔案變數列表、或者某個內部程式碼塊的變數列表。然後再才對單個檔案進行規則掃描。特別需要注意module方法宣告的模組,內部的不加local的變數屬於module的公共變數。
1 def build_symbols(block):
2 # block引數就是當前檔案的全部程式碼
3 for statement in block.statements:
4 # 變數宣告,屬於block的自私變數列表
5 if statement.nodeType == LNodeCode.IDENTIFIER:
6 # 賦值包括a=1 和 a,b =1,2 等複雜的賦值語句
7 # 沒有加local的情況下,很可能是全域性變數
8 elif statement.nodeType == LNodeCode.ASSIGNMENT:
9 # 函式定義,需要檢查函式體內部的語句statement.block
10 # 遞迴呼叫build_symbols檢查
11 elif statement.nodeType == LNodeCode.FUNC:
12 # 函式呼叫類似 module("test",seeall)模組宣告, 全域性的test
13 # 類似 CreateClass("A", superB) ,全域性的A
14 elif statemen
三、規則掃描
根據需要進行的一系列的規則進行語法樹掃描檢查,以檢查變數是否存在為例:
1. 檢查變數時需要先在區域性變數列表中查詢(if、for等程式碼塊;函式程式碼塊;檔案程式碼塊等)
2. 沒有找到再去模組的公共變數列表(module)
3. 最後去全域性變數列表中查詢
4. 如果都沒有找到的話那麼就很可能這個變數不存在。
1 if statement.nodeType == LNodeCode.IDENTIFIER:
2 # 單個變數的宣告只有可能是local a
3 check_var(statement.name, statement.is_local)
4 elif statement.nodeType == LNodeCode.FUNC_CALL:
5 # 函式呼叫的引數檢查
6 check_multi_vars(statement.args)
7 # 函式本身檢查,看是否有這樣的函式
8 check_func(statement.name)
9 elif statement.nodeType == LNodeCode.OP:
10 # 操作例如+-*/%等,需要檢查左右的操作變數是不是存在
11 check_left_vars(statement.left)
12 check_right_vars(statement.right)
同樣也是遍歷檔案的block、函式體block、各種語句的block進行變數的存在性檢查,具體的內部檢查由於程式碼篇幅太長,就不詳細貼出來了。
Lua靜態程式碼掃描的基本處理方法就算列舉完了,實際專案的程式碼中會比較複雜,而且掃描的規則也會有很多不同的需求,就需要實際去做才理解了。
文章來自我的公眾號,大家如果有興趣可以關注,具體掃描關注下圖。