searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

通过AST分析代码级字段血缘关系

2024-06-25 09:47:20
48
0

背景

在一些特定业务场景,比如视频播控平台,会通过多种字段条件组合控制业务逻辑,比如当视频品类字段值为A时,所属地域字段为B时,修改播放状态字段C为xx,实际情况会比这复杂很多。这种场景如果能通过技术手段分析出字段血缘关系,对于业务是非常有益的,可以在修改某些字段提示出会影响其它哪些字段,避免线上运营事故。

AST介绍

AST(Abstract Syntax Tree)抽象语法树,就是使用树状结构来表示源代码的抽象语法结构,由于它是抽象的,所以并不是源代码的每一个元素都能在AST找到对应的节点,我们平时使用的goimport、gomock等工具也都用到了AST。

分析方法

以下仅针对GO语言做一种可行性探索,因为GO语言生态比较丰富,实现起来比较简单,其它语言可以参考类似步骤。

  1. 利用golang提供分析AST的工具生成AST树
    工具代码如下:
fset := token.NewFileSet()

mode := packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports |

		packages.NeedDeps | packages.NeedExportFile | packages.NeedTypes |

		packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedTypesSizes |

		packages.NeedModule | packages.NeedEmbedFiles | packages.NeedEmbedPatterns


cfg := &packages.Config{Fset: fset, Mode: mode, Dir: projectPath}

pkgs, err := packages.Load(cfg, "./...")

if err != nil {

	return

}
  1. 利用ast.Walk深度优先遍历AST树,找到程序写入字段的地方(写点)

这里前提是我们知道服务中会调用哪些方法写,比如写方法的请求包体为writeReq

func (visitor \*ReadVisitor) Visit(nodeI ast.Node) ast.Visitor {

	switch node := nodeI.(type) {

	case \*ast.CallExpr:

		switch fun := node.Fun.(type) {

		case \*ast.SelectorExpr:

			if fun.Sel.Name == "writeReq" {

				visitor.Calls = append(visitor.Calls, node)

			}

		}

	}

	return visitor

}

从写点出发找出所有途径的字段,下图是经过分析后得出来的一棵AST树(部分):

image.jpg

由上面的结构可以看出来涉及的字段(可以和我们自己的元数据去匹配)。此时我们只需要找出字段逻辑写入的是哪个字段,就可以得出是哪些字段影响的。最终可以建成如图的关系
image (1).jpg

总结

在此次分析过程中主要是对AST树的不同Node进行不同情况的处理

以下是常见的AST节点类型:

  1. ast.File:表示整个源文件,包含了所有的代码和注释。
  2. ast.Package:表示一个包,包含了该包的名称和所有的文件。
  3. ast.ImportSpec:表示一个import语句中的一个导入路径。
  4. ast.Ident:表示一个标识符(变量、函数等),包含了标识符的名称。
  5. ast.Expr:表示一个表达式,包含了所有的表达式类型,如binary expression、unary expression、function call等。
  6. ast.Stmt:表示一个语句,包含了所有的语句类型,如if语句、for语句、switch语句等。
  7. ast.Decl:表示一个声明,包含了所有的声明类型,如变量声明、函数声明等。
  8. ast.FuncType:表示一个函数类型,包含了函数的参数和返回值。
  9. ast.FuncDecl:表示一个函数声明,包含了函数的名称、参数、返回值和函数体。
  10. ast.ValueSpec:表示一个常量或变量的声明,包含了该常量或变量的名称和值。
  11. ast.TypeSpec:表示一个类型的声明,包含了该类型的名称和类型。
  12. ast.StructType:表示一个结构体类型,包含了结构体的字段和标签。
  13. ast.Field:表示一个结构体字段,包含了字段的名称和类型。
  14. ast.Comment:表示一个注释。
  15. ast.CommentGroup:表示一组注释。
  16. ast.GenDecl:代表一般的声明节点。它可以表示包级别的变量、类型或常量声明,也可以表示函数或方法的参数或返回类型声明。其中包括了import、const、var、type等关键字所声明的内容
    对已有代码进行AST分析,能够获取程序中所涉及字段之间的关系,也能够较为准确的得出。但从上图的AST树可以看出,有先后引用关系的两个变量,并不是在一条链上,所以不能通过简单的遍历就能得出字段间的关系。最终如果需要得出较为精确的,不单单需要分析单个类型的节点,还需要分析节点之间的关系,这是需要一定的工作量。

此方法仍然存在一定的局限性
1、 目前AST能对单个服务分析出哪些字段会对写入字段有影响,但是这有一部分的人工成本在,需要对现有的服务进行部分改造,统一协议才能做成自动化

2、 AST只能对静态的代码进行分析,如果程序中利用了动态配置、反射、远程调用等,则无法分析出准确的字段血缘;

后续会在基础上进一步探讨其它方式搜集字段血缘的可行性。

可以参考的项目:

  1. go-critic:可用于代码检查
  2. go-callvis:可用于查看函数调用链
0条评论
0 / 1000
唐****胜
4文章数
0粉丝数
唐****胜
4 文章 | 0 粉丝
原创

