Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 54 additions & 19 deletions cl/blockctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,75 @@
package cl

import (
"go/ast"
"go/token"

"github.com/goplus/gogen"
"github.com/goplus/lib/c"
"github.com/goplus/llcppg/clang"
)

// -----------------------------------------------------------------------------

type node struct {
pos token.Pos
end token.Pos
ctx *blockCtx
}

func (p *node) Pos() token.Pos {
return p.pos
}

func (p *node) End() token.Pos {
return p.end
}

/* TODO(xsw):
func goNode(ctx *blockCtx, v clang.Cursor) ast.Node {
var pos, end c.Uint
rg := v.Extent()
rg.RangeStart().SpellingLocation(nil, nil, nil, &pos)
rg.RangeEnd().SpellingLocation(nil, nil, nil, &end)
base := ctx.file.Base()
return &node{pos: token.Pos(int(pos) + base), end: token.Pos(int(end) + base), ctx: ctx}
}
*/

func goNodePos(ctx *blockCtx, v clang.Cursor) token.Pos {
var pos c.Uint
v.Extent().RangeStart().SpellingLocation(nil, nil, nil, &pos)
return token.Pos(int(pos) + ctx.file.Base())
}

// -----------------------------------------------------------------------------

type nodeInterp struct {
fset *token.FileSet
}

func (p *nodeInterp) Position(start token.Pos) token.Position {
return p.fset.Position(start)
}

func (p *nodeInterp) LoadExpr(v ast.Node) string {
panic("todo: nodeInterp.LoadExpr")
}

// -----------------------------------------------------------------------------

type blockCtx struct {
pkg *gogen.Package
cb *gogen.CodeBuilder
fset *token.FileSet
file *token.File
c gogen.PkgRef
}

/*
func (ctx *blockCtx) goNode(v clang.Cursor) ast.Node {
if rg := v.Range; rg != nil && ctx.file != nil {
base := ctx.file.Base()
pos := token.Pos(int(rg.Begin.Offset) + base)
end := token.Pos(int(rg.End.Offset) + rg.End.TokLen + base)
return &node{pos: pos, end: end, ctx: ctx}
}
return nil
}
*/

