package cgo
// This file parses a fragment of C with libclang and stores the result for AST
// modification. It does not touch the AST itself.
import (
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"go/ast"
"go/scanner"
"go/token"
"path/filepath"
"strconv"
"strings"
"unsafe"
"tinygo.org/x/go-llvm"
)
/ *
# include < clang - c / Index . h > // If this fails, libclang headers aren't available. Please take a look here: https://tinygo.org/docs/guides/build/
# include < llvm / Config / llvm - config . h >
# include < stdlib . h >
# include < stdint . h >
// This struct should be ABI-compatible on all platforms (uintptr_t has the same
// alignment etc. as void*) but does not include void* pointers that are not
// always real pointers.
// The Go garbage collector assumes that all non-nil pointer-typed integers are
// actually pointers. This is not always true, as data[1] often contains 0x1,
// which is clearly not a valid pointer. Usually the GC won't catch this issue,
// but occasionally it will leading to a crash with a vague error message.
typedef struct {
enum CXCursorKind kind ;
int xdata ;
uintptr_t data [ 3 ] ;
} GoCXCursor ;
// Forwarding functions. They are implemented in libclang_stubs.c and forward to
// the real functions without doing anything else, thus they are entirely
// compatible with the versions without tinygo_ prefix. The only difference is
// the CXCursor type, which has been replaced with GoCXCursor.
GoCXCursor tinygo_clang_getTranslationUnitCursor ( CXTranslationUnit tu ) ;
unsigned tinygo_clang_visitChildren ( GoCXCursor parent , CXCursorVisitor visitor , CXClientData client_data ) ;
CXString tinygo_clang_getCursorSpelling ( GoCXCursor c ) ;
CXString tinygo_clang_getCursorPrettyPrinted ( GoCXCursor c , CXPrintingPolicy Policy ) ;
CXPrintingPolicy tinygo_clang_getCursorPrintingPolicy ( GoCXCursor c ) ;
enum CXCursorKind tinygo_clang_getCursorKind ( GoCXCursor c ) ;
CXType tinygo_clang_getCursorType ( GoCXCursor c ) ;
GoCXCursor tinygo_clang_getTypeDeclaration ( CXType t ) ;
CXType tinygo_clang_getTypedefDeclUnderlyingType ( GoCXCursor c ) ;
CXType tinygo_clang_getCursorResultType ( GoCXCursor c ) ;
int tinygo_clang_Cursor_getNumArguments ( GoCXCursor c ) ;
GoCXCursor tinygo_clang_Cursor_getArgument ( GoCXCursor c , unsigned i ) ;
enum CX_StorageClass tinygo_clang_Cursor_getStorageClass ( GoCXCursor c ) ;
CXSourceLocation tinygo_clang_getCursorLocation ( GoCXCursor c ) ;
CXSourceRange tinygo_clang_getCursorExtent ( GoCXCursor c ) ;
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit ( GoCXCursor c ) ;
long long tinygo_clang_getEnumConstantDeclValue ( GoCXCursor c ) ;
CXType tinygo_clang_getEnumDeclIntegerType ( GoCXCursor c ) ;
unsigned tinygo_clang_Cursor_isAnonymous ( GoCXCursor c ) ;
unsigned tinygo_clang_Cursor_isBitField ( GoCXCursor c ) ;
int tinygo_clang_globals_visitor ( GoCXCursor c , GoCXCursor parent , CXClientData client_data ) ;
int tinygo_clang_struct_visitor ( GoCXCursor c , GoCXCursor parent , CXClientData client_data ) ;
int tinygo_clang_enum_visitor ( GoCXCursor c , GoCXCursor parent , CXClientData client_data ) ;
void tinygo_clang_inclusion_visitor ( CXFile included_file , CXSourceLocation * inclusion_stack , unsigned include_len , CXClientData client_data ) ;
* /
import "C"
// storedRefs stores references to types, used for clang_visitChildren.
var storedRefs refMap
var diagnosticSeverity = [ ... ] string {
C . CXDiagnostic_Ignored : "ignored" ,
C . CXDiagnostic_Note : "note" ,
C . CXDiagnostic_Warning : "warning" ,
C . CXDiagnostic_Error : "error" ,
C . CXDiagnostic_Fatal : "fatal" ,
}
// Alias so that cgo.go (which doesn't import Clang related stuff and is in
// theory decoupled from Clang) can also use this type.
type clangCursor = C . GoCXCursor
func init ( ) {
// Check that we haven't messed up LLVM versioning.
// This can happen when llvm_config_*.go files in either this or the
// tinygo.org/x/go-llvm packages is incorrect. It should not ever happen
// with byollvm.
if C . LLVM_VERSION_STRING != llvm . Version {
panic ( "incorrect build: using LLVM version " + llvm . Version + " in the tinygo.org/x/llvm package, and version " + C . LLVM_VERSION_STRING + " in the ./cgo package" )
}
}
func ( f * cgoFile ) readNames ( fragment string , cflags [ ] string , filename string , callback func ( map [ string ] clangCursor ) ) {
index := C . clang_createIndex ( 0 , 0 )
defer C . clang_disposeIndex ( index )
// pretend to be a .c file
filenameC := C . CString ( filename + "!cgo.c" )
defer C . free ( unsafe . Pointer ( filenameC ) )
fragmentC := C . CString ( fragment )
defer C . free ( unsafe . Pointer ( fragmentC ) )
unsavedFile := C . struct_CXUnsavedFile {
Filename : filenameC ,
Length : C . ulong ( len ( fragment ) ) ,
Contents : fragmentC ,
}
// convert Go slice of strings to C array of strings.
cmdargsC := C . malloc ( C . size_t ( len ( cflags ) ) * C . size_t ( unsafe . Sizeof ( uintptr ( 0 ) ) ) )
defer C . free ( cmdargsC )
cmdargs := ( * [ 1 << 16 ] * C . char ) ( cmdargsC )
for i , cflag := range cflags {
s := C . CString ( cflag )
cmdargs [ i ] = s
defer C . free ( unsafe . Pointer ( s ) )
}
var unit C . CXTranslationUnit
errCode := C . clang_parseTranslationUnit2 (
index ,
filenameC ,
( * * C . char ) ( cmdargsC ) , C . int ( len ( cflags ) ) , // command line args
& unsavedFile , 1 , // unsaved files
C . CXTranslationUnit_DetailedPreprocessingRecord ,
& unit )
if errCode != 0 {
// This is probably a bug in the usage of libclang.
panic ( "cgo: failed to parse source with libclang" )
}
defer C . clang_disposeTranslationUnit ( unit )
// Report parser and type errors.
if numDiagnostics := int ( C . clang_getNumDiagnostics ( unit ) ) ; numDiagnostics != 0 {
addDiagnostic := func ( diagnostic C . CXDiagnostic ) {
spelling := getString ( C . clang_getDiagnosticSpelling ( diagnostic ) )
severity := diagnosticSeverity [ C . clang_getDiagnosticSeverity ( diagnostic ) ]
location := C . clang_getDiagnosticLocation ( diagnostic )
pos := f . getClangLocationPosition ( location , unit )
f . addError ( pos , severity + ": " + spelling )
}
for i := 0 ; i < numDiagnostics ; i ++ {
diagnostic := C . clang_getDiagnostic ( unit , C . uint ( i ) )
addDiagnostic ( diagnostic )
// Child diagnostics (like notes on redefinitions).
diagnostics := C . clang_getChildDiagnostics ( diagnostic )
for j := 0 ; j < int ( C . clang_getNumDiagnosticsInSet ( diagnostics ) ) ; j ++ {
addDiagnostic ( C . clang_getDiagnosticInSet ( diagnostics , C . uint ( j ) ) )
}
}
}
// Extract information required by CGo.
ref := storedRefs . Put ( f )
defer storedRefs . Remove ( ref )
cursor := C . tinygo_clang_getTranslationUnitCursor ( unit )
C . tinygo_clang_visitChildren ( cursor , C . CXCursorVisitor ( C . tinygo_clang_globals_visitor ) , C . CXClientData ( ref ) )
// Determine files read during CGo processing, for caching.
inclusionCallback := func ( includedFile C . CXFile ) {
// Get full file path.
path := getString ( C . clang_getFileName ( includedFile ) )
// Get contents of file (that should be in-memory).
size := C . size_t ( 0 )
rawData := C . clang_getFileContents ( unit , includedFile , & size )
if rawData == nil {
// Sanity check. This should (hopefully) never trigger.
panic ( "libclang: file contents was not loaded" )
}
data := ( * [ 1 << 24 ] byte ) ( unsafe . Pointer ( rawData ) ) [ : size ]
// Hash the contents if it isn't hashed yet.
if _ , ok := f . visitedFiles [ path ] ; ! ok {
// already stored
sum := sha512 . Sum512_224 ( data )
f . visitedFiles [ path ] = sum [ : ]
}
}
inclusionCallbackRef := storedRefs . Put ( inclusionCallback )
defer storedRefs . Remove ( inclusionCallbackRef )
C . clang_getInclusions ( unit , C . CXInclusionVisitor ( C . tinygo_clang_inclusion_visitor ) , C . CXClientData ( inclusionCallbackRef ) )
// Do all the C AST operations inside a callback. This makes sure that
// libclang related memory is only freed after it is not necessary anymore.
callback ( f . names )
}
// Convert the AST node under the given Clang cursor to a Go AST node and return
// it.
func ( f * cgoFile ) createASTNode ( name string , c clangCursor ) ( ast . Node , any ) {
kind := C . tinygo_clang_getCursorKind ( c )
pos := f . getCursorPosition ( c )
switch kind {
case C . CXCursor_FunctionDecl :
cursorType := C . tinygo_clang_getCursorType ( c )
numArgs := int ( C . tinygo_clang_Cursor_getNumArguments ( c ) )
obj := & ast . Object {
Kind : ast . Fun ,
Name : "C." + name ,
}
exportName := name
localName := name
var stringSignature string
if C . tinygo_clang_Cursor_getStorageClass ( c ) == C . CX_SC_Static {
// A static function is assigned a globally unique symbol name based
// on the file path (like _Cgo_static_2d09198adbf58f4f4655_foo) and
// has a different Go name in the form of C.foo!symbols.go instead
// of just C.foo.
path := f . importPath + "/" + filepath . Base ( f . fset . File ( f . file . Pos ( ) ) . Name ( ) )
staticIDBuf := sha256 . Sum256 ( [ ] byte ( path ) )
staticID := hex . EncodeToString ( staticIDBuf [ : 10 ] )
exportName = "_Cgo_static_" + staticID + "_" + name
localName = name + "!" + filepath . Base ( path )
// Create a signature. This is necessary for MacOS to forward the
// call, because MacOS doesn't support aliases like ELF and PE do.
// (There is N_INDR but __attribute__((alias("..."))) doesn't work).
policy := C . tinygo_clang_getCursorPrintingPolicy ( c )
defer C . clang_PrintingPolicy_dispose ( policy )
C . clang_PrintingPolicy_setProperty ( policy , C . CXPrintingPolicy_TerseOutput , 1 )
stringSignature = getString ( C . tinygo_clang_getCursorPrettyPrinted ( c , policy ) )
stringSignature = strings . Replace ( stringSignature , " " + name + "(" , " " + exportName + "(" , 1 )
stringSignature = strings . TrimPrefix ( stringSignature , "static " )
}
args := make ( [ ] * ast . Field , numArgs )
decl := & ast . FuncDecl {
Doc : & ast . CommentGroup {
List : [ ] * ast . Comment {
{
Slash : pos - 1 ,
Text : "//export " + exportName ,
} ,
} ,
} ,
Name : & ast . Ident {
NamePos : pos ,
Name : "C." + localName ,
Obj : obj ,
} ,
Type : & ast . FuncType {
Func : pos ,
Params : & ast . FieldList {
Opening : pos ,
List : args ,
Closing : pos ,
} ,
} ,
}
if C . clang_isFunctionTypeVariadic ( cursorType ) != 0 {
decl . Doc . List = append ( decl . Doc . List , & ast . Comment {
Slash : pos - 1 ,
Text : "//go:variadic" ,
} )
}
for i := 0 ; i < numArgs ; i ++ {
arg := C . tinygo_clang_Cursor_getArgument ( c , C . uint ( i ) )
argName := getString ( C . tinygo_clang_getCursorSpelling ( arg ) )
argType := C . clang_getArgType ( cursorType , C . uint ( i ) )
if argName == "" {
argName = "$" + strconv . Itoa ( i )
}
args [ i ] = & ast . Field {
Names : [ ] * ast . Ident {
{
NamePos : pos ,
Name : argName ,
Obj : & ast . Object {
Kind : ast . Var ,
Name : argName ,
Decl : decl ,
} ,
} ,
} ,
Type : f . makeDecayingASTType ( argType , pos ) ,
}
}
resultType := C . tinygo_clang_getCursorResultType ( c )
if resultType . kind != C . CXType_Void {
decl . Type . Results = & ast . FieldList {
List : [ ] * ast . Field {
{
Type : f . makeASTType ( resultType , pos ) ,
} ,
} ,
}
}
obj . Decl = decl
return decl , stringSignature
case C . CXCursor_StructDecl , C . CXCursor_UnionDecl :
typ := f . makeASTRecordType ( c , pos )
typeName := "C." + name
typeExpr := typ . typeExpr
if typ . unionSize != 0 {
// Convert to a single-field struct type.
typeExpr = f . makeUnionField ( typ )
}
obj := & ast . Object {
Kind : ast . Typ ,
Name : typeName ,
}
typeSpec := & ast . TypeSpec {
Name : & ast . Ident {
NamePos : typ . pos ,
Name : typeName ,
Obj : obj ,
} ,
Type : typeExpr ,
}
obj . Decl = typeSpec
return typeSpec , typ
case C . CXCursor_TypedefDecl :
typeName := "C." + name
underlyingType := C . tinygo_clang_getTypedefDeclUnderlyingType ( c )
obj := & ast . Object {
Kind : ast . Typ ,
Name : typeName ,
}
typeSpec := & ast . TypeSpec {
Name : & ast . Ident {
NamePos : pos ,
Name : typeName ,
Obj : obj ,
} ,
Type : f . makeASTType ( underlyingType , pos ) ,
}
if underlyingType . kind != C . CXType_Enum {
typeSpec . Assign = pos
}
obj . Decl = typeSpec
return typeSpec , nil
case C . CXCursor_VarDecl :
cursorType := C . tinygo_clang_getCursorType ( c )
typeExpr := f . makeASTType ( cursorType , pos )
gen := & ast . GenDecl {
TokPos : pos ,
Tok : token . VAR ,
Lparen : token . NoPos ,
Rparen : token . NoPos ,
Doc : & ast . CommentGroup {
List : [ ] * ast . Comment {
{
Slash : pos - 1 ,
Text : "//go:extern " + name ,
} ,
} ,
} ,
}
obj := & ast . Object {
Kind : ast . Var ,
Name : "C." + name ,
}
valueSpec := & ast . ValueSpec {
Names : [ ] * ast . Ident { {
NamePos : pos ,
Name : "C." + name ,
Obj : obj ,
} } ,
Type : typeExpr ,
}
obj . Decl = valueSpec
gen . Specs = append ( gen . Specs , valueSpec )
return gen , nil
case C . CXCursor_MacroDefinition :
sourceRange := C . tinygo_clang_getCursorExtent ( c )
start := C . clang_getRangeStart ( sourceRange )
end := C . clang_getRangeEnd ( sourceRange )
var file , endFile C . CXFile
var startOffset , endOffset C . unsigned
C . clang_getExpansionLocation ( start , & file , nil , nil , & startOffset )
if file == nil {
f . addError ( pos , "internal error: could not find file where macro is defined" )
return nil , nil
}
C . clang_getExpansionLocation ( end , & endFile , nil , nil , & endOffset )
if file != endFile {
f . addError ( pos , "internal error: expected start and end location of a macro to be in the same file" )
return nil , nil
}
if startOffset > endOffset {
f . addError ( pos , "internal error: start offset of macro is after end offset" )
return nil , nil
}
// read file contents and extract the relevant byte range
tu := C . tinygo_clang_Cursor_getTranslationUnit ( c )
var size C . size_t
sourcePtr := C . clang_getFileContents ( tu , file , & size )
if endOffset >= C . uint ( size ) {
f . addError ( pos , "internal error: end offset of macro lies after end of file" )
return nil , nil
}
source := string ( ( ( * [ 1 << 28 ] byte ) ( unsafe . Pointer ( sourcePtr ) ) ) [ startOffset : endOffset : endOffset ] )
if ! strings . HasPrefix ( source , name ) {
f . addError ( pos , fmt . Sprintf ( "internal error: expected macro value to start with %#v, got %#v" , name , source ) )
return nil , nil
}
value := source [ len ( name ) : ]
// Try to convert this #define into a Go constant expression.
expr , scannerError := parseConst ( pos + token . Pos ( len ( name ) ) , f . fset , value )
if scannerError != nil {
f . errors = append ( f . errors , * scannerError )
return nil , nil
}
gen := & ast . GenDecl {
TokPos : token . NoPos ,
Tok : token . CONST ,
Lparen : token . NoPos ,
Rparen : token . NoPos ,
}
obj := & ast . Object {
Kind : ast . Con ,
Name : "C." + name ,
}
valueSpec := & ast . ValueSpec {
Names : [ ] * ast . Ident { {
NamePos : pos ,
Name : "C." + name ,
Obj : obj ,
} } ,
Values : [ ] ast . Expr { expr } ,
}
obj . Decl = valueSpec
gen . Specs = append ( gen . Specs , valueSpec )
return gen , nil
case C . CXCursor_EnumDecl :
obj := & ast . Object {
Kind : ast . Typ ,
Name : "C." + name ,
}
underlying := C . tinygo_clang_getEnumDeclIntegerType ( c )
// TODO: gc's CGo implementation uses types such as `uint32` for enums
// instead of types such as C.int, which are used here.
typeSpec := & ast . TypeSpec {
Name : & ast . Ident {
NamePos : pos ,
Name : "C." + name ,
Obj : obj ,
} ,
Assign : pos ,
Type : f . makeASTType ( underlying , pos ) ,
}
obj . Decl = typeSpec
return typeSpec , nil
case C . CXCursor_EnumConstantDecl :
value := C . tinygo_clang_getEnumConstantDeclValue ( c )
expr := & ast . BasicLit {
ValuePos : pos ,
Kind : token . INT ,
Value : strconv . FormatInt ( int64 ( value ) , 10 ) ,
}
gen := & ast . GenDecl {
TokPos : token . NoPos ,
Tok : token . CONST ,
Lparen : token . NoPos ,
Rparen : token . NoPos ,
}
obj := & ast . Object {
Kind : ast . Con ,
Name : "C." + name ,
}
valueSpec := & ast . ValueSpec {
Names : [ ] * ast . Ident { {
NamePos : pos ,
Name : "C." + name ,
Obj : obj ,
} } ,
Values : [ ] ast . Expr { expr } ,
}
obj . Decl = valueSpec
gen . Specs = append ( gen . Specs , valueSpec )
return gen , nil
default :
f . addError ( pos , fmt . Sprintf ( "internal error: unknown cursor type: %d" , kind ) )
return nil , nil
}
}
func getString ( clangString C . CXString ) ( s string ) {
rawString := C . clang_getCString ( clangString )
s = C . GoString ( rawString )
C . clang_disposeString ( clangString )
return
}
//export tinygo_clang_globals_visitor
func tinygo_clang_globals_visitor ( c , parent C . GoCXCursor , client_data C . CXClientData ) C . int {
f := storedRefs . Get ( unsafe . Pointer ( client_data ) ) . ( * cgoFile )
switch C . tinygo_clang_getCursorKind ( c ) {
case C . CXCursor_FunctionDecl :
name := getString ( C . tinygo_clang_getCursorSpelling ( c ) )
f . names [ name ] = c
case C . CXCursor_StructDecl :
name := getString ( C . tinygo_clang_getCursorSpelling ( c ) )
if name != "" {
f . names [ "struct_" + name ] = c
}
case C . CXCursor_UnionDecl :
name := getString ( C . tinygo_clang_getCursorSpelling ( c ) )
if name != "" {
f . names [ "union_" + name ] = c
}
case C . CXCursor_TypedefDecl :
typedefType := C . tinygo_clang_getCursorType ( c )
name := getString ( C . clang_getTypedefName ( typedefType ) )
f . names [ name ] = c
case C . CXCursor_VarDecl :
name := getString ( C . tinygo_clang_getCursorSpelling ( c ) )
f . names [ name ] = c
case C . CXCursor_MacroDefinition :
name := getString ( C . tinygo_clang_getCursorSpelling ( c ) )
f . names [ name ] = c
case C . CXCursor_EnumDecl :
name := getString ( C . tinygo_clang_getCursorSpelling ( c ) )
if name != "" {
// Named enum, which can be referenced from Go using C.enum_foo.
f . names [ "enum_" + name ] = c
}
// The enum fields are in global scope, so recurse to visit them.
return C . CXChildVisit_Recurse
case C . CXCursor_EnumConstantDecl :
// We arrive here because of the "Recurse" above.
name := getString ( C . tinygo_clang_getCursorSpelling ( c ) )
f . names [ name ] = c
}
return C . CXChildVisit_Continue
}
// Get the precise location in the source code. Used for uniquely identifying
// source locations.
func ( f * cgoFile ) getUniqueLocationID ( pos token . Pos , cursor C . GoCXCursor ) interface { } {
clangLocation := C . tinygo_clang_getCursorLocation ( cursor )
var file C . CXFile
var line C . unsigned
var column C . unsigned
C . clang_getFileLocation ( clangLocation , & file , & line , & column , nil )
location := token . Position {
Filename : getString ( C . clang_getFileName ( file ) ) ,
Line : int ( line ) ,
Column : int ( column ) ,
}
if location . Filename == "" || location . Line == 0 {
// Not sure when this would happen, but protect from it anyway.
f . addError ( pos , "could not find file/line information" )
}
return location
}
// getCursorPosition returns a usable token.Pos from a libclang cursor.
func ( p * cgoPackage ) getCursorPosition ( cursor C . GoCXCursor ) token . Pos {
return p . getClangLocationPosition ( C . tinygo_clang_getCursorLocation ( cursor ) , C . tinygo_clang_Cursor_getTranslationUnit ( cursor ) )
}
// getClangLocationPosition returns a usable token.Pos based on a libclang
// location and translation unit. If the file for this cursor has not been seen
// before, it is read from libclang (which already has the file in memory) and
// added to the token.FileSet.
func ( p * cgoPackage ) getClangLocationPosition ( location C . CXSourceLocation , tu C . CXTranslationUnit ) token . Pos {
var file C . CXFile
var line C . unsigned
var column C . unsigned
var offset C . unsigned
C . clang_getExpansionLocation ( location , & file , & line , & column , & offset )
if line == 0 || file == nil {
// Invalid token.
return token . NoPos
}
filename := getString ( C . clang_getFileName ( file ) )
if _ , ok := p . tokenFiles [ filename ] ; ! ok {
// File has not been seen before in this package, add line information
// now by reading the file from libclang.
var size C . size_t
sourcePtr := C . clang_getFileContents ( tu , file , & size )
source := ( ( * [ 1 << 28 ] byte ) ( unsafe . Pointer ( sourcePtr ) ) ) [ : size : size ]
lines := [ ] int { 0 }
for i := 0 ; i < len ( source ) - 1 ; i ++ {
if source [ i ] == '\n' {
lines = append ( lines , i + 1 )
}
}
f := p . fset . AddFile ( filename , - 1 , int ( size ) )
f . SetLines ( lines )
p . tokenFiles [ filename ] = f
// Add dummy file AST, to satisfy the type checker.
astFile := & ast . File {
Package : f . Pos ( 0 ) ,
Name : ast . NewIdent ( p . packageName ) ,
}
setASTFileFields ( astFile , f . Pos ( 0 ) , f . Pos ( int ( size ) ) )
p . cgoFiles = append ( p . cgoFiles , astFile )
}
positionFile := p . tokenFiles [ filename ]
// Check for alternative line/column information (set with a line directive).
var filename2String C . CXString
var line2 C . unsigned
var column2 C . unsigned
C . clang_getPresumedLocation ( location , & filename2String , & line2 , & column2 )
filename2 := getString ( filename2String )
if filename2 != filename || line2 != line || column2 != column {
// The location was changed with a preprocessor directive.
// TODO: this only works for locations that are added in order. Adding
// line/column info to a file that already has line/column info after
// the given offset is ignored.
positionFile . AddLineColumnInfo ( int ( offset ) , filename2 , int ( line2 ) , int ( column2 ) )
}
return positionFile . Pos ( int ( offset ) )
}
// addError is a utility function to add an error to the list of errors. It will
// convert the token position to a line/column position first, and call
// addErrorAt.
func ( p * cgoPackage ) addError ( pos token . Pos , msg string ) {
p . addErrorAt ( p . fset . PositionFor ( pos , true ) , msg )
}
// addErrorAfter is like addError, but adds the text `after` to the source
// location.
func ( p * cgoPackage ) addErrorAfter ( pos token . Pos , after , msg string ) {
position := p . fset . PositionFor ( pos , true )
lines := strings . Split ( after , "\n" )
if len ( lines ) != 1 {
// Adjust lines.
// For why we can't just do pos+token.Pos(len(after)), see:
// https://github.com/golang/go/issues/35803
position . Line += len ( lines ) - 1
position . Column = len ( lines [ len ( lines ) - 1 ] ) + 1
} else {
position . Column += len ( after )
}
p . addErrorAt ( position , msg )
}
// addErrorAt is a utility function to add an error to the list of errors.
func ( p * cgoPackage ) addErrorAt ( position token . Position , msg string ) {
if filepath . IsAbs ( position . Filename ) {
// Relative paths for readability, like other Go parser errors.
relpath , err := filepath . Rel ( p . currentDir , position . Filename )
if err == nil {
position . Filename = relpath
}
}
p . errors = append ( p . errors , scanner . Error {
Pos : position ,
Msg : msg ,
} )
}
// makeDecayingASTType does the same as makeASTType but takes care of decaying
// types (arrays in function parameters, etc). It is otherwise identical to
// makeASTType.
func ( f * cgoFile ) makeDecayingASTType ( typ C . CXType , pos token . Pos ) ast . Expr {
// Strip typedefs, if any.
underlyingType := typ
if underlyingType . kind == C . CXType_Elaborated {
// Starting with LLVM 16, the elaborated type is used for more types.
// According to the Clang documentation, the elaborated type has no
// semantic meaning so can be stripped (it is used to better convey type
// name information).
// Source:
// https://clang.llvm.org/doxygen/classclang_1_1ElaboratedType.html#details
// > The type itself is always "sugar", used to express what was written
// > in the source code but containing no additional semantic information.
underlyingType = C . clang_Type_getNamedType ( underlyingType )
}
if underlyingType . kind == C . CXType_Typedef {
c := C . tinygo_clang_getTypeDeclaration ( underlyingType )
underlyingType = C . tinygo_clang_getTypedefDeclUnderlyingType ( c )
// TODO: support a chain of typedefs. At the moment, it seems to get
// stuck in an endless loop when trying to get to the most underlying
// type.
}
// Check for decaying type. An example would be an array type in a
// parameter. This declaration:
// void foo(char buf[6]);
// is the same as this one:
// void foo(char *buf);
// But this one:
// void bar(char buf[6][4]);
// equals this:
// void bar(char *buf[4]);
// so not all array dimensions should be stripped, just the first one.
// TODO: there are more kinds of decaying types.
if underlyingType . kind == C . CXType_ConstantArray {
// Apply type decaying.
pointeeType := C . clang_getElementType ( underlyingType )
return & ast . StarExpr {
Star : pos ,
X : f . makeASTType ( pointeeType , pos ) ,
}
}
return f . makeASTType ( typ , pos )
}
// makeASTType return the ast.Expr for the given libclang type. In other words,
// it converts a libclang type to a type in the Go AST.
func ( f * cgoFile ) makeASTType ( typ C . CXType , pos token . Pos ) ast . Expr {
var typeName string
switch typ . kind {
case C . CXType_Char_S , C . CXType_Char_U :
typeName = "C.char"
case C . CXType_SChar :
typeName = "C.schar"
case C . CXType_UChar :
typeName = "C.uchar"
case C . CXType_Short :
typeName = "C.short"
case C . CXType_UShort :
typeName = "C.ushort"
case C . CXType_Int :
typeName = "C.int"
case C . CXType_UInt :
typeName = "C.uint"
case C . CXType_Long :
typeName = "C.long"
case C . CXType_ULong :
typeName = "C.ulong"
case C . CXType_LongLong :
typeName = "C.longlong"
case C . CXType_ULongLong :
typeName = "C.ulonglong"
case C . CXType_Bool :
typeName = "bool"
case C . CXType_Float , C . CXType_Double , C . CXType_LongDouble :
switch C . clang_Type_getSizeOf ( typ ) {
case 4 :
typeName = "float32"
case 8 :
typeName = "float64"
default :
// Don't do anything, rely on the fallback code to show a somewhat
// sensible error message like "undeclared name: C.long double".
}
case C . CXType_Complex :
switch C . clang_Type_getSizeOf ( typ ) {
case 8 :
typeName = "complex64"
case 16 :
typeName = "complex128"
}
case C . CXType_Pointer :
pointeeType := C . clang_getPointeeType ( typ )
if pointeeType . kind == C . CXType_Void {
// void* type is translated to Go as unsafe.Pointer
return & ast . SelectorExpr {
X : & ast . Ident {
NamePos : pos ,
Name : "unsafe" ,
} ,
Sel : & ast . Ident {
NamePos : pos ,
Name : "Pointer" ,
} ,
}
}
return & ast . StarExpr {
Star : pos ,
X : f . makeASTType ( pointeeType , pos ) ,
}
case C . CXType_ConstantArray :
return & ast . ArrayType {
Lbrack : pos ,
Len : & ast . BasicLit {
ValuePos : pos ,
Kind : token . INT ,
Value : strconv . FormatInt ( int64 ( C . clang_getArraySize ( typ ) ) , 10 ) ,
} ,
Elt : f . makeASTType ( C . clang_getElementType ( typ ) , pos ) ,
}
case C . CXType_FunctionProto :
// Be compatible with gc, which uses the *[0]byte type for function
// pointer types.
// Return type [0]byte because this is a function type, not a pointer to
// this function type.
return & ast . ArrayType {
Lbrack : pos ,
Len : & ast . BasicLit {
ValuePos : pos ,
Kind : token . INT ,
Value : "0" ,
} ,
Elt : & ast . Ident {
NamePos : pos ,
Name : "byte" ,
} ,
}
case C . CXType_Typedef :
name := getString ( C . clang_getTypedefName ( typ ) )
c := C . tinygo_clang_getTypeDeclaration ( typ )
return & ast . Ident {
NamePos : pos ,
Name : f . getASTDeclName ( name , c , false ) ,
}
case C . CXType_Elaborated :
underlying := C . clang_Type_getNamedType ( typ )
switch underlying . kind {
case C . CXType_Record :
return f . makeASTType ( underlying , pos )
case C . CXType_Enum :
return f . makeASTType ( underlying , pos )
case C . CXType_Typedef :
return f . makeASTType ( underlying , pos )
default :
typeKindSpelling := getString ( C . clang_getTypeKindSpelling ( underlying . kind ) )
f . addError ( pos , fmt . Sprintf ( "unknown elaborated type (libclang type kind %s)" , typeKindSpelling ) )
typeName = "<unknown>"
}
case C . CXType_Record :
cursor := C . tinygo_clang_getTypeDeclaration ( typ )
name := getString ( C . tinygo_clang_getCursorSpelling ( cursor ) )
var cgoRecordPrefix string
switch C . tinygo_clang_getCursorKind ( cursor ) {
case C . CXCursor_StructDecl :
cgoRecordPrefix = "struct_"
case C . CXCursor_UnionDecl :
cgoRecordPrefix = "union_"
default :
// makeASTRecordType will create an appropriate error.
cgoRecordPrefix = "record_"
}
if name == "" || C . tinygo_clang_Cursor_isAnonymous ( cursor ) != 0 {
// Anonymous record, probably inside a typedef.
location := f . getUniqueLocationID ( pos , cursor )
name = f . getUnnamedDeclName ( "_Ctype_" + cgoRecordPrefix + "__" , location )
} else {
name = cgoRecordPrefix + name
}
return & ast . Ident {
NamePos : pos ,
Name : f . getASTDeclName ( name , cursor , false ) ,
}
case C . CXType_Enum :
cursor := C . tinygo_clang_getTypeDeclaration ( typ )
name := getString ( C . tinygo_clang_getCursorSpelling ( cursor ) )
if name == "" {
// Anonymous enum, probably inside a typedef.
location := f . getUniqueLocationID ( pos , cursor )
name = f . getUnnamedDeclName ( "_Ctype_enum___" , location )
} else {
name = "enum_" + name
}
return & ast . Ident {
NamePos : pos ,
Name : f . getASTDeclName ( name , cursor , false ) ,
}
}
if typeName == "" {
// Report this as an error.
typeSpelling := getString ( C . clang_getTypeSpelling ( typ ) )
typeKindSpelling := getString ( C . clang_getTypeKindSpelling ( typ . kind ) )
f . addError ( pos , fmt . Sprintf ( "unknown C type: %v (libclang type kind %s)" , typeSpelling , typeKindSpelling ) )
typeName = "C.<unknown>"
}
return & ast . Ident {
NamePos : pos ,
Name : typeName ,
}
}
// getIntegerType returns an AST node that defines types such as C.int.
func ( p * cgoPackage ) getIntegerType ( name string , cursor clangCursor ) * ast . TypeSpec {
pos := p . getCursorPosition ( cursor )
// Find a Go type that matches the size and signedness of the given C type.
underlyingType := C . tinygo_clang_getTypedefDeclUnderlyingType ( cursor )
var goName string
typeSize := C . clang_Type_getSizeOf ( underlyingType )
switch name {
case "C.char" :
if typeSize != 1 {
// This happens for some very special purpose architectures
// (DSPs etc.) that are not currently targeted.
// https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/
p . addError ( pos , fmt . Sprintf ( "unknown char width: %d" , typeSize ) )
}
switch underlyingType . kind {
case C . CXType_Char_S :
goName = "int8"
case C . CXType_Char_U :
goName = "uint8"
}
case "C.schar" , "C.short" , "C.int" , "C.long" , "C.longlong" :
switch typeSize {
case 1 :
goName = "int8"
case 2 :
goName = "int16"
case 4 :
goName = "int32"
case 8 :
goName = "int64"
}
case "C.uchar" , "C.ushort" , "C.uint" , "C.ulong" , "C.ulonglong" :
switch typeSize {
case 1 :
goName = "uint8"
case 2 :
goName = "uint16"
case 4 :
goName = "uint32"
case 8 :
goName = "uint64"
}
}
if goName == "" { // should not happen
p . addError ( pos , "internal error: did not find Go type for C type " + name )
goName = "int"
}
// Construct an *ast.TypeSpec for this type.
obj := & ast . Object {
Kind : ast . Typ ,
Name : name ,
}
spec := & ast . TypeSpec {
Name : & ast . Ident {
NamePos : pos ,
Name : name ,
Obj : obj ,
} ,
Type : & ast . Ident {
NamePos : pos ,
Name : goName ,
} ,
}
obj . Decl = spec
return spec
}
// makeASTRecordType parses a C record (struct or union) and translates it into
// a Go struct type.
func ( f * cgoFile ) makeASTRecordType ( cursor C . GoCXCursor , pos token . Pos ) * elaboratedTypeInfo {
fieldList := & ast . FieldList {
Opening : pos ,
Closing : pos ,
}
var bitfieldList [ ] bitfieldInfo
inBitfield := false
bitfieldNum := 0
ref := storedRefs . Put ( struct {
fieldList * ast . FieldList
file * cgoFile
inBitfield * bool
bitfieldNum * int
bitfieldList * [ ] bitfieldInfo
} { fieldList , f , & inBitfield , & bitfieldNum , & bitfieldList } )
defer storedRefs . Remove ( ref )
C . tinygo_clang_visitChildren ( cursor , C . CXCursorVisitor ( C . tinygo_clang_struct_visitor ) , C . CXClientData ( ref ) )
renameFieldKeywords ( fieldList )
switch C . tinygo_clang_getCursorKind ( cursor ) {
case C . CXCursor_StructDecl :
return & elaboratedTypeInfo {
typeExpr : & ast . StructType {
Struct : pos ,
Fields : fieldList ,
} ,
pos : pos ,
bitfields : bitfieldList ,
}
case C . CXCursor_UnionDecl :
typeInfo := & elaboratedTypeInfo {
typeExpr : & ast . StructType {
Struct : pos ,
Fields : fieldList ,
} ,
pos : pos ,
bitfields : bitfieldList ,
}
if len ( fieldList . List ) <= 1 {
// Useless union, treat it as a regular struct.
return typeInfo
}
if bitfieldList != nil {
// This is valid C... but please don't do this.
f . addError ( pos , "bitfield in a union is not supported" )
}
typ := C . tinygo_clang_getCursorType ( cursor )
alignInBytes := int64 ( C . clang_Type_getAlignOf ( typ ) )
sizeInBytes := int64 ( C . clang_Type_getSizeOf ( typ ) )
if sizeInBytes == 0 {
f . addError ( pos , "zero-length union is not supported" )
}
typeInfo . unionSize = sizeInBytes
typeInfo . unionAlign = alignInBytes
return typeInfo
default :
cursorKind := C . tinygo_clang_getCursorKind ( cursor )
cursorKindSpelling := getString ( C . clang_getCursorKindSpelling ( cursorKind ) )
f . addError ( pos , fmt . Sprintf ( "expected StructDecl or UnionDecl, not %s" , cursorKindSpelling ) )
return & elaboratedTypeInfo {
typeExpr : & ast . StructType {
Struct : pos ,
} ,
pos : pos ,
}
}
}
//export tinygo_clang_struct_visitor
func tinygo_clang_struct_visitor ( c , parent C . GoCXCursor , client_data C . CXClientData ) C . int {
passed := storedRefs . Get ( unsafe . Pointer ( client_data ) ) . ( struct {
fieldList * ast . FieldList
file * cgoFile
inBitfield * bool
bitfieldNum * int
bitfieldList * [ ] bitfieldInfo
} )
fieldList := passed . fieldList
f := passed . file
inBitfield := passed . inBitfield
bitfieldNum := passed . bitfieldNum
bitfieldList := passed . bitfieldList
pos := f . getCursorPosition ( c )
switch cursorKind := C . tinygo_clang_getCursorKind ( c ) ; cursorKind {
case C . CXCursor_FieldDecl :
// Expected. This is a regular field.
case C . CXCursor_StructDecl , C . CXCursor_UnionDecl :
// Ignore. The next field will be the struct/union itself.
return C . CXChildVisit_Continue
default :
cursorKindSpelling := getString ( C . clang_getCursorKindSpelling ( cursorKind ) )
f . addError ( pos , fmt . Sprintf ( "expected FieldDecl in struct or union, not %s" , cursorKindSpelling ) )
return C . CXChildVisit_Continue
}
name := getString ( C . tinygo_clang_getCursorSpelling ( c ) )
if name == "" {
// Assume this is a bitfield of 0 bits.
// Warning: this is not necessarily true!
return C . CXChildVisit_Continue
}
typ := C . tinygo_clang_getCursorType ( c )
field := & ast . Field {
Type : f . makeASTType ( typ , f . getCursorPosition ( c ) ) ,
}
offsetof := int64 ( C . clang_Type_getOffsetOf ( C . tinygo_clang_getCursorType ( parent ) , C . CString ( name ) ) )
alignOf := int64 ( C . clang_Type_getAlignOf ( typ ) * 8 )
bitfieldOffset := offsetof % alignOf
if bitfieldOffset != 0 {
if C . tinygo_clang_Cursor_isBitField ( c ) != 1 {
f . addError ( pos , "expected a bitfield" )
return C . CXChildVisit_Continue
}
if ! * inBitfield {
* bitfieldNum ++
}
bitfieldName := "__bitfield_" + strconv . Itoa ( * bitfieldNum )
prevField := fieldList . List [ len ( fieldList . List ) - 1 ]
if ! * inBitfield {
// The previous element also was a bitfield, but wasn't noticed
// then. Add it now.
* inBitfield = true
* bitfieldList = append ( * bitfieldList , bitfieldInfo {
field : prevField ,
name : prevField . Names [ 0 ] . Name ,
startBit : 0 ,
pos : prevField . Names [ 0 ] . NamePos ,
} )
prevField . Names [ 0 ] . Name = bitfieldName
prevField . Names [ 0 ] . Obj . Name = bitfieldName
}
prevBitfield := & ( * bitfieldList ) [ len ( * bitfieldList ) - 1 ]
prevBitfield . endBit = bitfieldOffset
* bitfieldList = append ( * bitfieldList , bitfieldInfo {
field : prevField ,
name : name ,
startBit : bitfieldOffset ,
pos : pos ,
} )
return C . CXChildVisit_Continue
}
* inBitfield = false
field . Names = [ ] * ast . Ident {
{
NamePos : pos ,
Name : name ,
Obj : & ast . Object {
Kind : ast . Var ,
Name : name ,
Decl : field ,
} ,
} ,
}
fieldList . List = append ( fieldList . List , field )
return C . CXChildVisit_Continue
}
//export tinygo_clang_inclusion_visitor
func tinygo_clang_inclusion_visitor ( includedFile C . CXFile , inclusionStack * C . CXSourceLocation , includeLen C . unsigned , clientData C . CXClientData ) {
callback := storedRefs . Get ( unsafe . Pointer ( clientData ) ) . ( func ( C . CXFile ) )
callback ( includedFile )
}