修改CppCheck,做自己的扫描器 - 02

栏目: C++ · 发布时间: 2年前

来源: www.nul.pw

内容简介:填坑。CppCheck在解析代码时,会将代码视作不同的Token。Token分类大致如下:1、 名称类:变量名 , 类型名 , 函数名, 语言关键字, 其他无法识别的名字

本文转载自:http://www.nul.pw/2018/12/27/273.html,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有。

填坑。

CppCheck在解析代码时,会将代码视作不同的Token。Token分类大致如下:

enum Type {
    eVariable, eType, eFunction, eKeyword, eName,
    eNumber, eString, eChar, eBoolean, eLiteral, eEnumerator, 
    eArithmeticalOp, eComparisonOp, eAssignmentOp, eLogicalOp, eBitOp, eIncDecOp, eExtendedOp, 
    eBracket, 
    eOther,
    eNone
};

1、 名称类:变量名 , 类型名 , 函数名, 语言关键字, 其他无法识别的名字

2、 字面量:数字,字符串,字符,布尔值,用户定义的常量(c++11),枚举

3、 操作符:数学操作符,比较符号,赋值符号,逻辑符号,位运算,自增自减符号,扩展符号

4、 括号:{, }, <, >

5、 其他

这些token在保存时都是通过字符串的方式保存的,所以在比较的时候,也是通过字符串比较的形式,来确定是否和规则匹配。

先看一个典型的例子:

bool Token::Match(const Token *tok, const char pattern[], unsigned int varid)

以及它的调用者;

if (Token::Match(tok2, "strncpy|strncat ( %varid% ,", arrayInfo.declarationId())) {

tok2是一个Token对象

pattern为 strncpy|strncat ( %varid% 。

pattern中的 | 代表多重比较,即:multiCompare。从当前token开始依次比较,以空格为界限。也即第一次比较到strncat之后停止。

multiCompare的参数为%varid%(目标参数)。

multiCompare会一直比较,直到token耗尽或者传入的pattern到头,最后给出比较结果。

除了%varid%,multiCompare也支持许多比较,具体可以参考源代码:token.cpp -> multiComparePercent。

可以看到,在匹配、抽取值时,并不需要传入完整的token。

所以,对我们编写CppCheck规则时,了解Token的内容十分重要。

以一个实际的例子来看:

const MathLib::biguint total_size = arrayInfo.num(0) * arrayInfo.element_size();
    if (printWarning && printInconclusive && Token::Match(tok2, "strncpy|memcpy|memmove ( %varid% , %str% , %num% )", arrayInfo.declarationId())) {
        if (Token::getStrLength(tok2->tokAt(4)) >= total_size) {
            const MathLib::biguint num = MathLib::toULongNumber(tok2->strAt(6));
            if (total_size == num)
                bufferNotZeroTerminatedError(tok2, tok2->strAt(2), tok2->str());
        }
    }

可以看到它:

1、 解析strncpy/memcpy/memmove并抽取varid(目标参数)、str(源字符串)、num(长度即操作数)。

2、 获取第5个token,即%str%处的token,获取它指向内容的长度。

3、 如果源操作数的长度大于等于缓冲区长度(由arrayInfo获取),代表可能潜在有问题。

4、 获取第7个token,即%num%处的token,获取它的值。

5、 如果缓冲区总大小等于长度,则报告缓冲区非零结尾的错误。

测试一下,给定测试用例:

int main()
{
 char a[10];
 memcpy(a, "abcdefghij", 10);

 return 0;
}

打开这三个提示信息以方便我们调试。为了方便我就不改全局的了,只在函数里面直接硬编码修改为true。

const bool printPortability = true; //mSettings->isEnabled(Settings::PORTABILITY);
const bool printWarning = true; //mSettings->isEnabled(Settings::WARNING);
const bool printInconclusive = true; // mSettings->inconclusive;

代码在我们指定的位置停下,调用栈如下:

cppcheck-core.dll!CheckBufferOverrun::checkScope_inner(const Token * tok, const CheckBufferOverrun::ArrayInfo & arrayInfo) 行 1005   C++
>   cppcheck-core.dll!CheckBufferOverrun::checkScope(const Token * tok, std::map<unsigned int,CheckBufferOverrun::ArrayInfo,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,CheckBufferOverrun::ArrayInfo> > > arrayInfos) 行 939   C++
    cppcheck-core.dll!CheckBufferOverrun::checkGlobalAndLocalVariable() 行 1242  C++
    cppcheck-core.dll!CheckBufferOverrun::runSimplifiedChecks(const Tokenizer * tokenizer, const Settings * settings, ErrorLogger * errorLogger) 行 79   C++
    cppcheck-core.dll!CppCheck::checkSimplifiedTokens(const Tokenizer & tokenizer) 行 593    C++
    cppcheck-core.dll!CppCheck::checkFile(const std::basic_string<char,std::char_traits<char>,std::allocator<char> > & filename, const std::basic_string<char,std::char_traits<char>,std::allocator<char> > & cfgname, std::basic_istream<char,std::char_traits<char> > & fileStream) 行 430 C++
    cppcheck-core.dll!CppCheck::check(const std::basic_string<char,std::char_traits<char>,std::allocator<char> > & path) 行 83   C++

可以发现:

1) arrayInfo,为我们定义的char a[10]

-       arrayInfo   {mNum={ size=1 } mVarName="a" mElementSize=1 ...}   const CheckBufferOverrun::ArrayInfo &
-       mNum    { size=1 }  std::vector<__int64,std::allocator<__int64> >
        [capacity]  1   int
+       [allocator] allocator   std::_Compressed_pair<std::allocator<__int64>,std::_Vector_val<std::_Simple_types<__int64> >,1>
        [0] 10  __int64
+       [原始视图]  {...}   std::vector<__int64,std::allocator<__int64> >
+       mVarName    "a" std::basic_string<char,std::char_traits<char>,std::allocator<char> >
        mElementSize    1   __int64
        mDeclarationId  1   unsigned int

2) token的层级列表(向后):

