github.com/aykevl/tinygo@v0.5.0/loader/libclang.go (about) 1 package loader 2 3 // This file parses a fragment of C with libclang and stores the result for AST 4 // modification. It does not touch the AST itself. 5 6 import ( 7 "go/ast" 8 "go/scanner" 9 "go/token" 10 "path/filepath" 11 "strconv" 12 "strings" 13 "unsafe" 14 ) 15 16 /* 17 #include <clang-c/Index.h> // if this fails, install libclang-8-dev 18 #include <stdlib.h> 19 #include <stdint.h> 20 21 // This struct should be ABI-compatible on all platforms (uintptr_t has the same 22 // alignment etc. as void*) but does not include void* pointers that are not 23 // always real pointers. 24 // The Go garbage collector assumes that all non-nil pointer-typed integers are 25 // actually pointers. This is not always true, as data[1] often contains 0x1, 26 // which is clearly not a valid pointer. Usually the GC won't catch this issue, 27 // but occasionally it will leading to a crash with a vague error message. 28 typedef struct { 29 enum CXCursorKind kind; 30 int xdata; 31 uintptr_t data[3]; 32 } GoCXCursor; 33 34 // Forwarding functions. They are implemented in libclang_stubs.c and forward to 35 // the real functions without doing anything else, thus they are entirely 36 // compatible with the versions without tinygo_ prefix. The only difference is 37 // the CXCursor type, which has been replaced with GoCXCursor. 38 GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu); 39 unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data); 40 CXString tinygo_clang_getCursorSpelling(GoCXCursor c); 41 enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c); 42 CXType tinygo_clang_getCursorType(GoCXCursor c); 43 GoCXCursor tinygo_clang_getTypeDeclaration(CXType t); 44 CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c); 45 CXType tinygo_clang_getCursorResultType(GoCXCursor c); 46 int tinygo_clang_Cursor_getNumArguments(GoCXCursor c); 47 GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i); 48 49 int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); 50 int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); 51 */ 52 import "C" 53 54 // refMap stores references to types, used for clang_visitChildren. 55 var refMap RefMap 56 57 var diagnosticSeverity = [...]string{ 58 C.CXDiagnostic_Ignored: "ignored", 59 C.CXDiagnostic_Note: "note", 60 C.CXDiagnostic_Warning: "warning", 61 C.CXDiagnostic_Error: "error", 62 C.CXDiagnostic_Fatal: "fatal", 63 } 64 65 func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilename string, posLine int) []error { 66 index := C.clang_createIndex(0, 0) 67 defer C.clang_disposeIndex(index) 68 69 filenameC := C.CString(posFilename + "!cgo.c") 70 defer C.free(unsafe.Pointer(filenameC)) 71 72 fragmentC := C.CString(fragment) 73 defer C.free(unsafe.Pointer(fragmentC)) 74 75 unsavedFile := C.struct_CXUnsavedFile{ 76 Filename: filenameC, 77 Length: C.ulong(len(fragment)), 78 Contents: fragmentC, 79 } 80 81 // convert Go slice of strings to C array of strings. 82 cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0)))) 83 defer C.free(cmdargsC) 84 cmdargs := (*[1 << 16]*C.char)(cmdargsC) 85 for i, cflag := range cflags { 86 s := C.CString(cflag) 87 cmdargs[i] = s 88 defer C.free(unsafe.Pointer(s)) 89 } 90 91 var unit C.CXTranslationUnit 92 errCode := C.clang_parseTranslationUnit2( 93 index, 94 filenameC, 95 (**C.char)(cmdargsC), C.int(len(cflags)), // command line args 96 &unsavedFile, 1, // unsaved files 97 C.CXTranslationUnit_None, 98 &unit) 99 if errCode != 0 { 100 panic("loader: failed to parse source with libclang") 101 } 102 defer C.clang_disposeTranslationUnit(unit) 103 104 if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 { 105 errs := []error{} 106 addDiagnostic := func(diagnostic C.CXDiagnostic) { 107 spelling := getString(C.clang_getDiagnosticSpelling(diagnostic)) 108 severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)] 109 location := C.clang_getDiagnosticLocation(diagnostic) 110 var file C.CXFile 111 var line C.unsigned 112 var column C.unsigned 113 var offset C.unsigned 114 C.clang_getExpansionLocation(location, &file, &line, &column, &offset) 115 filename := getString(C.clang_getFileName(file)) 116 if filename == posFilename+"!cgo.c" { 117 // Adjust errors from the `import "C"` snippet. 118 // Note: doesn't adjust filenames inside the error message 119 // itself. 120 filename = posFilename 121 line += C.uint(posLine) 122 offset = 0 // hard to calculate 123 } else if filepath.IsAbs(filename) { 124 // Relative paths for readability, like other Go parser errors. 125 relpath, err := filepath.Rel(info.Program.Dir, filename) 126 if err == nil { 127 filename = relpath 128 } 129 } 130 errs = append(errs, &scanner.Error{ 131 Pos: token.Position{ 132 Filename: filename, 133 Offset: int(offset), 134 Line: int(line), 135 Column: int(column), 136 }, 137 Msg: severity + ": " + spelling, 138 }) 139 } 140 for i := 0; i < numDiagnostics; i++ { 141 diagnostic := C.clang_getDiagnostic(unit, C.uint(i)) 142 addDiagnostic(diagnostic) 143 144 // Child diagnostics (like notes on redefinitions). 145 diagnostics := C.clang_getChildDiagnostics(diagnostic) 146 for j := 0; j < int(C.clang_getNumDiagnosticsInSet(diagnostics)); j++ { 147 addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j))) 148 } 149 } 150 return errs 151 } 152 153 ref := refMap.Put(info) 154 defer refMap.Remove(ref) 155 cursor := C.tinygo_clang_getTranslationUnitCursor(unit) 156 C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref)) 157 158 return nil 159 } 160 161 //export tinygo_clang_globals_visitor 162 func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { 163 info := refMap.Get(unsafe.Pointer(client_data)).(*fileInfo) 164 kind := C.tinygo_clang_getCursorKind(c) 165 switch kind { 166 case C.CXCursor_FunctionDecl: 167 name := getString(C.tinygo_clang_getCursorSpelling(c)) 168 cursorType := C.tinygo_clang_getCursorType(c) 169 if C.clang_isFunctionTypeVariadic(cursorType) != 0 { 170 return C.CXChildVisit_Continue // not supported 171 } 172 numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c)) 173 fn := &functionInfo{} 174 info.functions[name] = fn 175 for i := 0; i < numArgs; i++ { 176 arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i)) 177 argName := getString(C.tinygo_clang_getCursorSpelling(arg)) 178 argType := C.clang_getArgType(cursorType, C.uint(i)) 179 if argName == "" { 180 argName = "$" + strconv.Itoa(i) 181 } 182 fn.args = append(fn.args, paramInfo{ 183 name: argName, 184 typeExpr: info.makeASTType(argType), 185 }) 186 } 187 resultType := C.tinygo_clang_getCursorResultType(c) 188 if resultType.kind != C.CXType_Void { 189 fn.results = &ast.FieldList{ 190 List: []*ast.Field{ 191 &ast.Field{ 192 Type: info.makeASTType(resultType), 193 }, 194 }, 195 } 196 } 197 case C.CXCursor_TypedefDecl: 198 typedefType := C.tinygo_clang_getCursorType(c) 199 name := getString(C.clang_getTypedefName(typedefType)) 200 underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c) 201 expr := info.makeASTType(underlyingType) 202 if strings.HasPrefix(name, "_Cgo_") { 203 expr := expr.(*ast.Ident) 204 typeSize := C.clang_Type_getSizeOf(underlyingType) 205 switch expr.Name { 206 // TODO: plain char (may be signed or unsigned) 207 case "C.schar", "C.short", "C.int", "C.long", "C.longlong": 208 switch typeSize { 209 case 1: 210 expr.Name = "int8" 211 case 2: 212 expr.Name = "int16" 213 case 4: 214 expr.Name = "int32" 215 case 8: 216 expr.Name = "int64" 217 } 218 case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong": 219 switch typeSize { 220 case 1: 221 expr.Name = "uint8" 222 case 2: 223 expr.Name = "uint16" 224 case 4: 225 expr.Name = "uint32" 226 case 8: 227 expr.Name = "uint64" 228 } 229 } 230 } 231 info.typedefs[name] = &typedefInfo{ 232 typeExpr: expr, 233 } 234 case C.CXCursor_VarDecl: 235 name := getString(C.tinygo_clang_getCursorSpelling(c)) 236 cursorType := C.tinygo_clang_getCursorType(c) 237 info.globals[name] = &globalInfo{ 238 typeExpr: info.makeASTType(cursorType), 239 } 240 } 241 return C.CXChildVisit_Continue 242 } 243 244 func getString(clangString C.CXString) (s string) { 245 rawString := C.clang_getCString(clangString) 246 s = C.GoString(rawString) 247 C.clang_disposeString(clangString) 248 return 249 } 250 251 // makeASTType return the ast.Expr for the given libclang type. In other words, 252 // it converts a libclang type to a type in the Go AST. 253 func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr { 254 var typeName string 255 switch typ.kind { 256 case C.CXType_SChar: 257 typeName = "C.schar" 258 case C.CXType_UChar: 259 typeName = "C.uchar" 260 case C.CXType_Short: 261 typeName = "C.short" 262 case C.CXType_UShort: 263 typeName = "C.ushort" 264 case C.CXType_Int: 265 typeName = "C.int" 266 case C.CXType_UInt: 267 typeName = "C.uint" 268 case C.CXType_Long: 269 typeName = "C.long" 270 case C.CXType_ULong: 271 typeName = "C.ulong" 272 case C.CXType_LongLong: 273 typeName = "C.longlong" 274 case C.CXType_ULongLong: 275 typeName = "C.ulonglong" 276 case C.CXType_Bool: 277 typeName = "bool" 278 case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble: 279 switch C.clang_Type_getSizeOf(typ) { 280 case 4: 281 typeName = "float32" 282 case 8: 283 typeName = "float64" 284 default: 285 // Don't do anything, rely on the fallback code to show a somewhat 286 // sensible error message like "undeclared name: C.long double". 287 } 288 case C.CXType_Complex: 289 switch C.clang_Type_getSizeOf(typ) { 290 case 8: 291 typeName = "complex64" 292 case 16: 293 typeName = "complex128" 294 } 295 case C.CXType_Pointer: 296 return &ast.StarExpr{ 297 Star: info.importCPos, 298 X: info.makeASTType(C.clang_getPointeeType(typ)), 299 } 300 case C.CXType_ConstantArray: 301 return &ast.ArrayType{ 302 Lbrack: info.importCPos, 303 Len: &ast.BasicLit{ 304 ValuePos: info.importCPos, 305 Kind: token.INT, 306 Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10), 307 }, 308 Elt: info.makeASTType(C.clang_getElementType(typ)), 309 } 310 case C.CXType_FunctionProto: 311 // Be compatible with gc, which uses the *[0]byte type for function 312 // pointer types. 313 // Return type [0]byte because this is a function type, not a pointer to 314 // this function type. 315 return &ast.ArrayType{ 316 Lbrack: info.importCPos, 317 Len: &ast.BasicLit{ 318 ValuePos: info.importCPos, 319 Kind: token.INT, 320 Value: "0", 321 }, 322 Elt: &ast.Ident{ 323 NamePos: info.importCPos, 324 Name: "byte", 325 }, 326 } 327 case C.CXType_Typedef: 328 typedefName := getString(C.clang_getTypedefName(typ)) 329 return &ast.Ident{ 330 NamePos: info.importCPos, 331 Name: "C." + typedefName, 332 } 333 case C.CXType_Elaborated: 334 underlying := C.clang_Type_getNamedType(typ) 335 switch underlying.kind { 336 case C.CXType_Record: 337 cursor := C.tinygo_clang_getTypeDeclaration(typ) 338 name := getString(C.tinygo_clang_getCursorSpelling(cursor)) 339 // It is possible that this is a recursive definition, for example 340 // in linked lists (structs contain a pointer to the next element 341 // of the same type). If the name exists in info.elaboratedTypes, 342 // it is being processed, although it may not be fully defined yet. 343 if _, ok := info.elaboratedTypes[name]; !ok { 344 info.elaboratedTypes[name] = nil // predeclare (to avoid endless recursion) 345 info.elaboratedTypes[name] = info.makeASTType(underlying) 346 } 347 return &ast.Ident{ 348 NamePos: info.importCPos, 349 Name: "C.struct_" + name, 350 } 351 default: 352 panic("unknown elaborated type") 353 } 354 case C.CXType_Record: 355 cursor := C.tinygo_clang_getTypeDeclaration(typ) 356 fieldList := &ast.FieldList{ 357 Opening: info.importCPos, 358 Closing: info.importCPos, 359 } 360 ref := refMap.Put(struct { 361 fieldList *ast.FieldList 362 info *fileInfo 363 }{fieldList, info}) 364 defer refMap.Remove(ref) 365 C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref)) 366 switch C.tinygo_clang_getCursorKind(cursor) { 367 case C.CXCursor_StructDecl: 368 return &ast.StructType{ 369 Struct: info.importCPos, 370 Fields: fieldList, 371 } 372 case C.CXCursor_UnionDecl: 373 if len(fieldList.List) > 1 { 374 // Insert a special field at the front (of zero width) as a 375 // marker that this is struct is actually a union. This is done 376 // by giving the field a name that cannot be expressed directly 377 // in Go. 378 // Other parts of the compiler look at the first element in a 379 // struct (of size > 2) to know whether this is a union. 380 // Note that we don't have to insert it for single-element 381 // unions as they're basically equivalent to a struct. 382 unionMarker := &ast.Field{ 383 Type: &ast.StructType{ 384 Struct: info.importCPos, 385 }, 386 } 387 unionMarker.Names = []*ast.Ident{ 388 &ast.Ident{ 389 NamePos: info.importCPos, 390 Name: "C union", 391 Obj: &ast.Object{ 392 Kind: ast.Var, 393 Name: "C union", 394 Decl: unionMarker, 395 }, 396 }, 397 } 398 fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...) 399 } 400 return &ast.StructType{ 401 Struct: info.importCPos, 402 Fields: fieldList, 403 } 404 } 405 } 406 if typeName == "" { 407 // Fallback, probably incorrect but at least the error points to an odd 408 // type name. 409 typeName = "C." + getString(C.clang_getTypeSpelling(typ)) 410 } 411 return &ast.Ident{ 412 NamePos: info.importCPos, 413 Name: typeName, 414 } 415 } 416 417 //export tinygo_clang_struct_visitor 418 func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { 419 passed := refMap.Get(unsafe.Pointer(client_data)).(struct { 420 fieldList *ast.FieldList 421 info *fileInfo 422 }) 423 fieldList := passed.fieldList 424 info := passed.info 425 if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl { 426 panic("expected field inside cursor") 427 } 428 name := getString(C.tinygo_clang_getCursorSpelling(c)) 429 typ := C.tinygo_clang_getCursorType(c) 430 field := &ast.Field{ 431 Type: info.makeASTType(typ), 432 } 433 field.Names = []*ast.Ident{ 434 &ast.Ident{ 435 NamePos: info.importCPos, 436 Name: name, 437 Obj: &ast.Object{ 438 Kind: ast.Var, 439 Name: name, 440 Decl: field, 441 }, 442 }, 443 } 444 fieldList.List = append(fieldList.List, field) 445 return C.CXChildVisit_Continue 446 }