使用 Antlr 解析静态代码语法树存在先天缺陷:难以解析跨文件调用关系
解析的粒度调整为:方法、类的定义行
方法内的调用关系调整为仅解析名
Object 的唯一标识方法:path、masterObject、name(包含继承、拓展关系)
TODO: 方法重载问题
- 解析每个方法、类的声明/定义
- 解析获得每个方法、类的声明包含关系(定义链)
- Java 解析能力裁剪,只保留获取声明链的方法
改动后的 Java 解析数据结构:
type JavaTreeShapeListener struct {
AstInfoList AstResType
}
type AstResType struct {
Classes []ClassInfoType
Methods []MethodInfoType
}
type ClassInfoType struct {
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
ClassName string `json:"class_name"`
Extends string `json:"extends"`
}
type MethodInfoType struct {
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
MethodName string `json:"method_name"`
Parameters string `json:"parameters"`
}
存储的信息包含:
- 类
- 类名(包含定义链)
- 起止行号
- 继承的父类
- 方法
- 方法名(包含定义链)
- 起止行号
- 参数
手动实现的方法:
getParamsOfMethod(ctx *javaparser.MethodDeclarationContext)
返回方法的参数
func getParamsOfMethod(ctx *javaparser.MethodDeclarationContext) (params string) {
if ctx.FormalParameters() != nil {
if ctx.FormalParameters().(*javaparser.FormalParametersContext).FormalParameterList() != nil {
for index, item := range ctx.FormalParameters().(*javaparser.FormalParametersContext).FormalParameterList().(*javaparser.FormalParameterListContext).AllFormalParameter() {
params += item.(*javaparser.FormalParameterContext).TypeType().GetText() + " "
params += item.(*javaparser.FormalParameterContext).VariableDeclaratorId().GetText()
if index != len(ctx.FormalParameters().(*javaparser.FormalParametersContext).FormalParameterList().(*javaparser.FormalParameterListContext).AllFormalParameter())-1 {
params += ", "
}
}
}
}
return
}
findJavaDeclarationChain(ctx antlr.ParseTree)
获取对象的定义链
func findJavaDeclarationChain(ctx antlr.ParseTree) (chainName string) {
currentContext := ctx.GetParent()
for {
if _, ok := currentContext.(*javaparser.ClassDeclarationContext); ok {
chainName = currentContext.(*javaparser.ClassDeclarationContext).IDENTIFIER().GetText() + "." + chainName
}
if _, ok := currentContext.(*javaparser.MethodDeclarationContext); ok {
chainName = currentContext.(*javaparser.MethodDeclarationContext).IDENTIFIER().GetText() + "." + chainName
}
currentContext = currentContext.GetParent()
if currentContext == nil {
break
}
}
return
}
- Golang 解析能力裁剪,只保留获取声明链的能力
改动后的 Golang 解析数据结构:
type GoTreeShapeListener struct {
AstInfoList AstResType
}
type AstResType struct {
Classes []ClassInfoType
Methods []MethodInfoType
}
type ClassInfoType struct {
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
ClassName string `json:"class_name"`
Extends string `json:"extends"`
}
type MethodInfoType struct {
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
MethodName string `json:"method_name"`
Parameters string `json:"parameters"`
}
手动实现的方法:
getFunctionAndMethodParams(ctx antlr.ParseTree)
获得方法或函数的参数
func getFunctionAndMethodParams(ctx antlr.ParseTree) (params string) {
if _, ok := ctx.(*golang.FunctionDeclContext); ok {
if ctx.(*golang.FunctionDeclContext).Signature().(*golang.SignatureContext).Parameters() != nil {
for index, item := range ctx.(*golang.FunctionDeclContext).Signature().(*golang.SignatureContext).Parameters().(*golang.ParametersContext).AllParameterDecl() {
params += item.(*golang.ParameterDeclContext).IdentifierList().GetText() + " "
params += item.(*golang.ParameterDeclContext).Type_().GetText()
if index != len(ctx.(*golang.FunctionDeclContext).Signature().(*golang.SignatureContext).Parameters().(*golang.ParametersContext).AllParameterDecl())-1 {
params += ", "
}
}
}
}
if _, ok := ctx.(*golang.MethodDeclContext); ok {
if ctx.(*golang.MethodDeclContext).Signature().(*golang.SignatureContext).Parameters() != nil {
for index, item := range ctx.(*golang.MethodDeclContext).Signature().(*golang.SignatureContext).Parameters().(*golang.ParametersContext).AllParameterDecl() {
params += item.(*golang.ParameterDeclContext).IdentifierList().GetText() + " "
params += item.(*golang.ParameterDeclContext).Type_().GetText()
if index != len(ctx.(*golang.MethodDeclContext).Signature().(*golang.SignatureContext).Parameters().(*golang.ParametersContext).AllParameterDecl())-1 {
params += ", "
}
}
}
}
return
}
getRecvrTypes(ctx *golang.MethodDeclContext)
获得方法所属的结构体类型
func getRecvrTypes(ctx *golang.MethodDeclContext) (types []string) {
temp := ctx.Receiver().(*golang.ReceiverContext).Parameters().(*golang.ParametersContext).AllParameterDecl()
for _, item := range temp {
types = append(types, item.(*golang.ParameterDeclContext).Type_().GetText())
}
return
}
- Kotlin 解析能力裁剪,只保留获取声明链的能力
改动后的 Kotlin 解析数据结构:
type KotlinTreeShapeListener struct {
AstInfoList AstResType
}
type AstResType struct {
Classes []ClassInfoType
Methods []MethodInfoType
}
type ClassInfoType struct {
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
ClassName string `json:"class_name"`
Extends string `json:"extends"`
}
type MethodInfoType struct {
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
MethodName string `json:"method_name"`
Parameters string `json:"parameters"`
}
手动实现的方法:
findKotlinClassExtends(ctx *kotlin.ClassDeclarationContext)
获得类的继承类名
func findKotlinClassExtends(ctx *kotlin.ClassDeclarationContext) (extends string) {
if ctx.DelegationSpecifiers() != nil {
tempCtx := ctx.DelegationSpecifiers().(*kotlin.DelegationSpecifiersContext)
if tempCtx.AllDelegationSpecifier() != nil {
if tempCtx.DelegationSpecifier(0).(*kotlin.DelegationSpecifierContext).ConstructorInvocation() != nil {
tempCtx2 := tempCtx.DelegationSpecifier(0).(*kotlin.DelegationSpecifierContext).ConstructorInvocation()
extends = tempCtx2.(*kotlin.ConstructorInvocationContext).UserType().GetText()
}
}
}
return
}
findKotlinDeclarationChain(ctx antlr.ParseTree)
获得定义链
func findKotlinDeclarationChain(ctx antlr.ParseTree) (chainName string) {
currentContext := ctx.GetParent()
for {
if _, ok := currentContext.(*kotlin.ClassDeclarationContext); ok {
chainName = currentContext.(*kotlin.ClassDeclarationContext).SimpleIdentifier().GetText() + "." + chainName
}
if _, ok := currentContext.(*kotlin.FunctionDeclarationContext); ok {
chainName = currentContext.(*kotlin.FunctionDeclarationContext).Identifier().GetText() + "." + chainName
}
currentContext = currentContext.GetParent()
if currentContext == nil {
break
}
}
return
}
- Cpp 解析能力裁剪,只保留获取声明链的能力
- 不解析复杂环,只解析简单定义/声明
- 方法名的解析手动实现 更改后的数据结构:
type CppTreeShapeListener struct {
AstInfoList AstResType
}
type AstResType struct {
Classes []ClassInfoType
Methods []MethodInfoType
}
type ClassInfoType struct {
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
ClassName string `json:"class_name"`
Extends string `json:"extends"`
}
type MethodInfoType struct {
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
MethodName string `json:"method_name"`
Parameters string `json:"parameters"`
}
Antlr 语法树字段记录:
对于一个 Js 函数:
- 类型:
FunctionDeclarationContext
- 子字段
- "function"
- 函数名
IdentifierContext
- "("
- 参数
FormalParameterListContext
- ")"
- 函数体
FunctionBodyContext
对于一个 Js 类:
- 类型:
ClassDeclarationContext
- 子字段
- "Class"
- 类名
IdentifierContext
- "("
- 当含有 extends 字段时,有
ClassTailContext
SingleExpressionContext
/ArgumentsExpressionContext
,逐层遍历取SingleExpression()
可以得到IdentifierExpressionContext
作为准确的 Extends 名- 类内部字段
手动实现的方法:
-
getExtendIdentifier(ctx *javascript.ClassDeclarationContext)(extend string)
返回类定义中通过 Extends 继承的类名
func getExtendIdentifier(ctx *javascript.ClassDeclarationContext) (extend string) {
if ctx.ClassTail() != nil {
tempCtx := ctx.ClassTail().(*javascript.ClassTailContext).SingleExpression()
for {
if tempCtx == nil {
return
}
if _, ok := tempCtx.(*javascript.ArgumentsExpressionContext); ok {
tempCtx = tempCtx.(*javascript.ArgumentsExpressionContext).SingleExpression()
continue
}
if _, ok := tempCtx.(*javascript.IdentifierExpressionContext); ok {
extend = tempCtx.(*javascript.IdentifierExpressionContext).GetText()
return
}
return
}
}
return
}
-
findJsDeclChain(ctx antlr.ParseTree) (chain string)
返回类/函数定义的定义链关系(所属类、方法等)
func findJsDeclChain(ctx antlr.ParseTree) (chain string) {
tempCtx := ctx.GetParent()
for {
if _, ok := tempCtx.(*javascript.ClassDeclarationContext); ok {
chain = tempCtx.(*javascript.ClassDeclarationContext).Identifier().GetText() + "." + chain
}
if _, ok := tempCtx.(*javascript.FunctionDeclarationContext); ok {
chain = tempCtx.(*javascript.FunctionDeclarationContext).Identifier().GetText() + "." + chain
}
tempCtx = tempCtx.GetParent()
if tempCtx == nil {
return
}
}
}
更改后的数据结构:
type JavascriptTreeShapeListener struct {
AstInfoList AstResType
}
type AstResType struct {
Classes []ClassInfoType
Methods []MethodInfoType
}
type ClassInfoType struct {
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
ClassName string `json:"class_name"`
Extends string `json:"extends"`
}
type MethodInfoType struct {
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
MethodName string `json:"method_name"`
Parameters string `json:"parameters"`
}
我们可以看到,经过改造后的几种语言的解析数据结构已经实际统一,我们可以对其进行改造, 共用同一个类型。
改造嵌套类型,函数antlrAnalysis(diffText string, langMode string) (result AstResType)
直接返回AstResType
类型的结构体:
type AstResType struct {
Classes []ClassInfoType
Methods []MethodInfoType
}
antlrAnalysis
的修改直接影响到addObjectFromChangeLineNumber()
、findChangedMethod()
的传入参数类型,需要修改其逻辑/数据流:
type ObjectInfoType struct {
CommitHash string `json:"hash"`
Id string `json:"object_name"`
OldId string `json:"old_object_name"`
FilePath string `json:"path"`
OldLineCount int `json:"old_line_count"`
NewLineCount int `json:"new_line_count"`
ChangedOldLineCount int `json:"changed_old_line_count"`
ChangedNewLineCount int `json:"changed_new_line_count"`
+ Type string `json:"type"`
- Calling []string
}
新的算法模式将重点放置在定义链的解析,对堆栈内容的定义位置做出判断
- 减少的数据:
- Calling 信息不再支持解析
- MasterObject 不再支持解析
- 新增的数据:
- 完整的方法/类定义链
- 起止行号的支持
- 准确可靠的行数变动情况
- 不再依赖调用链的解析
调用链信息解析困难,且难以提高解析准确性
- 唯一标识对象的模式变更
仅使用定义链+文件目录定位的模式无法准确定位对象,使用定义链条+文件目录+起始行号进行定位
- 置信度计算依赖变更
置信度的计算不再能依赖调用链解析,置信度的维度缩减为依靠时间、行数作为置信度判据
- 不必花费极大的代价实现调用链的准确解析
- 不必等待单次解析的所有 Object 接收完毕再启动算法
可以在每一个 Object 上传同时进行计算,实时更新置信度
- 插件解析的数据流可以得到简化
插件解析的目标数据大幅度减少,可以缩减数据流中的重复流转信息
- 插件 Antlr 语法解析的效率优化
Antlr 解析语法树只需要获得定义信息,可以省去复杂大量的链式分析逻辑