Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

llcppg:refactor to type-node level #157

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

luoliwoshang
Copy link
Contributor

@luoliwoshang luoliwoshang commented Jan 15, 2025

fix #61
fix #99

缺陷

目前Go Binding的生成逻辑是,根据获得的文件,均先处理其include文件,再处理文件本身。

对于大多数较为头文件组织清晰的库,可以正常进行转换,这里即会优先访问 type.h 处理 A的定义,然后在main.h 中正确的引用。
main.h

typedef struct A {
    long a;
} A;
#include "compat.h"

compat.h

typedef A B;
func (p *DocFileSetProcessor) ProcessFileSet(files []*ast.FileEntry) error {
	for _, inc := range p.depIncs {
		idx := FindEntry(files, inc)
		if idx < 0 {
			continue
		}
		p.visitedFile[files[idx].Path] = struct{}{}
	}
	for _, file := range files {
		p.visitFile(file.Path, files)
	}
	if p.done != nil {
		p.done()
	}
	return nil
}

但是根据C语言本省的特性,其Include的位置是可以存在于文件的任何位置的,对于如下姿势的声明方式,即不能做到正确转换,如下的用例即会存在先访问compat.h,但是此时A并没有定义。
main.h

typedef struct A {
    int a;
} A;

#include "compat.h"

compat.h

typedef A B;

resolution

USR

通过libclang的USR的唯一标识,可以标识某个引用实际对应的类型。

这是对于 struct A 的描述。可以看到定义的时候Ident对应的USR是c:@S@A,其 Field的USR是 c:@S@A@FI@a, 对于typedef A; 对应的USR即为 c:main.h@T@A, 并且其引用的Underlying Type即为引用 struct A;所以可以看到Type对应的USR即为 c:@S@A

      "decls": [
        {
          "_Type": "TypeDecl",
          "Loc": {
            "_Type": "Location",
            "File": "./main.h"
          },
          "Name": {
            "_Type": "Ident",
            "Name": "A",
            "USR": "c:@S@A"
          },
          "Type": {
            "_Type": "RecordType",
            "Tag": 0,
            "Fields": {
              "_Type": "FieldList",
              "List": [
                {
                  "_Type": "Field",
                  "Type": {
                    "_Type": "BuiltinType",
                    "Kind": 6,
                    "Flags": 4
                  },
                  "Names": [
                    {
                      "_Type": "Ident",
                      "Name": "a",
                      "USR": "c:@S@A@FI@a"
                    }
                  ]
                }
              ]
            },
          }
        },
        {
          "_Type": "TypedefDecl",
          "Loc": {
            "_Type": "Location",
            "File": "./main.h"
          },
          "Name": {
            "_Type": "Ident",
            "Name": "A",
            "USR": "c:main.h@T@A"
          },
          "Type": {
            "_Type": "TagExpr",
            "Name": {
              "_Type": "Ident",
              "Name": "A",
              "USR": "c:@S@A"
            },
            "Tag": 0
          }
        }
      ],

而对于compat.h 中的 typedef 的 引用可以看到正常的 引用了 c:main.h@T@A

        {
          "_Type": "TypedefDecl",
          "Loc": {
            "_Type": "Location",
            "File": "./compat.h"
          },
          "Doc": null,
          "Parent": null,
          "Name": {
            "_Type": "Ident",
            "Name": "B",
            "USR": "c:compat.h@T@B"
          },
          "Type": {
            "_Type": "Ident",
            "Name": "A",
            "USR": "c:main.h@T@A"
          }
        }
  • 保留typedef struct A { } A;的两个同名节点,在gogensig中再进行处理,因为在引用关系中,引用struct A和引用typedef A是两个不同的USR表示。

通过类型引用关系重排文件顺序,保证被依赖的类型所在的文件会优先进行处理 (X,不可行)

可以解决如下问题,即根据类型引用可以分析得出 compat.h 的 B依赖了 A,所以优先处理main.h 的所有节点再处理compat.h 的定义。

typedef struct A {
    long a;
} A;
#include "compat.h"

compat.h

typedef A B;

但是这个方案会导致以下用例中 main.h中的C定义时,B还未定义,所以基于引用关系分析出来的文件粒度的处理顺序,并不能满足当前需求。
main.h

typedef struct A {
    long a;
} A;
#include "compat.h"
typedef B C;

compat.h

typedef A B;

