每一行部署到生產環境的程式碼都是由人們在各種限制條件下編寫的:時間壓力、上下文資訊不完整、文檔不全,以及即時處理大型系統邏輯推理的固有難度。靜態程式碼分析是一門系統性地檢查原始程式碼而不執行程式碼的學科,它利用自動化工具來發現人工審查容易忽略的問題:安全漏洞、邏輯錯誤、違反編碼規範、無用程式碼以及預示未來維護問題的結構模式。它並不能取代測試、設計審查或工程師的判斷。它是一層自動化審查機制,能夠對每個文件、每次提交和每次建置進行檢查,其速度和一致性是任何人工流程都無法比擬的。
這個定義聽起來很簡單。但實際上,靜態分析涵蓋了廣泛的技術,其深度和精確度各不相同,適用於開發生命週期的不同階段,而且不同工具的檢測能力也存在顯著差異。一個強制執行格式規則的程式碼檢查工具(linter)技術上也屬於靜態分析。而一個能夠建立完整呼叫圖、追蹤受污染資料到安全漏洞、識別不可達分支以及映射多語言企業系統中字段級依賴關係的工具也屬於靜態分析,但這兩個工具的技術深度和實用性卻截然不同。理解這種差異是有效選擇和使用靜態分析的前提。
什麼是靜態分析,它不是什麼?
靜態分析將原始程式碼視為結構化的工件,利用程式語言的語法和語義建立程式碼功能模型,然後查詢該模型以取得感興趣的屬性。此分析無需執行程式碼:無需執行時間環境,無需測試輸入,也無需觀察執行軌跡。原始檔案即為輸入,分析結果完全基於程式碼的結構、內容和相互關係得出。
這種不執行的特性既是靜態分析的價值所在,也是其限制的根源。由於靜態分析不執行程式碼,因此它可以覆蓋所有程式碼路徑,包括測試永遠不會觸及的路徑:很少執行的錯誤處理程序、僅由特定資料配置啟動的條件分支,以及多年未測試的遺留程式碼路徑。但由於它不執行程式碼,因此也無法觀察執行時間行為,無法推斷僅在執行時間確定的值,並且當程式碼行為依賴它無法存取的執行上下文時,它必須使用近似值。
實際應用效果是,靜態分析能夠發現一類具體、有價值且定義明確的問題:結構性問題、策略違規、與已知漏洞類型相關的模式,以及程式碼文字和結構中體現的依賴關係。它無法發現僅在特定運行時條件下才會出現的問題、需要並發執行才能觸發的競態條件,或依賴對程式碼預期功能的語意理解的業務邏輯錯誤。這些限制並不會降低靜態分析的價值;它們正好定義了靜態分析的適用範圍。正是對這一範圍的理解,使得團隊能夠將靜態分析適當地整合到測試、程式碼審查和運行時監控等流程中,而不是將其視為任何一項流程的替代品。
靜態分析與動態分析
動態分析透過執行程式碼來評估其效能。此工具會觀察運行時行為:記憶體分配和釋放、每條程式碼路徑的執行時間、特定點的變數值、並發模式以及系統呼叫。動態分析能夠發現僅在執行過程中才會出現的問題:例如,長時間運行累積的記憶體洩漏、並發執行緒之間的競爭條件、特定負載模式下的效能下降以及由意外輸入值引起的當機。
這兩種方法是互補的,而非競爭的。以下比較闡述了每種方法的實際應用範圍:
| Property | 靜態分析 | 動態分析 |
|---|---|---|
| 需要執行 | 沒有 | 可以 |
| 程式碼路徑覆蓋率 | 所有路徑,包括未執行的路徑 | 僅執行路徑 |
| 查找運行時記憶體錯誤 | 部分(僅限圖案) | 是的,直接 |
| 尋找程式碼結構中的安全漏洞 | 可以 | 部分 |
| 尋找並發錯誤 | 部分(僅限圖案) | 是的,直接 |
| 適用於不完整的程式碼 | 可以 | 沒有 |
| 一次即可擴展到整個程式碼庫 | 可以 | 取決於測試覆蓋率。 |
| 檢測死代碼 | 可以 | 沒有 |
| 識別跨組件依賴關係 | 可以 | 部分 |
最有效的品質保證程序會同時採用這兩種方法。靜態分析可以在程式碼運行之前,對結構性問題和策略違規進行早期、全面的覆蓋。動態分析則可以在執行時間驗證程式碼的行為。單獨使用任何一種方法都無法全面涵蓋品質和安全性方面的所有問題。
靜態分析在開發生命週期中的位置
靜態分析應該儘早融入開發生命週期:在開發者編寫程式碼時,在整合開發環境 (IDE) 中;在程式碼進入版本控制系統之前運行的 pre-commit 鉤子中;以及在持續整合 (CI) 管線中,對合併前的每一次變更進行驗證。正是這種部署方式使得靜態分析成為一種預防機製而非檢測機制:在 IDE 中發現的問題只需幾分鐘即可修復;在 pre-commit 階段發現的問題需要數小時才能修復;而部署後發現的問題,無論在時間還是風險方面,都會造成更大的損失。
這項原則有時被稱為“左移”,指的是將品質檢查提前到開發流程的早期階段,也就是典型的從左到右的軟體開發生命週期(SDLC)時間線的左側。靜態分析是實現安全性和品質檢查左移的主要技術機制,因為它是唯一一種可以在程式碼尚未完成到足以執行之前、在編寫測試套件之前以及在經過其他人審查之前對其進行自動化分析的方法。正如在以下上下文中所述: DevOps 整合提升程式碼品質將自動化分析嵌入日常開發工作流程中,是希望在不隨團隊規模成比例增加人工審查工作的情況下,大規模維持程式碼品質的組織的基本實踐。
靜態分析的工作原理:技術層面
靜態分析工具在幾個不同的技術層面上運行,每個層面都提供不同類型的分析並檢測不同類型的問題。理解這些層面至關重要,因為不同的工具在不同的層面上運行,而層面決定了工具能夠發現什麼以及不能發現什麼。
詞彙分析:表層
詞法分析是靜態分析中最基礎的一層。它將原始碼視為一系列字符,並將其分解為標記:關鍵字、標識符、運算符、字面量和分隔符。用於強制執行命名規範、空格規則、最大行長度和停用關鍵字使用等程式碼檢查工具主要在詞法層級進行分析。它們速度快、配置要求低,並且能夠持續有效地檢測出表面層面的策略違規。
詞法分析無法推斷程式碼的實際功能。它知道變數的命名方式,但不知道變數代表什麼,也不知道其值如何在程式中傳遞。它只注重形式,而不理解內容。因此,詞法分析作為一種獨立的品質保證機制是必要的,但卻是不充分的:它能確保程式碼的可讀性和一致性,但無法發現邏輯錯誤、安全漏洞或結構性問題。
句法分析:無語義的結構
語法分析根據程式語言的語法解析原始程式碼,產生抽象語法樹(AST),該語法樹表示程式碼的結構關係:哪些表達式是其他表達式的子表達式,哪些語句屬於哪些程式碼區塊,哪些標識符是聲明,哪些是引用。許多靜態分析工具主要在語法層面運行,使用 AST 模式匹配來檢測與已知問題相關的程式碼結構。
用於標記複雜度超過閾值的函數的規則是基於語法層面的:它會統計函數體抽象語法樹(AST)中的決策點數量。用於偵測空值解引用模式的規則也是基於語法層面的:它會尋找未進行空值檢查而使用了可能為空值的 AST 模式。這些檢測方法比詞法分析更強大,因為它們基於結構進行推理,但它們仍然是基於模式而非語義進行操作。空值解引用模式匹配並不知道變數在其使用的上下文中是否真的可以為空;它只知道該模式存在。
語意分析:意義與類型
語意分析基於程式碼的解析意義進行操作:每個表達式的型別是什麼,每個引用指向哪個聲明,呼叫了哪個重載方法,以及型別系統能夠證明程式中流轉的值的型別。類型檢查是最常見的語意分析形式。當編譯器類型檢查器拒絕傳遞字串但預期為整數的程式碼時,它就是在執行靜態分析。
更複雜的語意分析包括類型推論和空值安全分析。類型推斷用於確定未明確標註的表達式的類型,而空值安全分析則用於追蹤可能為空的值在使用前是否經過安全檢查。這些分析需要完整的符號解析,這意味著它們與特定語言相關,並且需要完整或接近完整的程式碼:它們無法處理缺少類型定義或引用在不可用依賴項中定義的符號的程式碼片段。正如在更廣泛的討論中所探討的… 傳統系統現代化規劃對可能存在不完整或未記錄依賴項的遺留程式碼庫執行完整的語義分析的能力,需要能夠處理這些環境的特定結構模式的專門工具。
資料流程分析:價值體現在執行過程中
資料流分析追蹤程式中值的流動方式。它基於程式的控制流程圖進行操作,沿著執行路徑傳播變數值的信息,並記錄值的來源、修改位置和使用位置。資料流分析能夠偵測出諸如未初始化變數讀取、記憶體管理中的釋放後使用以及從使用者輸入到安全敏感操作的污點傳播等問題。
污染分析是一種特殊的資料流分析方法,它追蹤來自不可信來源(使用者輸入、網路資料、檔案內容)的值,並識別這些值是否能在未經清理的情況下到達安全敏感操作(SQL 查詢、系統呼叫、輸出操作)。如果受污染的值未經清理就到達了安全目標,分析就會標示出潛在的注入漏洞。這是靜態分析工具中大多數 SQL 注入、跨站腳本攻擊和命令注入漏洞偵測背後的自動化機制。
這兩條路徑在程式碼上的差異微乎其微,但安全後果卻截然不同:
# Vulnerable: user input reaches SQL query without sanitization (tainted path)
def get_user(username):
query = "SELECT * FROM users WHERE name = '" + username + "'"
return db.execute(query) # sink: tainted value reaches SQL execution
# Safe: sanitization breaks the taint chain before the sink
def get_user_safe(username):
query = "SELECT * FROM users WHERE name = ?"
return db.execute(query, (username,)) # parameterized: taint neutralized
靜態污點分析無需執行程式碼,也無需惡意測試輸入觸發,即可偵測第一個函數中的漏洞模式。資料流分析計算量龐大,並且面臨著精度與效能之間的根本性權衡。對於大型程式碼庫而言,考慮所有可能執行路徑的精確資料流分析通常不切實際。大多數工具使用近似方法,以犧牲部分精確度為代價來換取可擴展性,這就是為什麼資料流分析結果通常包含一定比例的誤報,需要人工審核。 程式碼視覺化 執行路徑和資料流的分析結果使得開發人員能夠理解這些分析結果,並驗證標記的路徑是否真的可以在他們的應用程式上下文中被利用。
控制流程分析:執行路徑
控制流程分析建立一個包含所有可能程式碼執行路徑的圖,識別哪些語句可達、哪些語句無效,以及每個分支執行必須滿足哪些條件。控制流程圖是許多其他分析的基礎:資料流分析是基於控制流程圖進行操作,可達性分析利用它來識別無效程式碼,而諸如圈複雜度之類的複雜度指標也由此推導而來。
控制流程分析能夠實現死代碼偵測:已定義但從任何入口點都無法存取的程式碼,在控制流程圖中沒有入邊,可以被識別為未使用。這與以下方面直接相關: 應用程式依賴關係映射 企業團隊在現代化之前需要了解:哪些程式碼路徑是有效的,哪些是無效的,這決定了在遷移過程中哪些內容可以安全刪除,哪些內容必須保留。
呼叫圖分析:跨元件關係
呼叫圖分析建立了一個模型,用於描述整個程式碼庫中哪些函數呼叫了哪些其他函數。完整的呼叫圖支援呼叫者列舉、被呼叫者列舉、傳遞依賴分析,以及識別從未被任何入口點呼叫的函數。跨組件呼叫圖分析涵蓋多個文件、模組和套件,是影響分析的技術基礎:它能夠確定如果更改給定函數或接口,哪些部分會受到影響。
在單語言、單程式碼庫中,大多數成熟的靜態分析工具都能很好地支援呼叫圖的建構。而在多語言企業環境中,建構完整的呼叫圖需要一個統一的分析平台,該平台能夠整合系統中的所有語言,並解析它們之間的跨語言呼叫關係。 JavaScript 和 Node.js 程式碼庫動態模組載入、基於原型的調度和回調模式使情況變得更加複雜。對於混合了 COBOL、JCL、SQL 和現代服務層的企業系統而言,挑戰規模顯著擴大,需要特定語言的解析器和跨語言圖模型來表示整個系統。
靜態分析能偵測到什麼:一個實用的分類法
靜態分析工具能夠偵測的問題類別非常廣泛,不同的工具著重於不同的子類別。了解這些分類有助於團隊將工具的功能與特定的檢測需求相匹配。
透過模式和污點分析發現的安全漏洞:
- SQL注入、跨站腳本攻擊、透過污點傳播從使用者控制的來源向安全目標進行指令注入
- 不安全的加密使用:演算法強度不足、金鑰長度不夠、使用已棄用的加密模式
- 原始碼中嵌入了硬編碼的憑證、API金鑰和金鑰值
- 不安全的反序列化模式與不安全的 XML 解析配置
- 檔案存取操作中的路徑遍歷漏洞
透過結構分析發現的程式碼品質和可維護性問題:
- 過高的圈複雜度表示程式碼難以測試和安全修改。
- 過長的函數和類別違反了單一職責原則。
- 當其中一個副本更新而另一個副本未更新時,重複的程式碼區塊會帶來維護隱患。
- 未使用的變數、參數和導入會增加噪聲,而不會對行為產生任何影響。
- 命名規則不一致和樣式違規會降低可讀性
透過語意和資料流分析發現的正確性問題:
- 在沒有空安全強制機制的語言中,空引用解引用會造成問題。
- 讀取未初始化的變數會導致未定義行為
- 算術運算中的整數溢位與下溢
- 資源洩漏是指已取得的資源未在所有程式碼路徑上釋放。
- 不正確的異常處理會默默地吞噬錯誤。
透過呼叫圖和依賴關係分析發現的結構性問題:
- 死機號碼,從任何入口都無法接通任何呼叫者
- 模組間的循環依賴關係顯示架構分離性較差。
- 在已遷移到替代實作的程式碼庫中,已棄用的函數用法
- 無條件回傳或拋出異常後,程式碼無法執行。
- 在對可能傳回 null 的函數傳回的值進行解引用之前,缺少空值檢查。
對於 Node.js 應用程序 以及其他動態運行時環境,檢測類別擴展到涵蓋非同步模式:缺少 Promise 拒絕處理程序、回調錯誤優先模式違規以及事件發射器記憶體洩漏。 Rust 和系統編程 在某些情況下,分析重點關註生命週期違規、不安全區塊使用以及編譯器無法完全驗證的並發安全屬性。
靜態分析無法偵測到的內容
了解靜態分析的限制與了解其能力同樣重要。如果團隊期望靜態分析能夠捕捉所有缺陷,他們將會失望,並且可能對分析結果的準確性產生錯誤的信任。某些類型的問題在結構上超出了靜態分析的範疇。
僅運行時行為 從定義上講,動態分析無法觸及所有問題。例如,只有在長時間運行後才會出現的記憶體洩漏、特定負載模式下的效能下降、依賴非確定性執行緒調度的並發錯誤,以及由運行時狀態的意外組合導致的崩潰,都需要透過執行才能檢測到。動態分析、效能分析和壓力測試涵蓋了這些領域。
業務邏輯錯誤 依賴領域知識的問題無法透過靜態分析檢測。例如,由於公式錯誤而導致利息計算錯誤的函數、使用錯誤時間邊界匯總資料的報表,或授予錯誤使用者存取權限的授權檢查:這些都是需要語義知識才能發現的正確性問題。靜態分析可以驗證程式碼是否符合結構模式,但無法驗證程式碼是否實現了正確的業務行為。功能測試和規範審查可以彌補這一不足。
配置漏洞 存在於部署工件、基礎架構定義和環境設定(而非原始碼)中的設定問題,可以透過基礎架構即程式碼分析進行部分靜態分析來解決,但許多設定問題只有在執行時間或程式碼與其執行環境的互動中才能看到。
複雜的身份驗證和授權缺陷 涉及多個元件、會話狀態或依賴呼叫鏈中多個安全性檢查互動的事件,靜態分析難以正確推理。此類事件中誤報和漏報現象普遍存在,需要專家審核才能評估結果。
評估並選擇靜態分析工具
選擇靜態分析工具是配對問題:哪種工具的功能能夠滿足程式碼庫、團隊和組織的需求?工具之間差異顯著的面向包括語言支援、分析深度、誤報率、整合支援和可擴展性。
語言支持 這是初始約束。如果一個工具不支援程式碼庫中的語言,那麼它對該程式碼庫就沒有任何價值。在多語言環境中,我們面臨的選擇是:要麼使用多個單語言工具(每個工具都能很好地支援各自的語言,但無法進行跨語言分析),要麼使用一個支援多種語言並整合跨語言依賴關係解析的統一平台。對於既包含大量遺留程式碼又包含現代元件的企業系統而言,統一平台方案通常是必要的,因為跨語言依賴關係正是單語言工具無法表示的。
分析深度 工具能夠發現哪些類型的問題,取決於其分析深度。僅在詞法和語法層面運作的工具無法發現資料流漏洞或死程式碼。而能夠進行完整過程間資料流分析的工具雖然可以發現更多漏洞,但也會產生更多誤報,並且需要更多運算資源。分析深度應根據程式碼庫的風險等級而定:安全至關重要的金融或醫療保健系統通常需要進行深度資料流分析,而內部工具程式碼庫則可能只需進行較輕的結構分析即可。
誤報率 這會成為推廣應用的一大實際限制。如果一個工具在分析的每個程式碼庫中都標記出大量非問題,那麼團隊就會將其配置為忽略這些問題,這意味著團隊不僅無法從這些分析規則中獲益,還要承擔持續抑制這些錯誤發現的成本。誤報率取決於工具的分析品質和所應用規則的特異性。評估工具的團隊應該使用自身程式碼的代表性樣本進行測試,並衡量有效發現與被抑制發現的比率,而不是依賴供應商提供的基於合成程式碼庫的基準測試。
CI/CD 和 IDE 集成 這決定了該工具是實際應用還是僅作為偶爾的審計活動。需要單獨手動運行並在單獨介面中產生結果的工具,其使用頻率遠低於那些在開發者編寫程式碼時直接在 IDE 中顯示發現結果,並在引入新違規的拉取請求時發出警告的工具。整合品質是影響實際應用的一個因素,其重要性與分析品質不相上下,都關係到能否達到一致的覆蓋範圍。
可擴展性 在大型程式碼庫中,這會成為一個棘手的限制。一個分析百萬行程式碼庫需要數小時的工具無法整合到提交或拉取請求的工作流程中。增量分析(每次運行只重新分析已更改的文件及其依賴項,而不是整個程式碼庫)是實現大規模逐次提交靜態分析的技術機制。評估工具時,應同時考慮其增量分析能力和全掃描性能。
企業多語言環境下的靜態分析
在企業環境中,靜態分析的挑戰顯著增加,因為程式碼庫跨越多種語言、多個平台,並且累積了數十年之久。在單一語言的全新程式碼庫中行之有效的分析方法,在這些環境中往往會失效,原因可能是工具不支援現有語言,無法對跨語言依賴關係進行建模,或者遺留程式碼的結構模式與為現代程式碼庫設計的工具的固有假設不符。
例如,COBOL 程式的結構模型是基於分部、節和段落,這與大多數靜態分析框架所採用的函數類別模型有著根本的不同。基於 Copybook 的共享定義、PERFORM-THRU 段落範圍以及使用連字符而非駝峰式命名或下劃線的資料命名約定,都是 COBOL 的結構特徵,而這些特徵通常難以被語言無關的工具處理,甚至根本無法處理。 JCL 用於協調大型機批次程式的執行並定義它們之間流動的資料集,但任何通用靜態分析平台都無法對其進行分析。
在那些同時依賴大型主機、傳統平台和現代服務的組織中,由此導致程式碼覆蓋率存在結構性缺陷:靜態分析工具能夠全面覆蓋現代程式碼,卻完全忽略傳統程式碼,或只能分別分析每種語言,而無法了解它們之間的關係。這種缺陷在最難解決的地方影響最大:例如,跨語言介面中,COBOL 程式的變更會影響讀取其輸出的 Java 服務,或者資料庫模式的變更會同時影響傳統批次層和現代 API 層。正如在…的上下文中所述 大型主機現代化規劃 以及 IBM i RPG平台過渡要規劃任何現代化項目,使其在解決現有風險的同時不產生新的風險,就必須了解整個應用程式組合(包括遺留組件)的當前狀態。
SMART TS XL 提供全企業範圍的靜態程式碼分析
SMART TS XL 系統基於這樣的前提建構:企業程式碼庫需要進行系統層級分析,而非檔案層級或程式碼庫層級分析。其軟體智慧平台能夠從環境中所有語言和平台(包括 COBOL、JCL、Java、.NET、Python、JavaScript、TypeScript、SQL 等)提取原始程式碼,並使用特定語言的分析方法將每種語言的程式碼解析成一個統一的交叉引用模型。此模型展現了整個系統的結構關係:跨越語言邊界的呼叫圖、追蹤 COBOL 定義中位數經由資料庫列最終到達 Java 服務的欄位層級資料流追蹤、顯示哪些程式碼路徑處於活動狀態、哪些已失效的控制流程圖,以及識別受建議變更影響的每個元件的依賴關係圖。
这 靜態程式碼分析解決方案 每 SMART TS XL 提供的並非一系列透過通用儀表板協調的、針對特定語言的程式碼檢查工具。它是一個統一的分析平台,將系統作為一個整體進行建模,從而實現企業環境所需的跨語言和跨組件分析。開發人員如果詢問“如果我更改此函數,哪些方面會受到影響?”,則會獲得基於統一依賴關係圖的完整答案,而不是來自他們當前查看的單語言工具的部分答案。執行污點分析的安全分析師可以追蹤敏感資料在系統中從來源到目標的流轉,無論資料跨越多少種語言邊界。規劃遷移的現代化團隊可以全面了解哪些元件之間存在依賴關係,並按層、語言和特定關係類型進行組織,而不是僅限於恰好使用現代工具的元件。
SMART TS XL的企業級搜尋功能為調查提供了入口,返回的結果按結構關係類型而非字符串出現次數進行組織:定義、調用、讀取、寫入、副本包含、SQL 引用和 API 暴露等信息在結果集中均有區分,使開發人員無需篩選文本匹配列表即可獲得所需的特定信息。其程式碼視覺化功能將深入的結構分析轉化為易於導航的流程圖和依賴關係圖,使複雜的系統變得易於理解,而無需開發人員逐行閱讀程式碼。
靜態分析是基礎,而非終點
靜態分析的價值只有在將其視為基礎設施而非工具時才能最大程度地體現:它應該持續運行於所有程式碼之上,產生可進行系統性審查的分析結果,並且其輸出結果應與開發工作流程緊密結合,而非僅偶爾參考。實現這種整合程度的組織會發現,靜態分析能夠逐步將品質和安全工作從被動的補救(事後發現問題)轉變為主動的預防(在問題發生之前就消除與之相關的模式)。
實現這一目標的關鍵並非主要在於工具本身。更艱鉅的工作在於文化和流程層面:建立一種預期,即靜態分析結果必須得到解決而非被忽略;配置工具,使其能夠根據特定程式碼庫的分析深度與誤報率之間取得平衡;將分析結果整合到整合開發環境 (IDE) 和持續整合 (CI) 工作流程中,以便在進修階段而非單獨的程式碼審查階段就能發現這些設定庫的結果;工具能夠實現這些目標,而組織實踐則能維持這些目標的實現。對於經營跨多種語言、跨平台且擁有數十年程式碼累積的企業而言,工具基礎架構必須能夠涵蓋如此龐大的範圍。涵蓋 80% 程式碼庫的靜態分析的價值並非完全涵蓋的 80%;它受限於未涵蓋的 20% 程式碼庫所蘊含的風險。