-
簡介
在專案中,存在許多不規範的程式碼,其一就是將無符號變數賦值給有符號變數。在大多數情況下是不會出現問題的,因為那些變數值往往小於2147483648
。
但是一些特定的介面,如時間獲取介面,可能返回一個較大的無符號值,如果使用int
變數接收,便可能出現異常。當這些介面在專案中大量使用時,排查起來較為困難,容易發生遺漏,因此引入程式碼掃描工具進行特定介面的使用檢查。
後續將針對TimeGet
函式進行問題的詳細說明。 -
TimeGet 介面宣告
// 獲取時間 // IN: 時間格式(如 TIME_YYMMDDHHMM) // OUT: 時間值(如 2105301530 -> 21年5月30日15點30分) unsigned TimeGet(TIME_TYPE type);
-
問題表現
在進入22年後,TIME_YYMMDDHHMM
格式的時間值便超出了int
的表示範圍,如果誤用int
變數進行接收,便可能出現如開始時間未到等各種問題。 -
錯誤用法
- 使用
int
變數接收TimeGet
返回值。如下程式碼,在進入22年後,活動仍然處於未開始狀態。int nTime = TimeGet(TIME_YYMMDDHHMM); if (nTime < 2112125959) { ShowMessage("活動未開始。"); return ; }
- 格式串中使用
%d
接收TimeGet
返回值。如下程式碼,在進入22年後,會將整張表讀取到記憶體,實際上需要的可能只有三五條。char szSQL[1024]; sprintf(szSQL , "select * from tbl where out_time > %d" , TimeGet(TIME_YYMMDDHHMM)); auto result = database()->executeQuery(szSQL);
- 通用的資料庫記錄物件可能只提供了
GetInt
和SetInt
介面,在表欄位型別為unsigned
時,呼叫SetInt( TimeGet(TIME_YYMMDDHHMM) )
是不會出現問題的,但是需要小心使用GetInt
介面。如下程式碼就將問題藏得很隱蔽,實際上,GetInt
返回值已經是個負數了,直接賦值給long long
變數,i64Value
仍然是個負數。
這種情況下,加個型別轉換就能解決問題。long long i64Value = data.GetInt(start_time);
long long i64Value = (unsigned)data.GetInt(start_time);
- 使用
-
排查難點
TimeGet
在程式碼中使用得很頻繁,直接搜尋TimeGet(TIME_YYMMDDHHMM)
會出現大幾百行。- 有可能存在
TimeGet
接收時無誤,但後續傳遞過程中出現錯誤的情況,如下程式碼,Func
函式正確處理了TimeGet
的返回值,但外部使用Func
卻出現了錯誤。unsigned FuncX() { return TimeGet(TIME_YYMMDDHHMM); } int main() { int nTime = FuncX(); //... return 0; }
-
程式碼靜態掃描工具
考慮到人工排查的困難,這裡引入cppcheck 並自定義規則進行程式碼的掃描,通過工具輔助,來度過22年時間溢位帶來的危機。 -
掃描規則
- 確定合法的接收型別:
unsigned unsigned & unsigned long unsigned long & unsigned long long unsigned long long & signed long long signed long long &
- 定位到所有
TimeGet(TIME_YYMMDDHHMM)
呼叫位置。 - 查詢
TimeGet
返回值的接收者,為了方便理解,這裡直接描述為向前尋找接收物件(實際實現上使用cppcheck 的語法分析樹查詢)。接收者存在如下幾種情況:- 變數賦值
這裡向前查詢會遇到賦值符號(可能是=、+=、-=、|=等等),這說明int nTime = TimeGet(TIME_YYMMDDHHMM);
TimeGet
將會賦值給某個變數,這時可以檢查變數的型別是不是合法的。 - 函式返回
這裡向前查詢會遇到int func() { return TimeGet(TIME_YYMMDDHHMM); }
return
,這說明TimeGet
返回值將通過函式進一步返回,這時可以檢查函式的返回值型別是不是合法的。 - 函式傳參
這裡向前查詢會遇到func( TimeGet(TIME_YYMMDDHHMM) );
func(
,這說明TimeGet
返回值將傳遞給func
的引數,這時檢查對應函式的引數型別。 - 構造
- 匿名構造
這裡向前查詢會遇到struct User { User(int nTime) { //... } }; int main() { func ( User( TimeGet(TIME_YYMMDDHHMM) ) ); // ... }
User(
,此處的User
是一個型別名,這說明TimeGet
返回值將傳遞給User
的建構函式,這時檢查對應函式的引數型別。cppcheck 這裡並未直接將User
程式碼連結到User
類的建構函式,而僅僅認為此處的User
是一個型別,因此這裡需要自行根據傳入引數索引建構函式。 - 普通構造
這裡向前查詢會遇到struct User { User(int nTime) { //... } }; int main() { User user(TimeGet(TIME_YYMMDDHHMM)); }
user(
,此處的user
是一個變數,這說明TimeGet
返回值將傳遞給user
變數所屬型別的建構函式,同樣的,這時需要檢查對應函式的引數型別。 - 標準資料型別構造
這裡向前查詢會遇到int(TimeGet(TIME_YYMMDDHHMM))
int(
,此處的int
是一個型別,但其屬於基本型別,無法找到其建構函式,此時應直接判斷型別是否合法。
- 匿名構造
- 取餘、比較
這裡直接向前查詢會發生誤判,認為將時間賦值給int nHHMM = TimeGet(TIME_YYMMDDHHMM) % 10000; bool bZero = TimeGet(TIME_YYMMDDHHMM) == 0;
int
或bool
變數,但是使用語法分析樹判斷時,會先找到%
或==
符號,這裡認為返回值的性質已經發生了變化,則不應算是錯誤。 - 控制流
這裡最終會查詢到if (TimeGet(TIME_YYMMDDHHMM)) { // ... }
if(
,事實上這裡隱含了時間值與 0的判斷,可以認為返回值性質發生了變化,不應算是錯誤。掃描工具中允許了if
和switch
的控制流關鍵字,其他關鍵字(如while
)則輸出錯誤資訊。 - 不定參函式
這裡會查詢到char szSQL[1024]; sprintf(szSQL , "select * from tbl where out_time > %d" , TimeGet(TIME_YYMMDDHHMM));
sprintf(
,但與普通的函式傳參不同,這裡的sprintf
是不定參的,即無法正常檢查傳參型別是否合法。不定參函式過於複雜,目前版本只處理系統庫中格式串函式。 - 其他複雜情況
過於複雜的程式碼,這裡暫不考慮,目前版本只適用於一般情況。func( { TimeGet(TIME_YYMMDDHHMM) } ); array[0] = TimeGet(TIME_YYMMDDHHMM); *(p + 1) = TimeGet(TIME_YYMMDDHHMM); \\ ...
- 變數賦值
- 如果接收者的型別不合法,則可以簡單地輸出錯誤log 並結束該處
TimeGet
的檢查。如果接收者的型別合法,則需要進行遞迴檢查。遞迴檢查存在如下情況:- 接收者為變數
此時,需要重新掃描變數的生效區域,針對unsigned uTime = TimeGet(TIME_YYMMDDHHMM); int nTime = uTime;
uTime
進行類似TimeGet
返回值的檢查。值得注意的是,如果接收者是區域性變數,則只要搜尋當前塊即可,如果接收者是全域性變數,則需要搜尋全部程式碼。
目前版本未針對處理引用變數,如下問題工具無法掃描出來。unsigned uTime = 0; unsigned& rTime = uTime; rTime = TimeGet(TIME_YYMMDDHHMM); int nTime = uTime;
- 接收者為成員變數
此時,為了簡化邏輯,將遞迴檢查物件設定為struct User { unsigned nLoginTime; }; void func(User& user) { user.nLoginTime = TimeGet(TIME_YYMMDDHHMM); }
User::nLoginTime
,即所有User
物件的nLoginTime
都視為檢查目標,不關心是否真的傳遞過TimeGet
返回值。 - 接收者為函式返回值
此時,需要檢查unsigned func() { return TimeGet(TIME_YYMMDDHHMM); }
func
所有呼叫位置。 - 接收者為函式引數
此時,需要檢查void func(unsigned uTime) { int nTime = uTime; } func(TimeGet(TIME_YYMMDDHHMM));
func
引數列表中的uTime
變數。
- 接收者為變數
- 確定合法的接收型別:
-
標籤功能
基於cppcheck 的框架,掃描時並沒有一份全部程式碼的符號庫,而是遍歷掃描每一個cpp 檔案,同時間僅有當前掃描cpp 檔案的完整內容,及其關聯標頭檔案的函式宣告等。這意味著,發現向某函參傳遞TimeGet
時,如果該函式體未被cppcheck 載入分析,此時只能判斷引數型別是否合法,無法跟蹤函式引數後續的使用是否合法。
因為上述問題,引入標籤功能,對於當次執行無法掃描的功能,記錄標籤寫入配置檔案,下次執行cppcheck 時讀取標籤檔案進行掃描。
標籤型別如下:- 初始函式
[INIT_FUNCTION]
,即本例中的TimeGet
。
忽略引數可用於控制只檢查個別時間格式,如標籤型別;函式標識;追查堆疊;忽略引數 [INIT-FUNCTION];TimeGet(signed int type) at /project8/base.h;;
-1 |0 TIME_SECOND|0 TIME_DAY
表示不檢查TimeGet()
、TimeGet(TIME_SECOND)
、TimeGet(TIME_DAY)
的使用。 - 普通函式
[FUNCTION]
,即出現return TimeGet
並且返回值型別合法的函式。與[INIT_FUNCTION]
的區別在於後續掃描未增加該標籤,則會被清除(如函式丟失或函式的return
語句不再返回TimeGet
等)。標籤型別;函式標識;追查堆疊 [FUNCTION];Test() at /project8/main.cpp;$G_TimeGet at (/project8/main.cpp, 76)
- 函式引數
[FUNCTION-ARGUMENT]
,即出現func(TimeGet)
並且func
引數型別合法的情況。標籤型別;函式標識;引數編號;引數型別|引數名;追查堆疊 [FUNCTION-ARGUMENT];func(unsigned int uTime) at /project8/base.h;0;unsigned int|uTime;$G_TimeGet at (/project8/main.cpp, 83)
- 變數
[VARIABLE]
,即出現user.time = TimeGet
並且變數型別合法的情況。標籤型別;變數宣告的檔案路徑|變數歸屬|變數型別|變數名;追查堆疊 [VARIABLE];/project8/base.h|User|unsigned int|time;$G_TimeGet at (/project8/main.cpp, 83)
- 初始函式
-
原始碼地址
程式碼靜態掃描規則——型別轉換檢查
相關文章
- React的靜態型別檢查React型別
- 微服務測試之靜態程式碼掃描微服務
- Vue中的靜態型別檢查Vue型別
- ReactFlow程式碼靜態檢查React
- Flow_JS靜態型別檢查器JS型別
- java 基本型別的轉換規則Java型別
- JS資料型別轉換規則JS資料型別
- 【Lua篇】靜態程式碼掃描分析(一)初步介紹
- ESLint 靜態程式碼檢查EsLint
- Flow:Facebook 的 JavaScript 靜態型別檢查器JavaScript型別
- python程式碼檢查工具(靜態程式碼審查)Python
- 隨行付微服務測試之靜態程式碼掃描微服務
- FindBugs:Java 靜態程式碼檢查Java
- CSS 程式碼靜態質量檢查CSS
- JavaScript 程式碼靜態質量檢查JavaScript
- Typecho程式偽靜態規則
- flow–facebook出品的javascript靜態型別檢查器JavaScript型別
- 使用OClint進行iOS專案的靜態程式碼掃描iOS
- Groovy 2.0靜態型別檢查及編譯功能介紹型別編譯
- Android 隱私合規靜態檢查Android
- thinkphp 偽靜態規則PHP
- 靜態路由規則配置路由
- [原創]Java靜態程式碼檢查工具介紹Java
- 利用Sonar定製自定義掃描規則
- Flow靜態型別檢查及在Vue專案中的使用型別Vue
- target/mmk-ui-api: 根據規則引擎掃描惡意程式碼的工具UIAPI
- C的動態型別檢查型別
- JavaScript 運算子規則與隱式型別轉換詳解JavaScript型別
- 淺談程式語言型別的強型別,弱型別,動態型別,靜態型別型別
- 重拾Kotlin(10)-型別的檢查與轉換Kotlin型別
- c# 優化程式碼的一些規則——使用is或as和強制型別轉換的區別[三]C#優化型別
- Web應用型別掃描識別工具WhatWebWeb型別
- java靜態程式碼檢測-pmdJava
- GitHub 熱門:微軟新開源的 Python 靜態型別檢查器Github微軟Python型別
- C++ 動態型別轉換C++型別
- Nginx常用Rewrite偽靜態規則Nginx
- 利用 SonarScanner 靜態掃描 Rainbond 上的 Maven 專案AIMaven
- Model Inspector — 軟體模型靜態規範檢查工具模型