-       tok 0x02bfed58 "memcpy" const Token *
+       mTokensFrontBack    0x00afed90 "int" - "}"  TokensFrontBack *
+       mStr    "memcpy"    std::basic_string<char,std::char_traits<char>,std::allocator<char> >
-       mNext   0x02bfedf0 "("  Token *
+       mTokensFrontBack    0x00afed90 "int" - "}"  TokensFrontBack *
+       mStr    "(" std::basic_string<char,std::char_traits<char>,std::allocator<char> >
-       mNext   0x02bfee88 "a"  Token *
+       mTokensFrontBack    0x00afed90 "int" - "}"  TokensFrontBack *
+       mStr    "a" std::basic_string<char,std::char_traits<char>,std::allocator<char> >
-       mNext   0x02e677e8 ","  Token *
+       mTokensFrontBack    0x00afed90 "int" - "}"  TokensFrontBack *
+       mStr    "," std::basic_string<char,std::char_traits<char>,std::allocator<char> >
-       mNext   0x02e679b0 "\"abcdefghij\"" Token *

3)token向前,可以看到这东西基本就是一个双向链表:

-       mPrevious   0x02bfe838 ";"  Token *
+       mTokensFrontBack    0x00afed90 "int" - "}"  TokensFrontBack *
+       mStr    ";" std::basic_string<char,std::char_traits<char>,std::allocator<char> >
+       mNext   0x02bfed58 "memcpy" Token *
-       mPrevious   0x02bfe7a0 "]"  Token *
+       mTokensFrontBack    0x00afed90 "int" - "}"  TokensFrontBack *
+       mStr    "]" std::basic_string<char,std::char_traits<char>,std::allocator<char> >
+       mNext   0x02bfe838 ";"  Token *
-       mPrevious   0x02bfe708 "10" Token *

所以,如果我们不想仅仅依靠正则式写那些简单的规则,就需要自己抽象出一个常见的模式,并根据模式和token来编写规则。token会逐个遍历,所以不用担心跑不到你想要找的函数那里(参考CheckBufferOverrun::checkScope)。

最后需要注意的是CppCheck是按类型来扫描,而不是按代码顺序扫描,所以由于声明全局变量顺序的问题,很有可能你会发现某个靠后的代码先行被提示问题,这都是正常的结果。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

关注码农网公众号

关注我们,获取更多IT资讯^_^


查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

软件的奥秘

软件的奥秘

[美] V. Anton Spraul / 解福祥 / 人们邮电出版社 / 2017-9-1 / 49

软件已经成为人们日常生活与工作中常见的辅助工具,但是对于软件的工作原理,很多人却不是非常了解。 本书对软件的工作原理进行了解析,让读者对常用软件的工作原理有一个大致的了解。内容涉及数据如何加密、密码如何使用和保护、如何创建计算机图像、如何压缩和存储视频、如何搜索数据、程序如何解决同样的问题而不会引发冲突以及如何找出最佳路径等方面。 本书适合从事软件开发工作的专业技术人员,以及对软件工作......一起来看看 《软件的奥秘》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器