ClangCheckerExpandPlug
-
添加检查器的实现代码(检查用户是否调用了
main
函数)在
clang/lib/Analysis/plugins
目录中新建CheckerMainCall
目录。并在该目录下新建MainCallChecker.cpp
文件,内容如下:#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" using namespace clang; using namespace ento; namespace { class MainCallChecker : public Checker<check::PreCall> { public: void checkPreCall(const CallEvent &Call, CheckerContext &Ctx) const; private: mutable std::unique_ptr<BugType> BT; }; } // anonymous namespace void MainCallChecker::checkPreCall(const CallEvent &Call, CheckerContext &Ctx) const { if (const IdentifierInfo *II = Call.getCalleeIdentifier()) { if (II->isStr("main")) { if (!BT) { BT.reset(new BugType(this, "Call to main", "Example checker")); } ExplodedNode *Node = Ctx.generateErrorNode(); auto Report = std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), Node); Ctx.emitReport(std::move(Report)); } } } // Register plugin! extern "C" void clang_registerCheckers(CheckerRegistry ®istry) { registry.addChecker<MainCallChecker>( "plugin.MainCallChecker", "Disallows calls to functions called main", ""); } extern "C" const char clang_analyzerAPIVersionString[] = CLANG_ANALYZER_API_VERSION_STRING;
注:
- 新建的目录名称不必是
CheckerMainCall
,也可以是其他名称。 - 新建的源文件名称不必是
MainCallChecker.cpp
,也可以是其他名称。 - 以插件方式扩展 Clang 静态分析器时,不需要修改
Checkers.td
文件。 - 函数
clang_registerCheckers()
用于注册以插件方式提供的检查器。 registry.addChecker();
中的XXX
必须与检查器的类名保持一致。其第一个参数表示检查器的全称(这里是"plugin.MainCallChecker"
),第二个参数表示检查器的描述(这里是"Disallows calls to functions called main"
),第三个参数表示检查器文档的URI
地址(这里是""
),第四个参数表示检查器是否对用户可见(默认值为false
,表示对用户可见)。- 全局变量
clang_analyzerAPIVersionString
表示生成该插件时所使用的 Clang API 版本。如果与使用该插件的 Clang API 版本不兼容,那么检查器会注册失败。
- 新建的目录名称不必是
-
添加 MainCallCheckerPlugin.exports
在目录
clang/lib/Analysis/plugins/CheckerMainCall/
中新建MainCallCheckerPlugin.exports
文件。其内容如下:clang_registerCheckers clang_analyzerAPIVersionString
注:
- 新建的文件名称不必是
MainCallCheckerPlugin.exports
,也可以是其他名称。 - 这里的检查器注册函数名称
clang_registerCheckers
必须与MainCallChecker.cpp
中的保持一致。 - 这里的全局变量名称
clang_analyzerAPIVersionString
必须与MainCallChecker.cpp
中的保持一致。
- 新建的文件名称不必是
-
添加 CMakeLists.txt
在目录
clang/lib/Analysis/plugins/CheckerMainCall/
中新建CMakeLists.txt
文件。其内容如下:set(LLVM_LINK_COMPONENTS Support ) set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/MainCallCheckerPlugin.exports) add_llvm_library(MainCallCheckerPlugin MODULE BUILDTREE_ONLY MainCallChecker.cpp PLUGIN_TOOL clang) clang_target_link_libraries(MainCallCheckerPlugin PRIVATE clangAnalysis clangAST clangStaticAnalyzerCore clangStaticAnalyzerFrontend )
注:
- 这里的
MainCallCheckerPlugin
表示要生成的插件的名称为libMainCallCheckerPlugin.so
。 - 这里的
MainCallCheckerPlugin.exports
即为上文中新添加的文件名称。
- 这里的
-
修改插件目录中的 CMakeLists.txt
在
clang/lib/Analysis/plugins/CMakeLists.txt
文件中添加如下内容:add_subdirectory(CheckerMainCall)
注: 这里的
CheckerMainCall
即为上文中新建的目录名称。添加后的内容为:
if(CLANG_ENABLE_STATIC_ANALYZER AND LLVM_ENABLE_PLUGINS) add_subdirectory(SampleAnalyzer) add_subdirectory(CheckerDependencyHandling) add_subdirectory(CheckerOptionHandling) add_subdirectory(CheckerMainCall) endif()
需要注意的是, 要生成插件,需要在编译 Clang 时打开
CLANG_ENABLE_STATIC_ANALYZER
和LLVM_ENABLE_PLUGINS
选项。默认情况下,这两个选项是打开的。 -
编译插件
省略
-
查看所生成插件中的检查器
clang -cc1 -load /home/wxs/clang_build/lib/MainCallCheckerPlugin.so -analyzer-checker-help | grep MainCall
output
root@shjx-wxs:/home/wxs/clang_build/bin# clang -cc1 -load /home/wxs/clang_build/lib/MainCallCheckerPlugin.so -analyzer-checker-help | grep MainCall plugin.MainCallChecker Disallows calls to functions called main
从上面的输出可以看出:
plugin.MainCallChecker
,即我们在插件中自定义的检查器的完整名称。Disallows calls to functions called main
,即我们在插件中为自定义的检查器所添加的描述。
需要注意的是, 插件的路径(这里是
/home/wxs/clang_build/lib/MainCallCheckerPlugin.so
)可以是相对路径,也可以是绝对路径。 -
编写测试用例
Example_Test.c
typedef int (*main_t)(int, char **); int main(int argc, char **argv) { main_t foo = main; int exit_code = foo(argc, argv); // actually calls main()! return exit_code; }
上述测试程序存在这样的错误:通过函数指针变量
foo
调用main
函数。 -
运行插件
以插件方式运行检查器,命令如下:
clang -cc1 -load /home/wxs/clang_build/lib/MainCallCheckerPlugin.so -analyze -analyzer-checker=plugin.MainCallChecker Example_Test.c
output
root@shjx-wxs:/home/wxs/demo/clang-checker# clang -cc1 -load /home/wxs/clang_build/lib/MainCallCheckerPlugin.so -analyze -analyzer-checker=plugin.MainCallChecker Example_Test.c Example_Test.c:4:19: warning: Call to main [plugin.MainCallChecker] int exit_code = foo(argc, argv); // actually calls main()! ^~~~~~~~~~~~~~~ 1 warning generated.