func (ctx *blockCtx) goNodePos(v clang.Cursor) token.Pos {
/* if rg := v.Range; rg != nil && ctx.file != nil {
base := ctx.file.Base()
return token.Pos(int(rg.Begin.Offset) + base)
}
return token.NoPos */
panic("todo: goNodePos")
func (p *blockCtx) initFile(file Source) {
src := file.TU.FileContents(file.Handle)
p.file = p.fset.AddFile("", -1, len(src))
p.file.SetLinesForContent(src)
}

func (p *blockCtx) getPubName(pfnName *string) (rewritten bool) {
Expand Down
27 changes: 7 additions & 20 deletions cl/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package cl

import (
"go/ast"
"go/token"
"go/types"
"log"
Expand Down Expand Up @@ -47,20 +46,6 @@ func SetDebug(flags int) {

// -----------------------------------------------------------------------------

type nodeInterp struct {
fset *token.FileSet
}

func (p *nodeInterp) Position(start token.Pos) token.Position {
return p.fset.Position(start)
}

func (p *nodeInterp) LoadExpr(v ast.Node) string {
panic("todo: nodeInterp.LoadExpr")
}

// -----------------------------------------------------------------------------

type PkgInfo struct {
}

Expand Down Expand Up @@ -101,6 +86,7 @@ type Config struct {
// Source represents a C/C++ header to compile.
type Source struct {
TU clang.TranslationUnit
Handle clang.File
PresumedFile *c.Char

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P3] Source.PresumedFile: unused, undocumented exported field

PresumedFile *c.Char is an exported field that is never populated or read anywhere in the repo. Either add a doc comment describing its intended use, or drop it to avoid an undocumented public API surface. Raw *c.Char values (e.g. from a clang.String) also carry a use-after-free footgun once their source is Dispose()d, so an ownership note would help if it's kept.

}

Expand Down Expand Up @@ -138,10 +124,11 @@ func NewPackage(pkgPath, pkgName string, file Source, conf *Config) (pkg Package
// -----------------------------------------------------------------------------

func loadFile(p *gogen.Package, conf *Config, file Source) (pi *PkgInfo, err error) {
c := p.Import("github.com/lib/c")
c := p.Import("github.com/goplus/lib/c")
ctx := &blockCtx{
pkg: p, cb: p.CB(), fset: p.Fset, c: c,
}
ctx.initFile(file)
_ = conf
clang.VisitChildren(file.TU.Cursor(), func(decl, parent clang.Cursor) clang.ChildVisitResult {
compileDecl(ctx, decl)
Expand Down Expand Up @@ -243,9 +230,9 @@ func compileFunc(ctx *blockCtx, fn clang.Cursor) {
results = types.NewTuple(pkg.NewParam(token.NoPos, "", tyRet, false))
}
sig := types.NewSignatureType(nil, nil, nil, types.NewTuple(params...), results, variadic)
f := types.NewFunc(ctx.goNodePos(fn), pkg.Types, fnName, sig)
if old := pkg.Types.Scope().Insert(f); old != nil {
log.Panicln("Go func", fnName, "redefined")
f, err := pkg.NewFuncWith(goNodePos(ctx, fn), fnName, sig, nil)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] compileFunc: redeclaration will panic on repeated C decls

This calls pkg.NewFuncWith unconditionally and log.Paniclns on error. C headers routinely declare the same function multiple times (via includes). The commented-out reference implementation guarded with checkExists. Please confirm NewFuncWith tolerates duplicates given SetRedeclarable(true); if not, this will crash on realistic input. A dedup/exists guard may be needed.

if err != nil {
log.Panicln("compileFunc:", fnName, err)
}
// ctx.addExternFunc(fnName)
if rewritten {
Expand Down Expand Up @@ -356,7 +343,7 @@ func newParam(ctx *blockCtx, decl clang.Cursor, i c.Int) *types.Var {
} else {
declName = "__llcppg_param" + strconv.Itoa(int(i)+1)
}
return types.NewParam(ctx.goNodePos(decl), ctx.pkg.Types, declName, typ)
return types.NewParam(goNodePos(ctx, decl), ctx.pkg.Types, declName, typ)
}

// -----------------------------------------------------------------------------
12 changes: 9 additions & 3 deletions cl/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cl_test

import (
"bytes"
"log"
"os"
"testing"

Expand All @@ -43,11 +44,13 @@ func testDiff(t *testing.T, dir string, outfname string, b *bytes.Buffer, exp an
}

func testGenGo(t *testing.T, pkg *gogen.Package, dir string, exp any) {
log.Println("==> testGenGo", dir)
var b bytes.Buffer
err := pkg.WriteTo(&b)
if err != nil {
t.Fatal("gogen.WriteTo failed:", err)
}
log.Println("==> testGenGo", dir, "len:", b.Len())
testDiff(t, dir, "/result.txt", &b, exp)
}

Expand All @@ -56,11 +59,13 @@ func testFromDir(t *testing.T, sel, relDir, lang string) {
idx := clang.CreateIndex(0, 0)
defer idx.Dispose()

u := idx.ParseTranslationUnit(0, pkgDir+"/in.h", "-x", lang)
filename := pkgDir + "/in.h"
u := idx.ParseTranslationUnit(0, filename, "-x", lang)
defer u.Dispose()

imp := packages.NewImporter(nil, "./_mod")
pkg, err := cl.NewPackage("", "foo", cl.Source{TU: u}, &cl.Config{
imp := packages.NewImporter(nil)
file := u.File(filename)
pkg, err := cl.NewPackage("", "foo", cl.Source{TU: u, Handle: file}, &cl.Config{
Importer: imp,
NameLookup: cltest.MockNameLookup,
})
Expand All @@ -74,6 +79,7 @@ func testFromDir(t *testing.T, sel, relDir, lang string) {
}

func _TestMockC(t *testing.T) {
cl.SetDebug(cl.DbgFlagAll)
testFromDir(t, "", "./_testmockc", "c")
}

Expand Down
6 changes: 5 additions & 1 deletion cl/type_and_var.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,17 @@ func newPointer(typ types.Type) types.Type {

func toType(ctx *blockCtx, typ lc.Type, flags int) types.Type {
switch typ.Kind {
case lc.TypeCharS:
return ctx.c.Ref("Char").Type()
case lc.TypeInt:
return ctx.c.Ref("Int").Type()
case lc.TypeUInt:
return ctx.c.Ref("UInt").Type()
return ctx.c.Ref("Uint").Type()
case lc.TypePointer:
pointee := toType(ctx, typ.PointeeType(), flags)
return newPointer(pointee)
default:
log.Println("==> toType: unknown Kind -", typ.Kind)
Comment on lines +74 to +75

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P3] toType: default branch logs then falls through to panic

The new default case log.Printlns the unknown kind and then execution falls through to the panic("todo: toType ...") at the end of the function, producing duplicate diagnostics for the same event. Consider panicing directly in the default case (with the kind) instead of logging then panicking. The flags argument is also still unused in the switch — a // TODO would clarify that's intentional.

}
panic("todo: toType " + clang.String(typ))
}
Expand Down
19 changes: 19 additions & 0 deletions clang/clang.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ func (i Index) ParseTranslationUnit(options uint, filename string, args ...strin

// -----------------------------------------------------------------------------

/**
* A particular source file that is part of a translation unit.
*/
type File = clang.File

// -----------------------------------------------------------------------------

/**
* A single translation unit, which resides in an index.
*/
Expand All @@ -104,6 +111,18 @@ func (u TranslationUnit) Dispose() {
u.impl.Dispose()
}

// File returns the File object corresponding to the given filename in the translation unit.
func (u TranslationUnit) File(filename string) File {
return u.impl.File(c.AllocaCStr(filename))
}

// FileContents returns the contents of the specified file in the translation unit.
func (u TranslationUnit) FileContents(file File) []byte {
var size c.SizeT
data := u.impl.FileContents(file, &size)
return unsafe.Slice((*byte)(unsafe.Pointer(data)), int(size))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] FileContents: guard nil pointer and unsigned->int size narrowing

clang_getFileContents returns NULL (with size 0) when the file is not loaded/buffered in the TU. unsafe.Slice on a nil pointer with a non-zero/garbage size yields an invalid slice that panics on access. Also, int(size) narrows an unsigned c.SizeT to signed int, which can go negative/truncated for a pathologically large or bogus size. Recommend returning nil when data == nil before constructing the slice.

Note also that the returned []byte aliases C-owned memory tied to the TranslationUnit lifetime (invalid after Dispose()), and the doc comment states unconditionally that it "returns the contents" — worth documenting the empty/nil case and the aliasing/lifetime contract for future callers.

}

/**
* Retrieve the cursor that represents the given translation unit.
*
Expand Down
5 changes: 5 additions & 0 deletions lib/clang/clang.go
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,11 @@ func (t *TranslationUnit) File(filename *c.Char) (ret File) {
return
}

// llgo:link (*TranslationUnit).FileContents C.clang_getFileContents
func (t *TranslationUnit) FileContents(file File, size *c.SizeT) (ret *c.Char) {
return
}

// llgo:link (*TranslationUnit).Spelling C.clang_getTranslationUnitSpelling
func (t *TranslationUnit) Spelling() (ret String) {
return
Expand Down
Loading