2018年11月

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

CppCheck是一个静态语法扫描器,官方介绍:

Static analysis of C/C++ code. Checks for: memory leaks, mismatching allocation-deallocation, buffer overrun, and many more. The goal is 0% false positives. See http://cppcheck.sourceforge.net for more information.

CppCheck是一个开源工程,要对其进行修改,最好还是理清楚它的工作流程。首先,让我们对一个常见的问题进行检查,代码如下:

int a[10];
a[10] = 0;

对这个越界代码进行扫描,可以发现如下调用栈处报警:

>   cppcheck-core.dll!CheckBufferOverrun::arrayIndexOutOfBoundsError(const Token * tok, const CheckBufferOverrun::ArrayInfo & arrayInfo, const std::vector<ValueFlow::Value,std::allocator<ValueFlow::Value> > & index) 行 142   C++
    cppcheck-core.dll!CheckBufferOverrun::valueFlowCheckArrayIndex(const Token * const tok, const CheckBufferOverrun::ArrayInfo & arrayInfo) 行 878  C++
    cppcheck-core.dll!CheckBufferOverrun::bufferOverrun() 行 1593    C++
    cppcheck-core.dll!CheckBufferOverrun::runChecks(const Tokenizer * tokenizer, const Settings * settings, ErrorLogger * errorLogger) 行 91 C++
    cppcheck-core.dll!CppCheck::checkNormalTokens(const Tokenizer & tokenizer) 行 563    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) 行 416 C++
    cppcheck-core.dll!CppCheck::check(const std::basic_string<char,std::char_traits<char>,std::allocator<char> > & path) 行 83   C++
    cppcheck.exe!CppCheckExecutor::check_internal(CppCheck & cppcheck, int __formal, const char * const * argv) 行 871   C++
    cppcheck.exe!CppCheckExecutor::check(int argc, const char * const * argv) 行 198 C++
    cppcheck.exe!main(int argc, char * * argv) 行 95 C++

简单分析一下,基本是这几行起了作用:

    cppcheck-core.dll!CheckBufferOverrun::runChecks(const Tokenizer * tokenizer, const Settings * settings, ErrorLogger * errorLogger) 行 91 C++
    cppcheck-core.dll!CppCheck::checkNormalTokens(const Tokenizer & tokenizer) 行 563    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) 行 416 C++

注意第一行:

