英文:
Examining fields of a struct by reference (via static analysis)
问题
我正在尝试编写一个解析器来检查引用结构体的字段的 Golang 代码。例如,给定以下代码:
type Hello struct {
id int64
}
func Test(ref Hello) {}
我希望能够静态分析这段代码,并从 Test 函数的参数中检查 Hello
结构体的字段。
我目前正在使用 analysis 包。我知道如何在 AST 中检查结构体的定义本身,以及如何解析函数的参数类型。但是,是否有一种方法可以从引用中解析结构体呢?如果结构体在不同的文件中定义呢?
英文:
I'm trying to write a parser for golang code to examine the fields of a referenced struct. For example, given:
type Hello struct {
id int64
}
func Test(ref Hello) {}
I would like to be able to statically analyze this code and go from the args of Test and inspect Hello
's fields.
I'm currently using the analysis package. I know how to inspect the struct definition itself in the ast, and also how to parse the function's args for its types. But is there a way to go from reference to parsing the struct? What if the struct is defined in a different file?
答案1
得分: 2
如果您正在进行静态分析,并且希望更好地了解go/ast
、go/types
等包是如何协同工作的,那么您绝对应该查看Alan Donovan的go types文档。
您可以使用golang.org/x/tools/go/packages
包来获取语法树和类型信息。可能有更好、更简单的方法来实现相同的功能,但这是我熟悉的方法。
要获取Hello
的go/types
表示,您可以执行以下操作:
func main() {
cfg := new(packages.Config)
cfg.Mode = packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo
cfg.Fset = token.NewFileSet()
// “.”表示当前目录。
// 您应该将其替换为与要分析的包匹配的模式。
pkgs, err := packages.Load(cfg, ".")
if err != nil {
panic(err)
}
for _, pkg := range pkgs {
// 遍历包中的文件列表。
for _, syn := range pkg.Syntax {
// 遍历文件中的顶级声明。
for _, dec := range syn.Decls {
// 查找您的Help函数的func声明。
fd, ok := dec.(*ast.FuncDecl)
if !ok || fd.Name.Name != "Test" {
continue
}
// 获取表示参数类型的标识符的表达式节点,即Hello。
p := fd.Type.Params.List[0].Type
// 注意:如果类型不是命名的包局部类型,例如指针、切片或导入的类型,
// 那么您需要“深入挖掘”才能获得*ast.Ident。
id, ok := p.(*ast.Ident)
if !ok {
continue
}
// 使用packages.NeedTypesInfo模式设置后,
// 包还将包括包的语法树的完整类型检查结果。
//
// TypeInfo.Types字段将ast表达式映射到其类型,
// 这使您可以使用标识符获取类型信息。
typ := pkg.TypesInfo.Types[id]
named := typ.Type.(*types.Named)
fmt.Println(named) // Hello的*types.Named
fmt.Println(named.Underlying().(*types.Struct)) // Hello的*types.Struct
}
}
}
}
要获取Hello
类型定义的go/ast
表示,您可以执行以下操作:
func main() {
// 您需要重复上面的步骤
// 来加载包并找到*types.Named实例,
// 该实例将用于确定类型定义ast的位置。
pos := named.Obj().Pos() // 类型名称的源位置
for _, pkg := range pkgs {
// 遍历包中的文件。
for _, syn := range pkg.Syntax {
// 使用位置确定类型是否在此文件中声明,
// 如果不是,则继续下一个文件。
if syn.Pos() >= pos || pos >= syn.End() {
continue
}
// 遍历文件中的顶级声明。
for _, dec := range syn.Decls {
// 如果声明不是类型声明,则继续下一个。
gd, ok := dec.(*ast.GenDecl)
if !ok || gd.Tok != token.TYPE {
continue
}
// 遍历声明中的规范。
for _, spec := range gd.Specs {
// 查找名称与*types.Named实例名称匹配的类型规范。
ts, ok := spec.(*ast.TypeSpec)
if !ok || ts.Name.Name != named.Obj().Name() {
continue
}
fmt.Println(ts) // Hello的*ast.TypeSpec
fmt.Println(ts.Type.(*ast.StructType)) // Hello的*ast.StructType
}
}
}
}
}
英文:
If you're doing static analysis and you'd like to better understand how the packages go/ast
, go/types
, etc. work together then you should definitely check out Alan Donovan's go types document.
You can use the golang.org/x/tools/go/packages
package to get the syntax tree and the type info. There may be better, less involved, approaches to achieve the same but this one's the one I'm familiar with.
To get the go/types
representation of Hello
you can do the following:
func main() {
cfg := new(packages.Config)
cfg.Mode = packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo
cfg.Fset = token.NewFileSet()
// "." specifies the current directory.
// You should replace it with a pattern that
// will match the package you want to analyse.
pkgs, err := packages.Load(cfg, ".")
if err != nil {
panic(err)
}
for _, pkg := range pkgs {
// Loop over the list of files in the package.
for _, syn := range pkg.Syntax {
// Loop over the top-level declarations in the file.
for _, dec := range syn.Decls {
// Look for the func declaration
// of your Help function.
fd, ok := dec.(*ast.FuncDecl)
if !ok || fd.Name.Name != "Test" {
continue
}
// Get the expression node that
// represents the identifier of
// the parameter's type i.e. Hello.
p := fd.Type.Params.List[0].Type
// NOTE: if the type is not a named
// package-local type, e.g. a pointer,
// a slice, or an imported type, then
// you'll have have to "dig deeper"
// to get to the *ast.Ident.
id, ok := p.(*ast.Ident)
if !ok {
continue
}
// With the packages.NeedTypesInfo mode set
// the package will also include the result
// of the complete type-check of the package's
// syntax trees.
//
// The TypeInfo.Types field maps ast expressions
// to their types, this allows you to get the type
// information using the identifier.
typ := pkg.TypesInfo.Types[id]
named := typ.Type.(*types.Named)
fmt.Println(named) // Hello's *types.Named
fmt.Println(named.Underlying().(*types.Struct)) // Hello's *types.Struct
}
}
}
}
To get the go/ast
representation of the Hello
type's definition you can do the following:
func main() {
// You'll need to repeat the steps above
// to load the packages as well as finding
// the *types.Named instance which will be
// used to determine the position of the
// type's definition ast.
pos := named.Obj().Pos() // the source position of the type's name
for _, pkg := range pkgs {
// Loop over the files in the package.
for _, syn := range pkg.Syntax {
// Use the position to determine whether
// or not the type is declared in this
// file, if not then go to the next one.
if syn.Pos() >= pos || pos >= syn.End() {
continue
}
// Loop over the top-level declarations in the file.
for _, dec := range syn.Decls {
// If the declaration is something
// other than a type declaration then
// continue to the next one.
gd, ok := dec.(*ast.GenDecl)
if !ok || gd.Tok != token.TYPE {
continue
}
// Loop over the specs in the declaration.
for _, spec := range gd.Specs {
// Look for the type spec whose name matches
// the name of the *types.Named instance.
ts, ok := spec.(*ast.TypeSpec)
if !ok || ts.Name.Name != named.Obj().Name() {
continue
}
fmt.Println(ts) // Hello's *ast.TypeSpec
fmt.Println(ts.Type.(*ast.StructType)) // Hello's *ast.StructType
}
}
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论