根据类型定义粒度,决定处理类型初始化的顺序。

  • 根据AST引用关系分析出,创建类型的顺序,根据这个顺序来完成类型在Go包初始化的顺序。
  • 创建类型的顺序可能并不与C头文件中的一致?
    可能可以创建那些类型定义节点,保证其在树中的结构,再根据引用关系逐渐完成类型初始化?

@luoliwoshang luoliwoshang marked this pull request as draft January 15, 2025 02:56
Copy link

codecov bot commented Jan 15, 2025

Codecov Report

Attention: Patch coverage is 83.48416% with 146 lines in your changes missing coverage. Please review.

Project coverage is 93.17%. Comparing base (89c24fe) to head (d1303b1).
Report is 17 commits behind head on main.

Files with missing lines Patch % Lines
cmd/gogensig/convert/package.go 61.88% 75 Missing and 18 partials ⚠️
cmd/gogensig/convert/convert.go 71.50% 42 Missing and 9 partials ⚠️
cmd/gogensig/config/conf.go 87.50% 1 Missing ⚠️
cmd/gogensig/convert/type.go 93.75% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #157      +/-   ##
==========================================
- Coverage   98.16%   93.17%   -5.00%     
==========================================
  Files          17       18       +1     
  Lines        2179     2768     +589     
==========================================
+ Hits         2139     2579     +440     
- Misses         28      149     +121     
- Partials       12       40      +28     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@luoliwoshang luoliwoshang changed the title [wip] llcppg:type info llcppg:type info Jan 15, 2025
@luoliwoshang luoliwoshang force-pushed the llcppsigfetch/type branch 3 times, most recently from ef18c3b to b0f5135 Compare January 24, 2025 08:55
@luoliwoshang luoliwoshang changed the title llcppg:type info llcppg:refactor to type-node level Jan 26, 2025
if err != nil {
return err
}
p.Process(order)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error return value of p.Process is not checked (errcheck)

Details

lint 解释

这个lint结果表明在代码中调用了函数 p.Process,但没有检查其返回的错误值。根据Go语言的最佳实践,应该始终检查函数的错误返回值,以确保程序的健壮性。

错误用法

func main() {
    p := &Processor{}
    p.Process()
}

在这个示例中,调用了 p.Process() 但没有检查其返回的错误值。

正确用法

func main() {
    p := &Processor{}
    if err := p.Process(); err != nil {
        log.Fatalf("Process failed: %v", err)
    }
}

在这个示例中,调用了 p.Process() 并检查了其返回的错误值。如果错误不为空,则使用 log.Fatalf 记录错误并终止程序。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

return err
}
p.Process(order)
p.Pkg.WritePkgFiles()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error return value of p.Pkg.WritePkgFiles is not checked (errcheck)

Details

lint 解释

这个lint结果表明在调用函数 p.Pkg.WritePkgFiles 时,其返回的错误值 err 没有被检查。虽然Go语言鼓励显式处理错误,但有时开发者可能会忽略错误检查,这可能导致潜在的问题。

错误用法

以下是一个示例代码,展示了不正确的用法:

func writePackageFiles(p *Pkg) {
    p.Pkg.WritePkgFiles()
}

在这个例子中,p.Pkg.WritePkgFiles() 的返回值 err 被忽略了。

正确用法

以下是一个示例代码,展示了正确的用法:

func writePackageFiles(p *Pkg) error {
    err := p.Pkg.WritePkgFiles()
    if err != nil {
        return fmt.Errorf("failed to write package files: %w", err)
    }
    return nil
}

在这个例子中,p.Pkg.WritePkgFiles() 的返回值 err 被检查。如果 err 不为 nil,则返回一个包含错误信息的新的错误。这样可以确保在发生错误时能够及时处理和记录。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

}
p.Process(order)
p.Pkg.WritePkgFiles()
p.Pkg.WriteLinkFile()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error return value of p.Pkg.WriteLinkFile is not checked (errcheck)

Details

lint 解释

这个lint结果表明在代码中调用了函数 p.Pkg.WriteLinkFile,但没有检查该函数的返回值中的错误(error)。根据Go语言的最佳实践,应该始终检查函数的返回值中的错误,以确保程序的健壮性。

错误用法

以下是一个示例代码,展示了不正确的用法:

func main() {
    p := &Package{}
    p.Pkg.WriteLinkFile("path/to/file")
}

在这个示例中,调用了 p.Pkg.WriteLinkFile 但没有检查其返回的错误值。

正确用法

以下是一个示例代码,展示了正确的用法:

func main() {
    p := &Package{}
    err := p.Pkg.WriteLinkFile("path/to/file")
    if err != nil {
        // 处理错误
        log.Fatalf("Failed to write link file: %v", err)
    }
}