void CppCheck::checkNormalTokens(const Tokenizer &tokenizer)
{
    // call all "runChecks" in all registered Check classes
    for (std::list<Check *>::const_iterator it = Check::instances().begin(); it != Check::instances().end(); ++it) {
        if (mSettings.terminated())
            return;

        if (tokenizer.isMaxTime())
            return;

        Timer timerRunChecks((*it)->name() + "::runChecks", mSettings.showtime, &S_timerResults);
        (*it)->runChecks(&tokenizer, &mSettings, this);
    }

也就是说,it从Check::instances()中遍历所有项目。
Check::instances中有:

    名称  值   类型
▶   [0] 0x01557438 {cppcheck-core.dll!Check64BitPortability instance} {...} Check * {Check64BitPortability}
▶   [1] 0x01557494 {cppcheck-core.dll!CheckAssert instance} {...}   Check * {CheckAssert}
▶   [2] 0x015574ec {cppcheck-core.dll!CheckAutoVariables instance} {...}    Check * {CheckAutoVariables}
▶   [3] 0x01557550 {cppcheck-core.dll!CheckBool instance} {...} Check * {CheckBool}
▶   [4] 0x015575b8 {cppcheck-core.dll!CheckBoost instance} {...}    Check * {CheckBoost}
▶   [5] 0x01557614 {cppcheck-core.dll!CheckBufferOverrun instance} {...}    Check * {CheckBufferOverrun}
▶   [6] 0x01557774 {cppcheck-core.dll!CheckFunctions instance} {...}    Check * {CheckFunctions}
▶   [7] 0x0155768c {cppcheck-core.dll!CheckClass instance} {mSymbolDatabase=0x00000000 <NULL> } Check * {CheckClass}
▶   [8] 0x0155771c {cppcheck-core.dll!CheckCondition instance} {...}    Check * {CheckCondition}
▶   [9] 0x01557864 {cppcheck-core.dll!CheckExceptionSafety instance} {...}  Check * {CheckExceptionSafety}
▶   [10]    0x015578bc {cppcheck-core.dll!CheckIO instance} {...}   Check * {CheckIO}
▶   [11]    0x01557974 {cppcheck-core.dll!CheckLeakAutoVar instance} {...}  Check * {CheckLeakAutoVar}
▶   [12]    0x01557a7c {cppcheck-core.dll!CheckMemoryLeakNoVar instance4} {...} Check * {CheckMemoryLeakNoVar}
▶   [13]    0x01557a0c {cppcheck-core.dll!CheckMemoryLeakInClass instance2} {...}   Check * {CheckMemoryLeakInClass}
▶   [14]    0x015579d4 {cppcheck-core.dll!CheckMemoryLeakInFunction instance1} {...}    Check * {CheckMemoryLeakInFunction}
▶   [15]    0x01557a44 {cppcheck-core.dll!CheckMemoryLeakStructMember instance3} {...}  Check * {CheckMemoryLeakStructMember}
▶   [16]    0x01557b34 {cppcheck-core.dll!CheckNullPointer instance} {...}  Check * {CheckNullPointer}
▶   [17]    0x01557bb0 {cppcheck-core.dll!CheckOther instance} {...}    Check * {CheckOther}
▶   [18]    0x01557d20 {cppcheck-core.dll!CheckStl instance} {...}  Check * {CheckStl}
▶   [19]    0x01557cbc {cppcheck-core.dll!CheckSizeof instance} {...}   Check * {CheckSizeof}
▶   [20]    0x015577ec {cppcheck-core.dll!CheckString instance} {...}   Check * {CheckString}
▶   [21]    0x01557e98 {cppcheck-core.dll!CheckType instance} {...} Check * {CheckType}
▶   [22]    0x01557f00 {cppcheck-core.dll!CheckUninitVar instance} {...}    Check * {CheckUninitVar}
▶   [23]    0x01557f70 {cppcheck-core.dll!CheckUnusedFunctions CheckUnusedFunctions::instance} {mFunctions={ size=0 } ...}  Check * {CheckUnusedFunctions}
▶   [24]    0x01557ff8 {cppcheck-core.dll!CheckUnusedVar instance} {mIsRecordTypeWithoutSideEffectsMap={ size=0 } ...}  Check * {CheckUnusedVar}
▶   [25]    0x01557c64 {cppcheck-core.dll!CheckPostfixOperator instance} {...}  Check * {CheckPostfixOperator}
▶   [26]    0x01558070 {cppcheck-core.dll!CheckVaarg instance} {...}    Check * {CheckVaarg}

鉴于代码表明_instalces这是一个static变量,所以必然有代码调用了Check::instances().XXXX向它推送代码。

std::list<Check *> &Check::instances()
{
#ifdef __SVR4
    // Under Solaris, destructors are called in wrong order which causes a segmentation fault.
    // This fix ensures pointer remains valid and reachable until program terminates.
    static std::list<Check *> *_instances= new std::list<Check *>;
    return *_instances;
#else
    static std::list<Check *> _instances;
    return _instances;
#endif
}

查找所有的代码以后,只有构造函数有相关代码:

Check::Check(const std::string &aname)
    : mTokenizer(nullptr), mSettings(nullptr), mErrorLogger(nullptr), mName(aname)
{
    for (std::list<Check*>::iterator i = instances().begin(); i != instances().end(); ++i) {
        if ((*i)->name() > aname) {
            instances().insert(i, this);
            return;
        }
    }
    instances().push_back(this);
}

下断点,重新执行。

    cppcheck-core.dll!Check::Check(const std::basic_string<char,std::char_traits<char>,std::allocator<char> > & aname) 行 30 C++
    cppcheck-core.dll!Check64BitPortability::Check64BitPortability() 行 46   C++
>   cppcheck-core.dll!`anonymous namespace'::`dynamic initializer for 'instance''() 行 41    C++

可以看到这样的调用栈,再往下就是程序的初始化代码了,一层层看。

1:最上层可以看到一个类似全局变量的代码

// Register this check class (by creating a static instance of it)
namespace {
    Check64BitPortability instance;
}

2:可以看到检测的类都是公开继承Check的

class CPPCHECKLIB Check64BitPortability : public Check {
public:
    /** This constructor is used when registering the Check64BitPortability */
    Check64BitPortability() : Check(myName()) {
    }

3:随后代码走到Check中,该类被加入。

再保留断点继续执行,可以发现所有的Check都是这样加入的,因为这些Check类都是全局变量,所以程序一启动就会自动添加进去。等他们执行完了才会进入main。