`implementation(group = "de.fraunhofer.aisec", name = "cpg-core", version = cpgVersion) implementation(group = "de.fraunhofer.aisec", name = "cpg-language-java", version = cpgVersion) `
分析java项目需要导入针对java的cpg-language-java
大致逻辑:构建翻译配置TranslationConfiguration
,翻译管理TranslationManager
获取配置,通过builder
构建分析器,然后开始分析
TranslationConfiguration
构建参考:de.fraunhofer.aisec.cpg.analysis.AnalysisTest#testNullPointer
`fun testNullPointer() { val config = TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/Array.java")) .defaultPasses() // .registerLanguage<JavaLanguage>() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() NullPointerCheck().run(result) } `
通过cpg官方的测试用例中的测试方法,可以发现,在构建TranslationConfiguration
时传递了三个参数:
进入该方法
可以发现,参数是待分析的源码/源码所在目录,
进入该方法
该方法指定默认的Passes
策略,包含了 类型处理、DFG处理、EOG处理等等(为cpg的完整性提供了保障)
进入该方法
可以看到,其可以指定待分析项目的语言,其支持的语言有这么多~
另外,其还支持自定义语言,大概步骤如下:
要支持一种新的编程语言,你需要执行以下步骤:
提供一种新的语言定义。在这个阶段,你需要考虑该编程语言的特点以及它必须实现哪些
LanguageTraits
(语言特征)。通过这种方式,你可以对该语言进行精细化调整,确保各个分析流程(Passes)能够正确运作。实现一个新的
LanguageFrontend
。当遇到匹配新语言的文件时,这个前端将会被执行。前端的要求已经在上面详细描述过。为了让CPG能够使用新添加的前端,你需要相应地配置翻译过程。这是通过
TranslationConfiguration
来完成的,在此配置中,你需要通过调用其中一个registerLanguage()
方法来注册你的新语言,以便于CPG能够识别并使用针对新语言的前端进行源代码到代码属性图的转换。示例如下:`val config: TranslationConfiguration = TranslationConfiguration .builder() // More configuration .registerLanguage(MyNewLanguage()) // Option 1 .registerLanguage<MyOtherNewLanguage>() // Option 2 .registerLanguage("MyThirdNewLanguage") // Option 3 .build() `
很显然,要调用cpg进行代码分析,肯定要明确其内部有一些什么功能,可以通过什么方法去控制他(参数/配置等),那么TranslationConfiguration
就很重要了~
高能预警!!!高能预警!!!高能预警!!!
location:kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt
TranslationConfiguration
的类结构如下,这么多字段,看看每个字段都是干嘛的
附加符号定义,主要用于C++。这是一个Map<String,String>
映射表,可以用来预先定义一些全局符号或者宏定义,帮助解析器正确解析源代码。
源代码文件列表。用Map<String,List<File>>
存储,cpg默认将key
作为"application
",value
就是源文件List
。
当使用Builder构建时,可以调用sourceLocations
方法,指定源文件列表,该方法将文件列表存储到softwareComponents
表中
通常是待分析项目的根目录。其可以为null
是否启用解析器调试输出。当设为true
时,将在解析过程中生成额外的调试信息。 一般调试时开启。
解析过程发生Error时是否结束。如果为true
,则在遇到任何错误时立即停止解析和翻译;若为false
,即使存在错误,也会尽力继续执行。
是否递归加载头文件到CPG中(针对C++)。如果为true
,解析器不仅会解析头文件
来解析符号和模板,还会将其内容加载到CPG中;否则仅解析但不加载。
头文件目录列表(针对C++)。这是解析器查找头文件的目录集合,确保符号能够正确解析。
头文件白名单(针对C++)。非空情况下,只有指定列表中的头文件会被解析和加入到CPG中,除非它同时出现在黑名单中。支持相对路径。
头文件黑名单(针对C++)。非空情况下,指定的头文件不会被解析,也不会加入CPG节点中,黑名单优先于白名单。
解析过程中一系列的处理步骤。这些Pass
定义了代码翻译或解析过程中要执行的各种操作或步骤。每个Pass
可能包含多个处理类,这些类负责执行特定的任务,如解析、转换或优化代码。
What is a Pass?
Passes get a prebuilt CPG that at least contains the CPG-AST and output a modified graph. Their purpose is to extend the syntactic representation of code with additional nodes and edges to represent the semantics of the program. Passes can be executed in sequence, where the output of the previous pass serves as input of the next pass.
解释:
Passes
接收一个预构建的CPG
,其中至少包含了CPG-AST
(代码属性图抽象语法树),并输出一个经过修改的图。它们的目的是通过添加额外的节点和边来扩展代码的句法表示,从而体现程序的语义。这些Pass
可以按照序列执行,前一个Pass的输出结果作为下一个Pass的输入。
由于Pass
执行时是有顺序的,所以cpg还提供了一些注解来控制Pass
之间的执行顺序
DependsOn(other: KClass<out Pass>, softDependency: Boolean = false)
-- The annotated pass is executed after the other pass(es). IfsoftDependency
is set tofalse
, it automatically registers these passes if they haven't been registered by the user.
ExecuteBefore(other: KClass<out Pass>, ...)
-- The annotated pass is executed before the other pass(es) specified.
ExecuteFirst
-- The annotated pass is executed as the first pass if possible.
ExecuteLast
-- The annotated pass is executed as the last pass if possible.
RequiredFrontend(frontend: KClass<out LanguageFrontend>)
-- The annotated pass is only executed if the frontend has been used.
那么具体有哪些Pass
呢,看Pass
类结构:
替换特定Pass
的映射表。允许用户为特定语言替换默认的Pass
实现,通过Builder.replacePass
方法或ReplacePass
注解实现。
支持的语言列表,包括相应的前端解析器。
CPG节点中是否包含源代码片段。默认true
是否处理源代码中的注解。默认 false
是否禁用解析后的TypeManager内存清理步骤。默认false,测试时才可以打开。
是否启用统一构建模式。默认为false,仅针对C++。
在这种模式下,所有的翻译单元会被合并成单一的一个,从而避免重复处理头文件,降低向图中添加重复节点的情况。
是否使用多线程并发处理不同的语言前端。 默认fasle
如果设为true
,表明源文件的抽象语法树(AST)将并行地进行解析,尽管后续的遍历和增强操作仍然在一个线程中串行执行。这加快了初始解析的速度,同时保证后续图增强算法的正确性。
是否开启Pass并行执行,加速分析速度。默认false
推断配置对象,控制对未知节点的推断策略。
默认如下:
`private var inferenceConfiguration = InferenceConfiguration.Builder().build() `
InferenceConfiguration.Builder
有如下参数:(不显式build
时默认都开启)
在整个系统中开启推断模块
启用对强制转换表达式与调用表达式的智能猜测。仅针对C/C++。
是否推断record声明(类声明)。
是否推断方法声明。
是否推断变量,例如全局变量。
是否将方法调用表达式的DFG边添加到未解析的函数中。(例如:在给定的源代码中没有方法的实现)
编译数据库,可选参数,提供编译信息以辅助解析。 默认为 null
它存储了一个从源文件到其编译时所需包含文件路径的映射关系。目前主要由C++前端[CXXLanguageFrontend
]使用。可以通过[CompilationDatabase.Companion.fromFile
]方法从文件创建一个新的编译数据库。
是否采用启发式方法将源文件中找到的注释与其最近的AST节点匹配,并将注释保存在节点的注释属性中。
默认false
是否将头文件包含关系添加到CPG中(针对C++)。 默认true
当设为true
时,C++前端会在图中建立节点与所需的头文件之间的连接。
针对每个Pass
类型的个性化配置映射表,允许对单个Pass
调整其运行参数或行为。 默认为空
是一个映射表,键是Pass
的类型,值是Pass
的配置对象。这个映射允许针对不同类型Pass
定制特定的运行配置参数。
代码属性图CPG系列文章(白盒/静态代码分析方向):超万字的详细讲解,文章理论与实践相结合,示例代码可拿来即用,通俗易懂,这样的文章你爱了吗!
能够看到这篇文章,就是我们的缘分,坚持输出优质内容是笔者一直在做的事情。若文章对你有帮助,感谢点个免费的 点赞、在看,大家的鼓励是我最大的动力
点个关注,不后悔