在这个示例中,调用了 p.Pkg.WriteLinkFile 并检查了其返回的错误值。如果错误不为空,则进行相应的处理(例如记录日志并退出程序)。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

p.Process(order)
p.Pkg.WritePkgFiles()
p.Pkg.WriteLinkFile()
p.Pkg.WritePubFile()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error return value of p.Pkg.WritePubFile is not checked (errcheck)

Details

lint 解释

这个lint结果表明在调用函数 p.Pkg.WritePubFile 时,其返回的错误值 err 没有被检查。虽然Go语言鼓励显式处理错误,但有时开发者可能会忽略错误检查,这可能导致潜在的问题。

错误用法

以下是一个示例代码,展示了不正确的用法:

func writePubFile(p *Package) {
    p.Pkg.WritePubFile("path/to/file")
}

在这个例子中,p.Pkg.WritePubFile 的返回值 err 被忽略了。

正确用法

以下是一个示例代码,展示了正确的用法:

func writePubFile(p *Package) error {
    err := p.Pkg.WritePubFile("path/to/file")
    if err != nil {
        return fmt.Errorf("failed to write pub file: %w", err)
    }
    return nil
}

在这个例子中,p.Pkg.WritePubFile 的返回值 err 被检查。如果 err 不为 nil,则返回一个包含错误信息的新的错误。这样可以确保在调用该函数时能够正确处理可能发生的错误。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