通过AST分析代码级字段血缘关系

2024-06-25 09:47:20
48
0

背景

在一些特定业务场景,比如视频播控平台,会通过多种字段条件组合控制业务逻辑,比如当视频品类字段值为A时,所属地域字段为B时,修改播放状态字段C为xx,实际情况会比这复杂很多。这种场景如果能通过技术手段分析出字段血缘关系,对于业务是非常有益的,可以在修改某些字段提示出会影响其它哪些字段,避免线上运营事故。

AST介绍

AST(Abstract Syntax Tree)抽象语法树,就是使用树状结构来表示源代码的抽象语法结构,由于它是抽象的,所以并不是源代码的每一个元素都能在AST找到对应的节点,我们平时使用的goimport、gomock等工具也都用到了AST。

分析方法

以下仅针对GO语言做一种可行性探索,因为GO语言生态比较丰富,实现起来比较简单,其它语言可以参考类似步骤。

  1. 利用golang提供分析AST的工具生成AST树
    工具代码如下:
fset := token.NewFileSet()

mode := packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports |

		packages.NeedDeps | packages.NeedExportFile | packages.NeedTypes |

		packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedTypesSizes |

		packages.NeedModule | packages.NeedEmbedFiles | packages.NeedEmbedPatterns


cfg := &packages.Config{Fset: fset, Mode: mode, Dir: projectPath}

pkgs, err := packages.Load(cfg, "./...")

if err != nil {

	return

}
  1. 利用ast.Walk深度优先遍历AST树,找到程序写入字段的地方(写点)

这里前提是我们知道服务中会调用哪些方法写,比如写方法的请求包体为writeReq

func (visitor \*ReadVisitor) Visit(nodeI ast.Node) ast.Visitor {

	switch node := nodeI.(type) {

	case \*ast.CallExpr:

		switch fun := node.Fun.(type) {

		case \*ast.SelectorExpr:

			if fun.Sel.Name == "writeReq" {

				visitor.Calls = append(visitor.Calls, node)

			}

		}

	}

	return visitor

}

从写点出发找出所有途径的字段,下图是经过分析后得出来的一棵AST树(部分):

image.jpg

由上面的结构可以看出来涉及的字段(可以和我们自己的元数据去匹配)。此时我们只需要找出字段逻辑写入的是哪个字段,就可以得出是哪些字段影响的。最终可以建成如图的关系
image (1).jpg

总结

在此次分析过程中主要是对AST树的不同Node进行不同情况的处理

以下是常见的AST节点类型:

  1. ast.File:表示整个源文件,包含了所有的代码和注释。
  2. ast.Package:表示一个包,包含了该包的名称和所有的文件。
  3. ast.ImportSpec:表示一个import语句中的一个导入路径。
  4. ast.Ident:表示一个标识符(变量、函数等),包含了标识符的名称。
  5. ast.Expr:表示一个表达式,包含了所有的表达式类型,如binary expression、unary expression、function call等。
  6. ast.Stmt:表示一个语句,包含了所有的语句类型,如if语句、for语句、switch语句等。
  7. ast.Decl:表示一个声明,包含了所有的声明类型,如变量声明、函数声明等。
  8. ast.FuncType:表示一个函数类型,包含了函数的参数和返回值。
  9. ast.FuncDecl:表示一个函数声明,包含了函数的名称、参数、返回值和函数体。
  10. ast.ValueSpec:表示一个常量或变量的声明,包含了该常量或变量的名称和值。
  11. ast.TypeSpec:表示一个类型的声明,包含了该类型的名称和类型。
  12. ast.StructType:表示一个结构体类型,包含了结构体的字段和标签。
  13. ast.Field:表示一个结构体字段,包含了字段的名称和类型。
  14. ast.Comment:表示一个注释。
  15. ast.CommentGroup:表示一组注释。
  16. ast.GenDecl:代表一般的声明节点。它可以表示包级别的变量、类型或常量声明,也可以表示函数或方法的参数或返回类型声明。其中包括了import、const、var、type等关键字所声明的内容
    对已有代码进行AST分析,能够获取程序中所涉及字段之间的关系,也能够较为准确的得出。但从上图的AST树可以看出,有先后引用关系的两个变量,并不是在一条链上,所以不能通过简单的遍历就能得出字段间的关系。最终如果需要得出较为精确的,不单单需要分析单个类型的节点,还需要分析节点之间的关系,这是需要一定的工作量。

此方法仍然存在一定的局限性
1、 目前AST能对单个服务分析出哪些字段会对写入字段有影响,但是这有一部分的人工成本在,需要对现有的服务进行部分改造,统一协议才能做成自动化

2、 AST只能对静态的代码进行分析,如果程序中利用了动态配置、反射、远程调用等,则无法分析出准确的字段血缘;

后续会在基础上进一步探讨其它方式搜集字段血缘的可行性。

可以参考的项目:

  1. go-critic:可用于代码检查
  2. go-callvis:可用于查看函数调用链
文章来自个人专栏
微服务/可观测
4 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0