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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*.DS_Store

_temp/
*.log

# Test binary, built with `go test -c`
*.test
Expand Down
24 changes: 23 additions & 1 deletion clang/clang.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ type stringer interface {

// String returns the Go string of a value whose String() returns a clang String.
func String[T stringer](v T) string {
return clang.GoString(v.String())
str := v.String()
defer str.Dispose()
return c.GoString(str.CStr())

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] clang.String drops nil-guard on CStr(); risks nil deref

clang.String replaces lib/clang.GoString, but the old helper guarded the C string before conversion:

cstr := clangStr.CStr()
if cstr != nil {
    str = c.GoString(cstr)
}

The new version calls c.GoString(str.CStr()) unconditionally. clang_getCString (CStr()) returns NULL for an invalid/default CXString, and c.GoString links straight to the llgo string routine with no NULL guard, so a NULL CStr() scans from a nil pointer instead of yielding the previous safe empty string. Suggest restoring the guard:

func String[T stringer](v T) string {
    str := v.String()
    defer str.Dispose()
    if cstr := str.CStr(); cstr != nil {
        return c.GoString(cstr)
    }
    return ""
}

}

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -112,6 +114,11 @@ func (u TranslationUnit) Cursor() Cursor {
return u.impl.Cursor()
}

// Underlying returns the underlying clang TranslationUnit.
func (u TranslationUnit) Underlying() *clang.TranslationUnit {
return u.impl
}

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

/**
Expand All @@ -134,6 +141,21 @@ func (u TranslationUnit) Cursor() Cursor {
*/
type Cursor = clang.Cursor

/**
* Identifies a specific source location within a translation
* unit.
*
* Use clang_getExpansionLocation() or clang_getSpellingLocation()
* to map a source location to a particular file, line, and column.
*/
type SourceLocation = clang.SourceLocation

// PresumedFile returns the presumed file name for the given source location.

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] Document PresumedFile's dispose-ownership contract

PresumedFile returns an owning clang.String the caller must Dispose() (callers do so at cppdump.go:36 and :83). The sibling String[T] helper in this file disposes internally, so this function deviates from that pattern. A one-line doc noting the returned string must be disposed (and that an invalid location yields an empty string) would prevent future leaks.

func PresumedFile(loc SourceLocation) (filename clang.String) {
loc.PresumedLocation(&filename, nil, nil)
return
}

/**
* Describes how the traversal of the children of a particular
* cursor should proceed after visiting a particular child cursor.
Expand Down
41 changes: 34 additions & 7 deletions cmd/llcppdump/cppdump.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,33 @@ import (
"os"
"strings"

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

func dump(c clang.Cursor, ns string) {
clang.VisitChildren(c, func(cur, parent clang.Cursor) clang.ChildVisitResult {
func dump(node clang.Cursor, ns string, presumedFile *c.Char) {
clang.VisitChildren(node, func(cur, parent clang.Cursor) clang.ChildVisitResult {
if presumedFile != nil {
loc := cur.Location()
at := clang.PresumedFile(loc)

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] PresumedFile resolved per-node during full AST traversal

For every visited child this resolves clang_getPresumedLocation and allocates+frees a CXString, purely to filter by source file. On a real translation unit (system headers pulled in transitively) that is O(nodes) libclang calls and string allocations. Since this lives in the llcppdump debug tool the practical impact is bounded, but if a cheaper predicate fits the intent, clang_Location_isFromMainFile(loc) is a single call with no string allocation, or comparing CXFile handles avoids the strcmp entirely. Also consider filtering on cur.Kind (a free struct-field read) before doing the location resolution.

cmpf := c.Strcmp(at.CStr(), presumedFile)

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] strcmp on a possibly-NULL CStr is undefined behavior

clang.PresumedFile(loc) can return a CXString whose CStr() is NULL when the cursor's location is invalid (built-ins, macro-expansion artifacts, etc.). Passing NULL to c.Strcmp (C strcmp) is undefined behavior and typically segfaults. Since cur.Location() runs for every visited cursor during recursive traversal, this can be hit in practice. Guard at.CStr() (and the presumedFile operand from main) against NULL before the compare, or compare via the safe Go-string path. Note: the at.Dispose() ordering itself is fine — CStr() is consumed by Strcmp before disposal.

at.Dispose()
if cmpf != 0 {
return clang.Continue
}
}
kind := cur.Kind
if kind == lc.CursorCXXAccessSpecifier {
log.Println("==>", kind, "CXXAccessSpecifier", cur.CXXAccessSpecifier())
return clang.Continue
}
name := ns + clang.String(cur)
log.Println("==>", cur.Kind, clang.String(cur.Kind), name)
switch cur.Kind {
log.Println("==>", kind, clang.String(kind), name)
switch kind {
case lc.CursorFunctionDecl, lc.CursorCXXMethod, lc.CursorConstructor, lc.CursorDestructor:
case lc.CursorNamespace:
dump(cur, name+"::")
case lc.CursorClassDecl, lc.CursorNamespace:
dump(cur, name+"::", presumedFile)
}
return clang.Continue
})
Expand All @@ -57,5 +72,17 @@ func main() {
u := idx.ParseTranslationUnit(0, filename, "-x", lang)
defer u.Dispose()

dump(u.Cursor(), "")
usys := u.Underlying()
spelling := usys.Spelling()
defer spelling.Dispose()
log.Println("==> TranslationUnit", c.GoString(spelling.CStr()))

file := usys.File(spelling.CStr())
loc := usys.GetLocationForOffset(file, 2)
presumedFile := clang.PresumedFile(loc)
defer presumedFile.Dispose()
log.Println("==> PresumedFile", c.GoString(presumedFile.CStr()))

root := u.Cursor()
dump(root, "", presumedFile.CStr())
}
10 changes: 0 additions & 10 deletions lib/clang/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,3 @@ type StringSet struct {
*/
// llgo:link (*StringSet).Dispose C.clang_disposeStringSet
func (*StringSet) Dispose() {}

// GoString returns the Go string representation of clangStr and disposes it.
func GoString(clangStr String) (str string) {
defer clangStr.Dispose()
cstr := clangStr.CStr()
if cstr != nil {
str = c.GoString(cstr)
}
return
}
40 changes: 15 additions & 25 deletions lib/clang/clang.go
Original file line number Diff line number Diff line change
Expand Up @@ -1284,7 +1284,7 @@ type TranslationUnit struct {
* Destroy the specified CXTranslationUnit object.
*/
// llgo:link (*TranslationUnit).Dispose C.clang_disposeTranslationUnit
func (*TranslationUnit) Dispose() {}
func (t *TranslationUnit) Dispose() {}

/**
* Retrieve the cursor that represents the given translation unit.
Expand All @@ -1297,6 +1297,16 @@ func (t *TranslationUnit) Cursor() (ret Cursor) {
return
}

// llgo:link (*TranslationUnit).File C.clang_getFile
func (t *TranslationUnit) File(filename *c.Char) (ret File) {
return
}

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

/**
* Describes the kind of entity that a cursor refers to.
*/
Expand Down Expand Up @@ -1507,6 +1517,9 @@ type Type struct {
*/
type File uintptr

//llgo:link File.FileName C.clang_getFileName
func (File) FileName() (ret String) { return }

/**
* Identifies a specific source location within a translation
* unit.
Expand Down Expand Up @@ -2487,7 +2500,7 @@ func (c Token) Kind() (ret TokenKind) {
* the text of an identifier or keyword.
*/
// llgo:link (*TranslationUnit).Token C.clang_getTokenSpelling
func (c *TranslationUnit) Token(token Token) (ret String) {
func (t *TranslationUnit) Token(token Token) (ret String) {
return
}

Expand Down Expand Up @@ -2634,26 +2647,6 @@ func (l SourceLocation) IsInSystemHeader() (ret c.Uint) {
func (l SourceLocation) SpellingLocation(file *File, line, column, offset *c.Uint) {
}

func (l SourceLocation) File() (ret File) {
l.SpellingLocation(&ret, nil, nil, nil)
return
}

func (l SourceLocation) Line() (ret c.Uint) {
l.SpellingLocation(nil, &ret, nil, nil)
return
}

func (l SourceLocation) Column() (ret c.Uint) {
l.SpellingLocation(nil, nil, &ret, nil)
return
}

func (l SourceLocation) Offset() (ret c.Uint) {
l.SpellingLocation(nil, nil, nil, &ret)
return
}

/**
* Retrieve the file, line and column represented by the given source
* location, as specified in a # line directive.
Expand Down Expand Up @@ -2715,6 +2708,3 @@ func (r SourceRange) RangeStart() (loc SourceLocation) {
func (r SourceRange) RangeEnd() (loc SourceLocation) {
return
}

//llgo:link File.FileName C.clang_getFileName
func (File) FileName() (ret String) { return }
Loading