switch decl := typ.(type) {
case *ast.TypeDecl:
p.Pkg.SetCurFile(Hfile(p.Pkg, p.files[decl.DeclBase.Loc.File]))
p.Pkg.ConvertTypeDecl(decl)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error return value of p.Pkg.ConvertTypeDecl is not checked (errcheck)

Details

lint 解释

这个lint结果表明在调用函数 p.Pkg.ConvertTypeDecl 时,其返回的错误值 err 没有被检查。虽然Go语言鼓励显式处理错误,但未检查错误可能会导致程序在遇到错误时继续执行,从而引发难以调试的问题。

错误用法

以下是一个示例代码,展示了不正确的用法:

func convertTypeDecl(p *Package) {
    p.Pkg.ConvertTypeDecl()
}

在这个例子中,p.Pkg.ConvertTypeDecl() 的返回值 err 被忽略了。

正确用法

以下是一个示例代码,展示了正确的用法:

func convertTypeDecl(p *Package) error {
    if err := p.Pkg.ConvertTypeDecl(); err != nil {
        return fmt.Errorf("failed to convert type declaration: %w", err)
    }
    return nil
}

在这个例子中,p.Pkg.ConvertTypeDecl() 的返回值 err 被检查。如果 err 不为 nil,则返回一个包含错误信息的新的错误。这样可以确保在遇到错误时能够正确处理并传递错误信息。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

p.Pkg.ConvertTypeDecl(decl)
case *ast.EnumTypeDecl:
p.Pkg.SetCurFile(Hfile(p.Pkg, p.files[decl.DeclBase.Loc.File]))
p.Pkg.ConvertEnumTypeDecl(decl)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error return value of p.Pkg.ConvertEnumTypeDecl is not checked (errcheck)

Details

lint 解释

这个lint结果表明在函数 p.Pkg.ConvertEnumTypeDecl 的调用中,返回的错误值没有被检查。在Go语言中,函数通常会返回一个或多个值,其中最后一个值通常是错误(error)类型。如果调用者不检查这个错误值,可能会导致程序在遇到错误时继续执行,从而引发不可预测的行为。

错误用法

以下是一个示例代码,展示了如何错误地使用 p.Pkg.ConvertEnumTypeDecl 函数:

func someFunction(p *SomePackage) {
    p.Pkg.ConvertEnumTypeDecl()
}

在这个例子中,p.Pkg.ConvertEnumTypeDecl() 的返回值没有被检查。

正确用法

以下是一个示例代码,展示了如何正确地使用 p.Pkg.ConvertEnumTypeDecl 函数:

func someFunction(p *SomePackage) {
    if err := p.Pkg.ConvertEnumTypeDecl(); err != nil {
        // 处理错误
        log.Fatalf("Failed to convert enum type decl: %v", err)
    }
}

在这个例子中,p.Pkg.ConvertEnumTypeDecl() 的返回值被检查,并且如果发生错误,程序会记录错误并退出。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

p.Pkg.ConvertEnumTypeDecl(decl)
case *ast.TypedefDecl:
p.Pkg.SetCurFile(Hfile(p.Pkg, p.files[decl.DeclBase.Loc.File]))
p.Pkg.ConvertTypedefDecl(decl)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error return value of p.Pkg.ConvertTypedefDecl is not checked (errcheck)

Details

lint 解释

这个lint结果表明在函数 p.Pkg.ConvertTypedefDecl 的调用中,返回的错误值没有被检查。在Go语言中,函数通常会返回一个或多个值,其中一个值是错误信息(error)。如果忽略了这个错误值,可能会导致程序在遇到错误时无法正确处理,从而引发潜在的问题。

错误用法

以下是一个示例代码,展示了不正确的用法:

func main() {
    p := &Package{}
    p.Pkg.ConvertTypedefDecl()
}

在这个例子中,p.Pkg.ConvertTypedefDecl() 调用没有检查返回的错误值。

正确用法

以下是一个示例代码,展示了正确的用法:

func main() {
    p := &Package{}
    if err := p.Pkg.ConvertTypedefDecl(); err != nil {
        // 处理错误
        log.Fatalf("Error converting typedef declaration: %v", err)
    }
}

在这个例子中,p.Pkg.ConvertTypedefDecl() 调用的返回值被检查,并且在遇到错误时进行了处理。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

if err != nil {
t.Fatal(err)
}
for _, fi := range outDir {
if strings.HasSuffix(fi.Name(), "go.mod") || strings.HasSuffix(fi.Name(), "go.sum") || strings.HasSuffix(fi.Name(), "llcppg.pub") {
continue
} else {
content, err := os.ReadFile(filepath.Join(outputDir, fi.Name()))
content, err := os.ReadFile(filepath.Join(testInfo.outputDir, fi.Name()))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shadow: declaration of "err" shadows declaration at line 156 (govet)

Details

lint 解释

shadow: declaration of "err" shadows declaration at line 156 (govet) 这个lint结果表示在代码中,变量 err 在当前作用域内被重新声明了。这可能会导致混淆和潜在的错误,因为之前的 err 变量可能没有被正确使用。

错误用法

func example() error {
    err := someFunction()
    if err != nil {
        return err
    }
    // 其他代码
    err := anotherFunction() // 这里重新声明了 err,导致 shadow 警告
    if err != nil {
        return err
    }
    return nil
}

正确用法

func example() error {
    err := someFunction()
    if err != nil {
        return err
    }
    // 其他代码
    anotherErr := anotherFunction() // 使用不同的变量名避免 shadow
    if anotherErr != nil {
        return anotherErr
    }
    return nil
}

💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

t.Fatal(err)
}
defer func() {
if err := os.Chdir(originalWd); err != nil {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shadow: declaration of "err" shadows declaration at line 537 (govet)

Details

lint 解释

shadow: declaration of "err" shadows declaration at line 537 (govet) 这个lint结果表示在代码中,变量 err 在当前作用域内被重新声明了。这可能会导致混淆和潜在的错误,因为之前的 err 变量可能没有被正确使用。

错误用法

func example() error {
    err := someFunction()
    if err != nil {
        return err
    }
    // 其他代码
    err := anotherFunction() // 这里重新声明了 err,导致之前的 err 被覆盖
    if err != nil {
        return err
    }
    return nil
}

正确用法

func example() error {
    err := someFunction()
    if err != nil {
        return err
    }
    // 其他代码
    anotherErr := anotherFunction() // 使用不同的变量名避免 shadow
    if anotherErr != nil {
        return anotherErr
    }
    return nil
}

💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

return nil
}

func (p *Package) ConvertTypeDecl(typeDecl *ast.TypeDecl) error {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

359-398 lines are duplicate of cmd/gogensig/convert/package.go:318-357 (dupl)

Details

lint 解释

这个lint结果表明在文件 cmd/gogensig/convert/package.go 中,从第359行到第398行的代码与从第318行到第357行的代码是完全重复的。这种重复性可能会导致代码冗余、维护困难以及潜在的错误。

错误用法

以下是一个示例,展示了可能的错误用法:

// 错误用法示例
func process() {
    // 重复的代码块1
    fmt.Println("Processing data")
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }

    // 重复的代码块2
    fmt.Println("Processing data")
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }
}

正确用法

以下是一个示例,展示了如何通过提取公共部分来避免重复:

// 正确用法示例
func process() {
    // 提取公共部分到一个函数中
    processData()

    // 调用公共函数
    processData()
}

func processData() {
    fmt.Println("Processing data")
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }
}

通过这种方式,可以减少代码冗余,提高代码的可维护性和可读性。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant