1package cgo 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 6import ( 7 "fmt" 8 "go/ast" 9 "go/scanner" 10 "go/token" 11 "path/filepath" 12 "strconv" 13 "strings" 14 "unsafe" 15) 16 17/* 18#include <clang-c/Index.h> // if this fails, install libclang-10-dev 19#include <stdlib.h> 20#include <stdint.h> 21 22// This struct should be ABI-compatible on all platforms (uintptr_t has the same 23// alignment etc. as void*) but does not include void* pointers that are not 24// always real pointers. 25// The Go garbage collector assumes that all non-nil pointer-typed integers are 26// actually pointers. This is not always true, as data[1] often contains 0x1, 27// which is clearly not a valid pointer. Usually the GC won't catch this issue, 28// but occasionally it will leading to a crash with a vague error message. 29typedef struct { 30 enum CXCursorKind kind; 31 int xdata; 32 uintptr_t data[3]; 33} GoCXCursor; 34 35// Forwarding functions. They are implemented in libclang_stubs.c and forward to 36// the real functions without doing anything else, thus they are entirely 37// compatible with the versions without tinygo_ prefix. The only difference is 38// the CXCursor type, which has been replaced with GoCXCursor. 39GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu); 40unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data); 41CXString tinygo_clang_getCursorSpelling(GoCXCursor c); 42enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c); 43CXType tinygo_clang_getCursorType(GoCXCursor c); 44GoCXCursor tinygo_clang_getTypeDeclaration(CXType t); 45CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c); 46CXType tinygo_clang_getCursorResultType(GoCXCursor c); 47int tinygo_clang_Cursor_getNumArguments(GoCXCursor c); 48GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i); 49CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c); 50CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c); 51CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c); 52long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c); 53CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c); 54unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c); 55 56int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); 57int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); 58int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); 59*/ 60import "C" 61 62// storedRefs stores references to types, used for clang_visitChildren. 63var storedRefs refMap 64 65var diagnosticSeverity = [...]string{ 66 C.CXDiagnostic_Ignored: "ignored", 67 C.CXDiagnostic_Note: "note", 68 C.CXDiagnostic_Warning: "warning", 69 C.CXDiagnostic_Error: "error", 70 C.CXDiagnostic_Fatal: "fatal", 71} 72 73func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename string, posLine int) { 74 index := C.clang_createIndex(0, 0) 75 defer C.clang_disposeIndex(index) 76 77 // pretend to be a .c file 78 filenameC := C.CString(posFilename + "!cgo.c") 79 defer C.free(unsafe.Pointer(filenameC)) 80 81 // fix up error locations 82 fragment = fmt.Sprintf("# %d %#v\n", posLine+1, posFilename) + fragment 83 84 fragmentC := C.CString(fragment) 85 defer C.free(unsafe.Pointer(fragmentC)) 86 87 unsavedFile := C.struct_CXUnsavedFile{ 88 Filename: filenameC, 89 Length: C.ulong(len(fragment)), 90 Contents: fragmentC, 91 } 92 93 // convert Go slice of strings to C array of strings. 94 cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0)))) 95 defer C.free(cmdargsC) 96 cmdargs := (*[1 << 16]*C.char)(cmdargsC) 97 for i, cflag := range cflags { 98 s := C.CString(cflag) 99 cmdargs[i] = s 100 defer C.free(unsafe.Pointer(s)) 101 } 102 103 var unit C.CXTranslationUnit 104 errCode := C.clang_parseTranslationUnit2( 105 index, 106 filenameC, 107 (**C.char)(cmdargsC), C.int(len(cflags)), // command line args 108 &unsavedFile, 1, // unsaved files 109 C.CXTranslationUnit_DetailedPreprocessingRecord, 110 &unit) 111 if errCode != 0 { 112 // This is probably a bug in the usage of libclang. 113 panic("cgo: failed to parse source with libclang") 114 } 115 defer C.clang_disposeTranslationUnit(unit) 116 117 if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 { 118 addDiagnostic := func(diagnostic C.CXDiagnostic) { 119 spelling := getString(C.clang_getDiagnosticSpelling(diagnostic)) 120 severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)] 121 location := C.clang_getDiagnosticLocation(diagnostic) 122 pos := p.getClangLocationPosition(location, unit) 123 p.addError(pos, severity+": "+spelling) 124 } 125 for i := 0; i < numDiagnostics; i++ { 126 diagnostic := C.clang_getDiagnostic(unit, C.uint(i)) 127 addDiagnostic(diagnostic) 128 129 // Child diagnostics (like notes on redefinitions). 130 diagnostics := C.clang_getChildDiagnostics(diagnostic) 131 for j := 0; j < int(C.clang_getNumDiagnosticsInSet(diagnostics)); j++ { 132 addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j))) 133 } 134 } 135 } 136 137 ref := storedRefs.Put(p) 138 defer storedRefs.Remove(ref) 139 cursor := C.tinygo_clang_getTranslationUnitCursor(unit) 140 C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref)) 141} 142 143//export tinygo_clang_globals_visitor 144func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { 145 p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage) 146 kind := C.tinygo_clang_getCursorKind(c) 147 pos := p.getCursorPosition(c) 148 switch kind { 149 case C.CXCursor_FunctionDecl: 150 name := getString(C.tinygo_clang_getCursorSpelling(c)) 151 if _, required := p.missingSymbols[name]; !required { 152 return C.CXChildVisit_Continue 153 } 154 cursorType := C.tinygo_clang_getCursorType(c) 155 if C.clang_isFunctionTypeVariadic(cursorType) != 0 { 156 return C.CXChildVisit_Continue // not supported 157 } 158 numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c)) 159 fn := &functionInfo{ 160 pos: pos, 161 } 162 p.functions[name] = fn 163 for i := 0; i < numArgs; i++ { 164 arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i)) 165 argName := getString(C.tinygo_clang_getCursorSpelling(arg)) 166 argType := C.clang_getArgType(cursorType, C.uint(i)) 167 if argName == "" { 168 argName = "$" + strconv.Itoa(i) 169 } 170 fn.args = append(fn.args, paramInfo{ 171 name: argName, 172 typeExpr: p.makeASTType(argType, pos), 173 }) 174 } 175 resultType := C.tinygo_clang_getCursorResultType(c) 176 if resultType.kind != C.CXType_Void { 177 fn.results = &ast.FieldList{ 178 List: []*ast.Field{ 179 &ast.Field{ 180 Type: p.makeASTType(resultType, pos), 181 }, 182 }, 183 } 184 } 185 case C.CXCursor_StructDecl: 186 typ := C.tinygo_clang_getCursorType(c) 187 name := getString(C.tinygo_clang_getCursorSpelling(c)) 188 if _, required := p.missingSymbols["struct_"+name]; !required { 189 return C.CXChildVisit_Continue 190 } 191 p.makeASTType(typ, pos) 192 case C.CXCursor_TypedefDecl: 193 typedefType := C.tinygo_clang_getCursorType(c) 194 name := getString(C.clang_getTypedefName(typedefType)) 195 if _, required := p.missingSymbols[name]; !required { 196 return C.CXChildVisit_Continue 197 } 198 p.makeASTType(typedefType, pos) 199 case C.CXCursor_VarDecl: 200 name := getString(C.tinygo_clang_getCursorSpelling(c)) 201 if _, required := p.missingSymbols[name]; !required { 202 return C.CXChildVisit_Continue 203 } 204 cursorType := C.tinygo_clang_getCursorType(c) 205 p.globals[name] = globalInfo{ 206 typeExpr: p.makeASTType(cursorType, pos), 207 pos: pos, 208 } 209 case C.CXCursor_MacroDefinition: 210 name := getString(C.tinygo_clang_getCursorSpelling(c)) 211 if _, required := p.missingSymbols[name]; !required { 212 return C.CXChildVisit_Continue 213 } 214 sourceRange := C.tinygo_clang_getCursorExtent(c) 215 start := C.clang_getRangeStart(sourceRange) 216 end := C.clang_getRangeEnd(sourceRange) 217 var file, endFile C.CXFile 218 var startOffset, endOffset C.unsigned 219 C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset) 220 if file == nil { 221 p.addError(pos, "internal error: could not find file where macro is defined") 222 break 223 } 224 C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset) 225 if file != endFile { 226 p.addError(pos, "internal error: expected start and end location of a macro to be in the same file") 227 break 228 } 229 if startOffset > endOffset { 230 p.addError(pos, "internal error: start offset of macro is after end offset") 231 break 232 } 233 234 // read file contents and extract the relevant byte range 235 tu := C.tinygo_clang_Cursor_getTranslationUnit(c) 236 var size C.size_t 237 sourcePtr := C.clang_getFileContents(tu, file, &size) 238 if endOffset >= C.uint(size) { 239 p.addError(pos, "internal error: end offset of macro lies after end of file") 240 break 241 } 242 source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset]) 243 if !strings.HasPrefix(source, name) { 244 p.addError(pos, fmt.Sprintf("internal error: expected macro value to start with %#v, got %#v", name, source)) 245 break 246 } 247 value := source[len(name):] 248 // Try to convert this #define into a Go constant expression. 249 expr, scannerError := parseConst(pos+token.Pos(len(name)), p.fset, value) 250 if scannerError != nil { 251 p.errors = append(p.errors, *scannerError) 252 } 253 if expr != nil { 254 // Parsing was successful. 255 p.constants[name] = constantInfo{expr, pos} 256 } 257 case C.CXCursor_EnumDecl: 258 // Visit all enums, because the fields may be used even when the enum 259 // type itself is not. 260 typ := C.tinygo_clang_getCursorType(c) 261 p.makeASTType(typ, pos) 262 } 263 return C.CXChildVisit_Continue 264} 265 266func getString(clangString C.CXString) (s string) { 267 rawString := C.clang_getCString(clangString) 268 s = C.GoString(rawString) 269 C.clang_disposeString(clangString) 270 return 271} 272 273// getCursorPosition returns a usable token.Pos from a libclang cursor. 274func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos { 275 return p.getClangLocationPosition(C.tinygo_clang_getCursorLocation(cursor), C.tinygo_clang_Cursor_getTranslationUnit(cursor)) 276} 277 278// getClangLocationPosition returns a usable token.Pos based on a libclang 279// location and translation unit. If the file for this cursor has not been seen 280// before, it is read from libclang (which already has the file in memory) and 281// added to the token.FileSet. 282func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.CXTranslationUnit) token.Pos { 283 var file C.CXFile 284 var line C.unsigned 285 var column C.unsigned 286 var offset C.unsigned 287 C.clang_getExpansionLocation(location, &file, &line, &column, &offset) 288 if line == 0 || file == nil { 289 // Invalid token. 290 return token.NoPos 291 } 292 filename := getString(C.clang_getFileName(file)) 293 if _, ok := p.tokenFiles[filename]; !ok { 294 // File has not been seen before in this package, add line information 295 // now by reading the file from libclang. 296 var size C.size_t 297 sourcePtr := C.clang_getFileContents(tu, file, &size) 298 source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size] 299 lines := []int{0} 300 for i := 0; i < len(source)-1; i++ { 301 if source[i] == '\n' { 302 lines = append(lines, i+1) 303 } 304 } 305 f := p.fset.AddFile(filename, -1, int(size)) 306 f.SetLines(lines) 307 p.tokenFiles[filename] = f 308 } 309 positionFile := p.tokenFiles[filename] 310 311 // Check for alternative line/column information (set with a line directive). 312 var filename2String C.CXString 313 var line2 C.unsigned 314 var column2 C.unsigned 315 C.clang_getPresumedLocation(location, &filename2String, &line2, &column2) 316 filename2 := getString(filename2String) 317 if filename2 != filename || line2 != line || column2 != column { 318 // The location was changed with a preprocessor directive. 319 // TODO: this only works for locations that are added in order. Adding 320 // line/column info to a file that already has line/column info after 321 // the given offset is ignored. 322 positionFile.AddLineColumnInfo(int(offset), filename2, int(line2), int(column2)) 323 } 324 325 return positionFile.Pos(int(offset)) 326} 327 328// addError is a utility function to add an error to the list of errors. It will 329// convert the token position to a line/column position first, and call 330// addErrorAt. 331func (p *cgoPackage) addError(pos token.Pos, msg string) { 332 p.addErrorAt(p.fset.PositionFor(pos, true), msg) 333} 334 335// addErrorAfter is like addError, but adds the text `after` to the source 336// location. 337func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) { 338 position := p.fset.PositionFor(pos, true) 339 lines := strings.Split(after, "\n") 340 if len(lines) != 1 { 341 // Adjust lines. 342 // For why we can't just do pos+token.Pos(len(after)), see: 343 // https://github.com/golang/go/issues/35803 344 position.Line += len(lines) - 1 345 position.Column = len(lines[len(lines)-1]) + 1 346 } else { 347 position.Column += len(after) 348 } 349 p.addErrorAt(position, msg) 350} 351 352// addErrorAt is a utility function to add an error to the list of errors. 353func (p *cgoPackage) addErrorAt(position token.Position, msg string) { 354 if filepath.IsAbs(position.Filename) { 355 // Relative paths for readability, like other Go parser errors. 356 relpath, err := filepath.Rel(p.dir, position.Filename) 357 if err == nil { 358 position.Filename = relpath 359 } 360 } 361 p.errors = append(p.errors, scanner.Error{ 362 Pos: position, 363 Msg: msg, 364 }) 365} 366 367// makeASTType return the ast.Expr for the given libclang type. In other words, 368// it converts a libclang type to a type in the Go AST. 369func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { 370 var typeName string 371 switch typ.kind { 372 case C.CXType_Char_S, C.CXType_Char_U: 373 typeName = "C.char" 374 case C.CXType_SChar: 375 typeName = "C.schar" 376 case C.CXType_UChar: 377 typeName = "C.uchar" 378 case C.CXType_Short: 379 typeName = "C.short" 380 case C.CXType_UShort: 381 typeName = "C.ushort" 382 case C.CXType_Int: 383 typeName = "C.int" 384 case C.CXType_UInt: 385 typeName = "C.uint" 386 case C.CXType_Long: 387 typeName = "C.long" 388 case C.CXType_ULong: 389 typeName = "C.ulong" 390 case C.CXType_LongLong: 391 typeName = "C.longlong" 392 case C.CXType_ULongLong: 393 typeName = "C.ulonglong" 394 case C.CXType_Bool: 395 typeName = "bool" 396 case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble: 397 switch C.clang_Type_getSizeOf(typ) { 398 case 4: 399 typeName = "float32" 400 case 8: 401 typeName = "float64" 402 default: 403 // Don't do anything, rely on the fallback code to show a somewhat 404 // sensible error message like "undeclared name: C.long double". 405 } 406 case C.CXType_Complex: 407 switch C.clang_Type_getSizeOf(typ) { 408 case 8: 409 typeName = "complex64" 410 case 16: 411 typeName = "complex128" 412 } 413 case C.CXType_Pointer: 414 pointeeType := C.clang_getPointeeType(typ) 415 if pointeeType.kind == C.CXType_Void { 416 // void* type is translated to Go as unsafe.Pointer 417 return &ast.SelectorExpr{ 418 X: &ast.Ident{ 419 NamePos: pos, 420 Name: "unsafe", 421 }, 422 Sel: &ast.Ident{ 423 NamePos: pos, 424 Name: "Pointer", 425 }, 426 } 427 } 428 return &ast.StarExpr{ 429 Star: pos, 430 X: p.makeASTType(pointeeType, pos), 431 } 432 case C.CXType_ConstantArray: 433 return &ast.ArrayType{ 434 Lbrack: pos, 435 Len: &ast.BasicLit{ 436 ValuePos: pos, 437 Kind: token.INT, 438 Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10), 439 }, 440 Elt: p.makeASTType(C.clang_getElementType(typ), pos), 441 } 442 case C.CXType_FunctionProto: 443 // Be compatible with gc, which uses the *[0]byte type for function 444 // pointer types. 445 // Return type [0]byte because this is a function type, not a pointer to 446 // this function type. 447 return &ast.ArrayType{ 448 Lbrack: pos, 449 Len: &ast.BasicLit{ 450 ValuePos: pos, 451 Kind: token.INT, 452 Value: "0", 453 }, 454 Elt: &ast.Ident{ 455 NamePos: pos, 456 Name: "byte", 457 }, 458 } 459 case C.CXType_Typedef: 460 name := getString(C.clang_getTypedefName(typ)) 461 if _, ok := p.typedefs[name]; !ok { 462 p.typedefs[name] = nil // don't recurse 463 c := C.tinygo_clang_getTypeDeclaration(typ) 464 underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c) 465 expr := p.makeASTType(underlyingType, pos) 466 if strings.HasPrefix(name, "_Cgo_") { 467 expr := expr.(*ast.Ident) 468 typeSize := C.clang_Type_getSizeOf(underlyingType) 469 switch expr.Name { 470 case "C.char": 471 if typeSize != 1 { 472 // This happens for some very special purpose architectures 473 // (DSPs etc.) that are not currently targeted. 474 // https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/ 475 p.addError(pos, fmt.Sprintf("unknown char width: %d", typeSize)) 476 } 477 switch underlyingType.kind { 478 case C.CXType_Char_S: 479 expr.Name = "int8" 480 case C.CXType_Char_U: 481 expr.Name = "uint8" 482 } 483 case "C.schar", "C.short", "C.int", "C.long", "C.longlong": 484 switch typeSize { 485 case 1: 486 expr.Name = "int8" 487 case 2: 488 expr.Name = "int16" 489 case 4: 490 expr.Name = "int32" 491 case 8: 492 expr.Name = "int64" 493 } 494 case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong": 495 switch typeSize { 496 case 1: 497 expr.Name = "uint8" 498 case 2: 499 expr.Name = "uint16" 500 case 4: 501 expr.Name = "uint32" 502 case 8: 503 expr.Name = "uint64" 504 } 505 } 506 } 507 p.typedefs[name] = &typedefInfo{ 508 typeExpr: expr, 509 pos: pos, 510 } 511 } 512 return &ast.Ident{ 513 NamePos: pos, 514 Name: "C." + name, 515 } 516 case C.CXType_Elaborated: 517 underlying := C.clang_Type_getNamedType(typ) 518 switch underlying.kind { 519 case C.CXType_Record: 520 return p.makeASTType(underlying, pos) 521 case C.CXType_Enum: 522 return p.makeASTType(underlying, pos) 523 default: 524 typeKindSpelling := getString(C.clang_getTypeKindSpelling(underlying.kind)) 525 p.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling)) 526 typeName = "<unknown>" 527 } 528 case C.CXType_Record: 529 cursor := C.tinygo_clang_getTypeDeclaration(typ) 530 name := getString(C.tinygo_clang_getCursorSpelling(cursor)) 531 var cgoRecordPrefix string 532 switch C.tinygo_clang_getCursorKind(cursor) { 533 case C.CXCursor_StructDecl: 534 cgoRecordPrefix = "struct_" 535 case C.CXCursor_UnionDecl: 536 cgoRecordPrefix = "union_" 537 default: 538 // makeASTRecordType will create an appropriate error. 539 cgoRecordPrefix = "record_" 540 } 541 if name == "" { 542 // Anonymous record, probably inside a typedef. 543 typeInfo := p.makeASTRecordType(cursor, pos) 544 if typeInfo.bitfields != nil || typeInfo.unionSize != 0 { 545 // This record is a union or is a struct with bitfields, so we 546 // have to declare it as a named type (for getters/setters to 547 // work). 548 p.anonStructNum++ 549 cgoName := cgoRecordPrefix + strconv.Itoa(p.anonStructNum) 550 p.elaboratedTypes[cgoName] = typeInfo 551 return &ast.Ident{ 552 NamePos: pos, 553 Name: "C." + cgoName, 554 } 555 } 556 return typeInfo.typeExpr 557 } else { 558 cgoName := cgoRecordPrefix + name 559 if _, ok := p.elaboratedTypes[cgoName]; !ok { 560 p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion) 561 p.elaboratedTypes[cgoName] = p.makeASTRecordType(cursor, pos) 562 } 563 return &ast.Ident{ 564 NamePos: pos, 565 Name: "C." + cgoName, 566 } 567 } 568 case C.CXType_Enum: 569 cursor := C.tinygo_clang_getTypeDeclaration(typ) 570 name := getString(C.tinygo_clang_getCursorSpelling(cursor)) 571 underlying := C.tinygo_clang_getEnumDeclIntegerType(cursor) 572 if name == "" { 573 // anonymous enum 574 ref := storedRefs.Put(p) 575 defer storedRefs.Remove(ref) 576 C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref)) 577 return p.makeASTType(underlying, pos) 578 } else { 579 // named enum 580 if _, ok := p.enums[name]; !ok { 581 ref := storedRefs.Put(p) 582 defer storedRefs.Remove(ref) 583 C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref)) 584 p.enums[name] = enumInfo{ 585 typeExpr: p.makeASTType(underlying, pos), 586 pos: pos, 587 } 588 } 589 return &ast.Ident{ 590 NamePos: pos, 591 Name: "C.enum_" + name, 592 } 593 } 594 } 595 if typeName == "" { 596 // Report this as an error. 597 typeSpelling := getString(C.clang_getTypeSpelling(typ)) 598 typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind)) 599 p.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling)) 600 typeName = "C.<unknown>" 601 } 602 return &ast.Ident{ 603 NamePos: pos, 604 Name: typeName, 605 } 606} 607 608// makeASTRecordType parses a C record (struct or union) and translates it into 609// a Go struct type. 610func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elaboratedTypeInfo { 611 fieldList := &ast.FieldList{ 612 Opening: pos, 613 Closing: pos, 614 } 615 var bitfieldList []bitfieldInfo 616 inBitfield := false 617 bitfieldNum := 0 618 ref := storedRefs.Put(struct { 619 fieldList *ast.FieldList 620 pkg *cgoPackage 621 inBitfield *bool 622 bitfieldNum *int 623 bitfieldList *[]bitfieldInfo 624 }{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList}) 625 defer storedRefs.Remove(ref) 626 C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref)) 627 renameFieldKeywords(fieldList) 628 switch C.tinygo_clang_getCursorKind(cursor) { 629 case C.CXCursor_StructDecl: 630 return &elaboratedTypeInfo{ 631 typeExpr: &ast.StructType{ 632 Struct: pos, 633 Fields: fieldList, 634 }, 635 pos: pos, 636 bitfields: bitfieldList, 637 } 638 case C.CXCursor_UnionDecl: 639 typeInfo := &elaboratedTypeInfo{ 640 typeExpr: &ast.StructType{ 641 Struct: pos, 642 Fields: fieldList, 643 }, 644 pos: pos, 645 bitfields: bitfieldList, 646 } 647 if len(fieldList.List) <= 1 { 648 // Useless union, treat it as a regular struct. 649 return typeInfo 650 } 651 if bitfieldList != nil { 652 // This is valid C... but please don't do this. 653 p.addError(pos, "bitfield in a union is not supported") 654 } 655 typ := C.tinygo_clang_getCursorType(cursor) 656 alignInBytes := int64(C.clang_Type_getAlignOf(typ)) 657 sizeInBytes := int64(C.clang_Type_getSizeOf(typ)) 658 if sizeInBytes == 0 { 659 p.addError(pos, "zero-length union is not supported") 660 } 661 typeInfo.unionSize = sizeInBytes 662 typeInfo.unionAlign = alignInBytes 663 return typeInfo 664 default: 665 cursorKind := C.tinygo_clang_getCursorKind(cursor) 666 cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind)) 667 p.addError(pos, fmt.Sprintf("expected StructDecl or UnionDecl, not %s", cursorKindSpelling)) 668 return &elaboratedTypeInfo{ 669 typeExpr: &ast.StructType{ 670 Struct: pos, 671 }, 672 pos: pos, 673 } 674 } 675} 676 677//export tinygo_clang_struct_visitor 678func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { 679 passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct { 680 fieldList *ast.FieldList 681 pkg *cgoPackage 682 inBitfield *bool 683 bitfieldNum *int 684 bitfieldList *[]bitfieldInfo 685 }) 686 fieldList := passed.fieldList 687 p := passed.pkg 688 inBitfield := passed.inBitfield 689 bitfieldNum := passed.bitfieldNum 690 bitfieldList := passed.bitfieldList 691 pos := p.getCursorPosition(c) 692 switch cursorKind := C.tinygo_clang_getCursorKind(c); cursorKind { 693 case C.CXCursor_FieldDecl: 694 // Expected. This is a regular field. 695 case C.CXCursor_StructDecl, C.CXCursor_UnionDecl: 696 // Ignore. The next field will be the struct/union itself. 697 return C.CXChildVisit_Continue 698 default: 699 cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind)) 700 p.addError(pos, fmt.Sprintf("expected FieldDecl in struct or union, not %s", cursorKindSpelling)) 701 return C.CXChildVisit_Continue 702 } 703 name := getString(C.tinygo_clang_getCursorSpelling(c)) 704 if name == "" { 705 // Assume this is a bitfield of 0 bits. 706 // Warning: this is not necessarily true! 707 return C.CXChildVisit_Continue 708 } 709 typ := C.tinygo_clang_getCursorType(c) 710 field := &ast.Field{ 711 Type: p.makeASTType(typ, p.getCursorPosition(c)), 712 } 713 offsetof := int64(C.clang_Type_getOffsetOf(C.tinygo_clang_getCursorType(parent), C.CString(name))) 714 alignOf := int64(C.clang_Type_getAlignOf(typ) * 8) 715 bitfieldOffset := offsetof % alignOf 716 if bitfieldOffset != 0 { 717 if C.tinygo_clang_Cursor_isBitField(c) != 1 { 718 p.addError(pos, "expected a bitfield") 719 return C.CXChildVisit_Continue 720 } 721 if !*inBitfield { 722 *bitfieldNum++ 723 } 724 bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum) 725 prevField := fieldList.List[len(fieldList.List)-1] 726 if !*inBitfield { 727 // The previous element also was a bitfield, but wasn't noticed 728 // then. Add it now. 729 *inBitfield = true 730 *bitfieldList = append(*bitfieldList, bitfieldInfo{ 731 field: prevField, 732 name: prevField.Names[0].Name, 733 startBit: 0, 734 pos: prevField.Names[0].NamePos, 735 }) 736 prevField.Names[0].Name = bitfieldName 737 prevField.Names[0].Obj.Name = bitfieldName 738 } 739 prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1] 740 prevBitfield.endBit = bitfieldOffset 741 *bitfieldList = append(*bitfieldList, bitfieldInfo{ 742 field: prevField, 743 name: name, 744 startBit: bitfieldOffset, 745 pos: pos, 746 }) 747 return C.CXChildVisit_Continue 748 } 749 *inBitfield = false 750 field.Names = []*ast.Ident{ 751 &ast.Ident{ 752 NamePos: pos, 753 Name: name, 754 Obj: &ast.Object{ 755 Kind: ast.Var, 756 Name: name, 757 Decl: field, 758 }, 759 }, 760 } 761 fieldList.List = append(fieldList.List, field) 762 return C.CXChildVisit_Continue 763} 764 765//export tinygo_clang_enum_visitor 766func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { 767 p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage) 768 name := getString(C.tinygo_clang_getCursorSpelling(c)) 769 pos := p.getCursorPosition(c) 770 value := C.tinygo_clang_getEnumConstantDeclValue(c) 771 p.constants[name] = constantInfo{ 772 expr: &ast.BasicLit{pos, token.INT, strconv.FormatInt(int64(value), 10)}, 773 pos: pos, 774 } 775 return C.CXChildVisit_Continue 776} 777