libclang.go raw
1 package 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
6 import (
7 "bytes"
8 "crypto/sha256"
9 "crypto/sha512"
10 "encoding/hex"
11 "fmt"
12 "go/ast"
13 "go/scanner"
14 "go/token"
15 "path/filepath"
16 "strconv"
17 "strings"
18 "unsafe"
19
20 "tinygo.org/x/go-llvm"
21 )
22
23 /*
24 #include <clang-c/Index.h> // If this fails, libclang headers aren't available. Please take a look here: https://moxie.dev/docs/guides/build/
25 #include <llvm/Config/llvm-config.h>
26 #include <stdlib.h>
27 #include <stdint.h>
28
29 // This struct should be ABI-compatible on all platforms (uintptr_t has the same
30 // alignment etc. as void*) but does not include void* pointers that are not
31 // always real pointers.
32 // The Go garbage collector assumes that all non-nil pointer-typed integers are
33 // actually pointers. This is not always true, as data[1] often contains 0x1,
34 // which is clearly not a valid pointer. Usually the GC won't catch this issue,
35 // but occasionally it will leading to a crash with a vague error message.
36 typedef struct {
37 enum CXCursorKind kind;
38 int xdata;
39 uintptr_t data[3];
40 } GoCXCursor;
41
42 // Forwarding functions. They are implemented in libclang_stubs.c and forward to
43 // the real functions without doing anything else, thus they are entirely
44 // compatible with the versions without moxie_ prefix. The only difference is
45 // the CXCursor type, which has been replaced with GoCXCursor.
46 GoCXCursor moxie_clang_getTranslationUnitCursor(CXTranslationUnit tu);
47 unsigned moxie_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data);
48 CXString moxie_clang_getCursorSpelling(GoCXCursor c);
49 CXString moxie_clang_getCursorPrettyPrinted(GoCXCursor c, CXPrintingPolicy Policy);
50 CXPrintingPolicy moxie_clang_getCursorPrintingPolicy(GoCXCursor c);
51 enum CXCursorKind moxie_clang_getCursorKind(GoCXCursor c);
52 CXType moxie_clang_getCursorType(GoCXCursor c);
53 GoCXCursor moxie_clang_getTypeDeclaration(CXType t);
54 CXType moxie_clang_getTypedefDeclUnderlyingType(GoCXCursor c);
55 CXType moxie_clang_getCursorResultType(GoCXCursor c);
56 int moxie_clang_Cursor_getNumArguments(GoCXCursor c);
57 GoCXCursor moxie_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
58 enum CX_StorageClass moxie_clang_Cursor_getStorageClass(GoCXCursor c);
59 CXSourceLocation moxie_clang_getCursorLocation(GoCXCursor c);
60 CXSourceRange moxie_clang_getCursorExtent(GoCXCursor c);
61 CXTranslationUnit moxie_clang_Cursor_getTranslationUnit(GoCXCursor c);
62 long long moxie_clang_getEnumConstantDeclValue(GoCXCursor c);
63 CXType moxie_clang_getEnumDeclIntegerType(GoCXCursor c);
64 unsigned moxie_clang_Cursor_isAnonymous(GoCXCursor c);
65 unsigned moxie_clang_Cursor_isBitField(GoCXCursor c);
66 unsigned moxie_clang_Cursor_isMacroFunctionLike(GoCXCursor c);
67
68 // Fix some warnings on Windows ARM. Without the __declspec(dllexport), it gives warnings like this:
69 // In file included from _cgo_export.c:4:
70 // cgo-gcc-export-header-prolog:49:34: warning: redeclaration of 'moxie_clang_globals_visitor' should not add 'dllexport' attribute [-Wdll-attribute-on-redeclaration]
71 // libclang.go:68:5: note: previous declaration is here
72 // See: https://github.com/golang/go/issues/49721
73 #if defined(_WIN32)
74 #define CGO_DECL __declspec(dllexport)
75 #else
76 #define CGO_DECL
77 #endif
78
79 CGO_DECL
80 int moxie_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
81 CGO_DECL
82 int moxie_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
83 CGO_DECL
84 void moxie_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data);
85 */
86 import "C"
87
88 // storedRefs stores references to types, used for clang_visitChildren.
89 var storedRefs refMap
90
91 var diagnosticSeverity = [...]string{
92 C.CXDiagnostic_Ignored: "ignored",
93 C.CXDiagnostic_Note: "note",
94 C.CXDiagnostic_Warning: "warning",
95 C.CXDiagnostic_Error: "error",
96 C.CXDiagnostic_Fatal: "fatal",
97 }
98
99 // Alias so that cgo.go (which doesn't import Clang related stuff and is in
100 // theory decoupled from Clang) can also use this type.
101 type clangCursor = C.GoCXCursor
102
103 func init() {
104 // Check that we haven't messed up LLVM versioning.
105 // This can happen when llvm_config_*.go files in either this or the
106 // tinygo.org/x/go-llvm package are incorrect. It should not ever happen
107 // with byollvm.
108 if C.LLVM_VERSION_STRING != llvm.Version {
109 panic("incorrect build: using LLVM version " + llvm.Version + " in the tinygo.org/x/go-llvm package, and version " + C.LLVM_VERSION_STRING + " in the ./cgo package")
110 }
111 }
112
113 func (f *cgoFile) readNames(fragment string, cflags []string, filename string, callback func(map[string]clangCursor)) {
114 index := C.clang_createIndex(0, 0)
115 defer C.clang_disposeIndex(index)
116
117 // pretend to be a .c file
118 filenameC := C.CString(filename + "!cgo.c")
119 defer C.free(unsafe.Pointer(filenameC))
120
121 fragmentC := C.CString(fragment)
122 defer C.free(unsafe.Pointer(fragmentC))
123
124 unsavedFile := C.struct_CXUnsavedFile{
125 Filename: filenameC,
126 Length: C.ulong(len(fragment)),
127 Contents: fragmentC,
128 }
129
130 // convert Go slice of strings to C array of strings.
131 cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
132 defer C.free(cmdargsC)
133 cmdargs := (*[1 << 16]*C.char)(cmdargsC)
134 for i, cflag := range cflags {
135 s := C.CString(cflag)
136 cmdargs[i] = s
137 defer C.free(unsafe.Pointer(s))
138 }
139
140 var unit C.CXTranslationUnit
141 errCode := C.clang_parseTranslationUnit2(
142 index,
143 filenameC,
144 (**C.char)(cmdargsC), C.int(len(cflags)), // command line args
145 &unsavedFile, 1, // unsaved files
146 C.CXTranslationUnit_DetailedPreprocessingRecord,
147 &unit)
148 if errCode != 0 {
149 // This is probably a bug in the usage of libclang.
150 panic("cgo: failed to parse source with libclang")
151 }
152 defer C.clang_disposeTranslationUnit(unit)
153
154 // Report parser and type errors.
155 if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
156 addDiagnostic := func(diagnostic C.CXDiagnostic) {
157 spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
158 severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
159 location := C.clang_getDiagnosticLocation(diagnostic)
160 pos := f.getClangLocationPosition(location, unit)
161 f.addError(pos, severity+": "+spelling)
162 }
163 for i := 0; i < numDiagnostics; i++ {
164 diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
165 addDiagnostic(diagnostic)
166
167 // Child diagnostics (like notes on redefinitions).
168 diagnostics := C.clang_getChildDiagnostics(diagnostic)
169 for j := 0; j < int(C.clang_getNumDiagnosticsInSet(diagnostics)); j++ {
170 addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
171 }
172 }
173 }
174
175 // Extract information required by CGo.
176 ref := storedRefs.Put(f)
177 defer storedRefs.Remove(ref)
178 cursor := C.moxie_clang_getTranslationUnitCursor(unit)
179 C.moxie_clang_visitChildren(cursor, C.CXCursorVisitor(C.moxie_clang_globals_visitor), C.CXClientData(ref))
180
181 // Determine files read during CGo processing, for caching.
182 inclusionCallback := func(includedFile C.CXFile) {
183 // Get full file path.
184 path := getString(C.clang_getFileName(includedFile))
185
186 // Get contents of file (that should be in-memory).
187 size := C.size_t(0)
188 rawData := C.clang_getFileContents(unit, includedFile, &size)
189 if rawData == nil {
190 // Sanity check. This should (hopefully) never trigger.
191 panic("libclang: file contents was not loaded")
192 }
193 data := (*[1 << 24]byte)(unsafe.Pointer(rawData))[:size]
194
195 // Hash the contents if it isn't hashed yet.
196 if _, ok := f.visitedFiles[path]; !ok {
197 // already stored
198 sum := sha512.Sum512_224(data)
199 f.visitedFiles[path] = sum[:]
200 }
201 }
202 inclusionCallbackRef := storedRefs.Put(inclusionCallback)
203 defer storedRefs.Remove(inclusionCallbackRef)
204 C.clang_getInclusions(unit, C.CXInclusionVisitor(C.moxie_clang_inclusion_visitor), C.CXClientData(inclusionCallbackRef))
205
206 // Do all the C AST operations inside a callback. This makes sure that
207 // libclang related memory is only freed after it is not necessary anymore.
208 callback(f.names)
209 }
210
211 // Convert the AST node under the given Clang cursor to a Go AST node and return
212 // it.
213 func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
214 kind := C.moxie_clang_getCursorKind(c)
215 pos := f.getCursorPosition(c)
216 switch kind {
217 case C.CXCursor_FunctionDecl:
218 cursorType := C.moxie_clang_getCursorType(c)
219 numArgs := int(C.moxie_clang_Cursor_getNumArguments(c))
220 obj := &ast.Object{
221 Kind: ast.Fun,
222 Name: "_Cgo_" + name,
223 }
224 exportName := name
225 localName := name
226 var stringSignature string
227 if C.moxie_clang_Cursor_getStorageClass(c) == C.CX_SC_Static {
228 // A static function is assigned a globally unique symbol name based
229 // on the file path (like _Cgo_static_2d09198adbf58f4f4655_foo) and
230 // has a different Go name in the form of C.foo!symbols.go instead
231 // of just C.foo.
232 path := f.importPath + "/" + filepath.Base(f.fset.File(f.file.Pos()).Name())
233 staticIDBuf := sha256.Sum256([]byte(path))
234 staticID := hex.EncodeToString(staticIDBuf[:10])
235 exportName = "_Cgo_static_" + staticID + "_" + name
236 localName = name + "!" + filepath.Base(path)
237
238 // Create a signature. This is necessary for MacOS to forward the
239 // call, because MacOS doesn't support aliases like ELF and PE do.
240 // (There is N_INDR but __attribute__((alias("..."))) doesn't work).
241 policy := C.moxie_clang_getCursorPrintingPolicy(c)
242 defer C.clang_PrintingPolicy_dispose(policy)
243 C.clang_PrintingPolicy_setProperty(policy, C.CXPrintingPolicy_TerseOutput, 1)
244 stringSignature = getString(C.moxie_clang_getCursorPrettyPrinted(c, policy))
245 stringSignature = strings.Replace(stringSignature, " "+name+"(", " "+exportName+"(", 1)
246 stringSignature = strings.TrimPrefix(stringSignature, "static ")
247 }
248 args := make([]*ast.Field, numArgs)
249 decl := &ast.FuncDecl{
250 Doc: &ast.CommentGroup{
251 List: []*ast.Comment{
252 {
253 Slash: pos - 1,
254 Text: "//export " + exportName,
255 },
256 },
257 },
258 Name: &ast.Ident{
259 NamePos: pos,
260 Name: "_Cgo_" + localName,
261 Obj: obj,
262 },
263 Type: &ast.FuncType{
264 Func: pos,
265 Params: &ast.FieldList{
266 Opening: pos,
267 List: args,
268 Closing: pos,
269 },
270 },
271 }
272 var doc []string
273 if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
274 doc = append(doc, "//go:variadic")
275 }
276 if _, ok := f.noescapingFuncs[name]; ok {
277 doc = append(doc, "//go:noescape")
278 f.noescapingFuncs[name].used = true
279 }
280 if len(doc) != 0 {
281 decl.Doc.List = append(decl.Doc.List, &ast.Comment{
282 Slash: pos - 1,
283 Text: strings.Join(doc, "\n"),
284 })
285 }
286 for i := 0; i < numArgs; i++ {
287 arg := C.moxie_clang_Cursor_getArgument(c, C.uint(i))
288 argName := getString(C.moxie_clang_getCursorSpelling(arg))
289 argType := C.clang_getArgType(cursorType, C.uint(i))
290 if argName == "" {
291 argName = "$" + strconv.Itoa(i)
292 }
293 args[i] = &ast.Field{
294 Names: []*ast.Ident{
295 {
296 NamePos: pos,
297 Name: argName,
298 Obj: &ast.Object{
299 Kind: ast.Var,
300 Name: argName,
301 Decl: decl,
302 },
303 },
304 },
305 Type: f.makeDecayingASTType(argType, pos),
306 }
307 }
308 resultType := C.moxie_clang_getCursorResultType(c)
309 if resultType.kind != C.CXType_Void {
310 decl.Type.Results = &ast.FieldList{
311 List: []*ast.Field{
312 {
313 Type: f.makeASTType(resultType, pos),
314 },
315 },
316 }
317 }
318 obj.Decl = decl
319 return decl, stringSignature
320 case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
321 typ := f.makeASTRecordType(c, pos)
322 typeName := "_Cgo_" + name
323 typeExpr := typ.typeExpr
324 if typ.unionSize != 0 {
325 // Convert to a single-field struct type.
326 typeExpr = f.makeUnionField(typ)
327 }
328 obj := &ast.Object{
329 Kind: ast.Typ,
330 Name: typeName,
331 }
332 typeSpec := &ast.TypeSpec{
333 Name: &ast.Ident{
334 NamePos: typ.pos,
335 Name: typeName,
336 Obj: obj,
337 },
338 Type: typeExpr,
339 }
340 obj.Decl = typeSpec
341 return typeSpec, typ
342 case C.CXCursor_TypedefDecl:
343 typeName := "_Cgo_" + name
344 underlyingType := C.moxie_clang_getTypedefDeclUnderlyingType(c)
345 obj := &ast.Object{
346 Kind: ast.Typ,
347 Name: typeName,
348 }
349 typeSpec := &ast.TypeSpec{
350 Name: &ast.Ident{
351 NamePos: pos,
352 Name: typeName,
353 Obj: obj,
354 },
355 Type: f.makeASTType(underlyingType, pos),
356 }
357 if underlyingType.kind != C.CXType_Enum {
358 typeSpec.Assign = pos
359 }
360 obj.Decl = typeSpec
361 return typeSpec, nil
362 case C.CXCursor_VarDecl:
363 cursorType := C.moxie_clang_getCursorType(c)
364 typeExpr := f.makeASTType(cursorType, pos)
365 gen := &ast.GenDecl{
366 TokPos: pos,
367 Tok: token.VAR,
368 Lparen: token.NoPos,
369 Rparen: token.NoPos,
370 Doc: &ast.CommentGroup{
371 List: []*ast.Comment{
372 {
373 Slash: pos - 1,
374 Text: "//go:extern " + name,
375 },
376 },
377 },
378 }
379 obj := &ast.Object{
380 Kind: ast.Var,
381 Name: "_Cgo_" + name,
382 }
383 valueSpec := &ast.ValueSpec{
384 Names: []*ast.Ident{{
385 NamePos: pos,
386 Name: "_Cgo_" + name,
387 Obj: obj,
388 }},
389 Type: typeExpr,
390 }
391 obj.Decl = valueSpec
392 gen.Specs = append(gen.Specs, valueSpec)
393 return gen, nil
394 case C.CXCursor_MacroDefinition:
395 tokenPos, value := f.getMacro(c)
396 expr, scannerError := parseConst(tokenPos, f.fset, value, nil, token.NoPos, f)
397 if scannerError != nil {
398 f.errors = append(f.errors, *scannerError)
399 return nil, nil
400 }
401
402 gen := &ast.GenDecl{
403 TokPos: token.NoPos,
404 Tok: token.CONST,
405 Lparen: token.NoPos,
406 Rparen: token.NoPos,
407 }
408 obj := &ast.Object{
409 Kind: ast.Con,
410 Name: "_Cgo_" + name,
411 }
412 valueSpec := &ast.ValueSpec{
413 Names: []*ast.Ident{{
414 NamePos: pos,
415 Name: "_Cgo_" + name,
416 Obj: obj,
417 }},
418 Values: []ast.Expr{expr},
419 }
420 obj.Decl = valueSpec
421 gen.Specs = append(gen.Specs, valueSpec)
422 return gen, nil
423 case C.CXCursor_EnumDecl:
424 obj := &ast.Object{
425 Kind: ast.Typ,
426 Name: "_Cgo_" + name,
427 }
428 underlying := C.moxie_clang_getEnumDeclIntegerType(c)
429 // TODO: gc's CGo implementation uses types such as `uint32` for enums
430 // instead of types such as C.int, which are used here.
431 typeSpec := &ast.TypeSpec{
432 Name: &ast.Ident{
433 NamePos: pos,
434 Name: "_Cgo_" + name,
435 Obj: obj,
436 },
437 Assign: pos,
438 Type: f.makeASTType(underlying, pos),
439 }
440 obj.Decl = typeSpec
441 return typeSpec, nil
442 case C.CXCursor_EnumConstantDecl:
443 value := C.moxie_clang_getEnumConstantDeclValue(c)
444 expr := &ast.BasicLit{
445 ValuePos: pos,
446 Kind: token.INT,
447 Value: strconv.FormatInt(int64(value), 10),
448 }
449 gen := &ast.GenDecl{
450 TokPos: token.NoPos,
451 Tok: token.CONST,
452 Lparen: token.NoPos,
453 Rparen: token.NoPos,
454 }
455 obj := &ast.Object{
456 Kind: ast.Con,
457 Name: "_Cgo_" + name,
458 }
459 valueSpec := &ast.ValueSpec{
460 Names: []*ast.Ident{{
461 NamePos: pos,
462 Name: "_Cgo_" + name,
463 Obj: obj,
464 }},
465 Values: []ast.Expr{expr},
466 }
467 obj.Decl = valueSpec
468 gen.Specs = append(gen.Specs, valueSpec)
469 return gen, nil
470 default:
471 f.addError(pos, fmt.Sprintf("internal error: unknown cursor type: %d", kind))
472 return nil, nil
473 }
474 }
475
476 // Return whether this is a macro that's also function-like, like this:
477 //
478 // #define add(a, b) (a+b)
479 func (f *cgoFile) isFunctionLikeMacro(c clangCursor) bool {
480 if C.moxie_clang_getCursorKind(c) != C.CXCursor_MacroDefinition {
481 return false
482 }
483 return C.moxie_clang_Cursor_isMacroFunctionLike(c) != 0
484 }
485
486 // Get the macro value: the position in the source file and the string value of
487 // the macro.
488 func (f *cgoFile) getMacro(c clangCursor) (pos token.Pos, value string) {
489 // Extract tokens from the Clang tokenizer.
490 // See: https://stackoverflow.com/a/19074846/559350
491 sourceRange := C.moxie_clang_getCursorExtent(c)
492 tu := C.moxie_clang_Cursor_getTranslationUnit(c)
493 var rawTokens *C.CXToken
494 var numTokens C.unsigned
495 C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens)
496 tokens := unsafe.Slice(rawTokens, numTokens)
497 defer C.clang_disposeTokens(tu, rawTokens, numTokens)
498
499 // Convert this range of tokens back to source text.
500 // Ugly, but it works well enough.
501 sourceBuf := &bytes.Buffer{}
502 var startOffset int
503 for i, token := range tokens {
504 spelling := getString(C.clang_getTokenSpelling(tu, token))
505 location := C.clang_getTokenLocation(tu, token)
506 var tokenOffset C.unsigned
507 C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset)
508 if i == 0 {
509 // The first token is the macro name itself.
510 // Skip it (after using its location).
511 startOffset = int(tokenOffset)
512 } else {
513 // Later tokens are the macro contents.
514 for int(tokenOffset) > (startOffset + sourceBuf.Len()) {
515 // Pad the source text with whitespace (that must have been
516 // present in the original source as well).
517 sourceBuf.WriteByte(' ')
518 }
519 sourceBuf.WriteString(spelling)
520 }
521 }
522 value = sourceBuf.String()
523
524 // Obtain the position of this token. This is the position of the first
525 // character in the 'value' string and is used to report errors at the
526 // correct location in the source file.
527 pos = f.getCursorPosition(c)
528
529 return
530 }
531
532 func getString(clangString C.CXString) (s string) {
533 rawString := C.clang_getCString(clangString)
534 s = C.GoString(rawString)
535 C.clang_disposeString(clangString)
536 return
537 }
538
539 //export moxie_clang_globals_visitor
540 func moxie_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
541 f := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoFile)
542 switch C.moxie_clang_getCursorKind(c) {
543 case C.CXCursor_FunctionDecl:
544 name := getString(C.moxie_clang_getCursorSpelling(c))
545 f.names[name] = c
546 case C.CXCursor_StructDecl:
547 name := getString(C.moxie_clang_getCursorSpelling(c))
548 if name != "" {
549 f.names["struct_"+name] = c
550 }
551 case C.CXCursor_UnionDecl:
552 name := getString(C.moxie_clang_getCursorSpelling(c))
553 if name != "" {
554 f.names["union_"+name] = c
555 }
556 case C.CXCursor_TypedefDecl:
557 typedefType := C.moxie_clang_getCursorType(c)
558 name := getString(C.clang_getTypedefName(typedefType))
559 f.names[name] = c
560 case C.CXCursor_VarDecl:
561 name := getString(C.moxie_clang_getCursorSpelling(c))
562 f.names[name] = c
563 case C.CXCursor_MacroDefinition:
564 name := getString(C.moxie_clang_getCursorSpelling(c))
565 f.names[name] = c
566 case C.CXCursor_EnumDecl:
567 name := getString(C.moxie_clang_getCursorSpelling(c))
568 if name != "" {
569 // Named enum, which can be referenced from Go using C.enum_foo.
570 f.names["enum_"+name] = c
571 }
572 // The enum fields are in global scope, so recurse to visit them.
573 return C.CXChildVisit_Recurse
574 case C.CXCursor_EnumConstantDecl:
575 // We arrive here because of the "Recurse" above.
576 name := getString(C.moxie_clang_getCursorSpelling(c))
577 f.names[name] = c
578 }
579 return C.CXChildVisit_Continue
580 }
581
582 // Get the precise location in the source code. Used for uniquely identifying
583 // source locations.
584 func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) interface{} {
585 clangLocation := C.moxie_clang_getCursorLocation(cursor)
586 var file C.CXFile
587 var line C.unsigned
588 var column C.unsigned
589 C.clang_getFileLocation(clangLocation, &file, &line, &column, nil)
590 location := token.Position{
591 Filename: getString(C.clang_getFileName(file)),
592 Line: int(line),
593 Column: int(column),
594 }
595 if location.Filename == "" || location.Line == 0 {
596 // Not sure when this would happen, but protect from it anyway.
597 f.addError(pos, "could not find file/line information")
598 }
599 return location
600 }
601
602 // getCursorPosition returns a usable token.Pos from a libclang cursor.
603 func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
604 return p.getClangLocationPosition(C.moxie_clang_getCursorLocation(cursor), C.moxie_clang_Cursor_getTranslationUnit(cursor))
605 }
606
607 // getClangLocationPosition returns a usable token.Pos based on a libclang
608 // location and translation unit. If the file for this cursor has not been seen
609 // before, it is read from libclang (which already has the file in memory) and
610 // added to the token.FileSet.
611 func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.CXTranslationUnit) token.Pos {
612 var file C.CXFile
613 var line C.unsigned
614 var column C.unsigned
615 var offset C.unsigned
616 C.clang_getExpansionLocation(location, &file, &line, &column, &offset)
617 if line == 0 || file == nil {
618 // Invalid token.
619 return token.NoPos
620 }
621 filename := getString(C.clang_getFileName(file))
622 if _, ok := p.tokenFiles[filename]; !ok {
623 // File has not been seen before in this package, add line information
624 // now by reading the file from libclang.
625 var size C.size_t
626 sourcePtr := C.clang_getFileContents(tu, file, &size)
627 source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size]
628 lines := []int{0}
629 for i := 0; i < len(source)-1; i++ {
630 if source[i] == '\n' {
631 lines = append(lines, i+1)
632 }
633 }
634 f := p.fset.AddFile(filename, -1, int(size))
635 f.SetLines(lines)
636 p.tokenFiles[filename] = f
637 // Add dummy file AST, to satisfy the type checker.
638 astFile := &ast.File{
639 Package: f.Pos(0),
640 Name: ast.NewIdent(p.packageName),
641 }
642 setASTFileFields(astFile, f.Pos(0), f.Pos(int(size)))
643 p.cgoFiles = append(p.cgoFiles, astFile)
644 }
645 positionFile := p.tokenFiles[filename]
646
647 // Check for alternative line/column information (set with a line directive).
648 var filename2String C.CXString
649 var line2 C.unsigned
650 var column2 C.unsigned
651 C.clang_getPresumedLocation(location, &filename2String, &line2, &column2)
652 filename2 := getString(filename2String)
653 if filename2 != filename || line2 != line || column2 != column {
654 // The location was changed with a preprocessor directive.
655 // TODO: this only works for locations that are added in order. Adding
656 // line/column info to a file that already has line/column info after
657 // the given offset is ignored.
658 positionFile.AddLineColumnInfo(int(offset), filename2, int(line2), int(column2))
659 }
660
661 return positionFile.Pos(int(offset))
662 }
663
664 // addError is a utility function to add an error to the list of errors. It will
665 // convert the token position to a line/column position first, and call
666 // addErrorAt.
667 func (p *cgoPackage) addError(pos token.Pos, msg string) {
668 p.addErrorAt(p.fset.PositionFor(pos, true), msg)
669 }
670
671 // addErrorAfter is like addError, but adds the text `after` to the source
672 // location.
673 func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) {
674 position := p.fset.PositionFor(pos, true)
675 lines := strings.Split(after, "\n")
676 if len(lines) != 1 {
677 // Adjust lines.
678 // For why we can't just do pos+token.Pos(len(after)), see:
679 // https://github.com/golang/go/issues/35803
680 position.Line += len(lines) - 1
681 position.Column = len(lines[len(lines)-1]) + 1
682 } else {
683 position.Column += len(after)
684 }
685 p.addErrorAt(position, msg)
686 }
687
688 // addErrorAt is a utility function to add an error to the list of errors.
689 func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
690 p.errors = append(p.errors, scanner.Error{
691 Pos: position,
692 Msg: msg,
693 })
694 }
695
696 // makeDecayingASTType does the same as makeASTType but takes care of decaying
697 // types (arrays in function parameters, etc). It is otherwise identical to
698 // makeASTType.
699 func (f *cgoFile) makeDecayingASTType(typ C.CXType, pos token.Pos) ast.Expr {
700 // Strip typedefs, if any.
701 underlyingType := typ
702 if underlyingType.kind == C.CXType_Elaborated {
703 // Starting with LLVM 16, the elaborated type is used for more types.
704 // According to the Clang documentation, the elaborated type has no
705 // semantic meaning so can be stripped (it is used to better convey type
706 // name information).
707 // Source:
708 // https://clang.llvm.org/doxygen/classclang_1_1ElaboratedType.html#details
709 // > The type itself is always "sugar", used to express what was written
710 // > in the source code but containing no additional semantic information.
711 underlyingType = C.clang_Type_getNamedType(underlyingType)
712 }
713 if underlyingType.kind == C.CXType_Typedef {
714 c := C.moxie_clang_getTypeDeclaration(underlyingType)
715 underlyingType = C.moxie_clang_getTypedefDeclUnderlyingType(c)
716 // TODO: support a chain of typedefs. At the moment, it seems to get
717 // stuck in an endless loop when trying to get to the most underlying
718 // type.
719 }
720 // Check for decaying type. An example would be an array type in a
721 // parameter. This declaration:
722 // void foo(char buf[6]);
723 // is the same as this one:
724 // void foo(char *buf);
725 // But this one:
726 // void bar(char buf[6][4]);
727 // equals this:
728 // void bar(char *buf[4]);
729 // so not all array dimensions should be stripped, just the first one.
730 // TODO: there are more kinds of decaying types.
731 if underlyingType.kind == C.CXType_ConstantArray {
732 // Apply type decaying.
733 pointeeType := C.clang_getElementType(underlyingType)
734 return &ast.StarExpr{
735 Star: pos,
736 X: f.makeASTType(pointeeType, pos),
737 }
738 }
739 return f.makeASTType(typ, pos)
740 }
741
742 // makeASTType return the ast.Expr for the given libclang type. In other words,
743 // it converts a libclang type to a type in the Go AST.
744 func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
745 var typeName string
746 switch typ.kind {
747 case C.CXType_Char_S, C.CXType_Char_U:
748 typeName = "_Cgo_char"
749 case C.CXType_SChar:
750 typeName = "_Cgo_schar"
751 case C.CXType_UChar:
752 typeName = "_Cgo_uchar"
753 case C.CXType_Short:
754 typeName = "_Cgo_short"
755 case C.CXType_UShort:
756 typeName = "_Cgo_ushort"
757 case C.CXType_Int:
758 typeName = "_Cgo_int"
759 case C.CXType_UInt:
760 typeName = "_Cgo_uint"
761 case C.CXType_Long:
762 typeName = "_Cgo_long"
763 case C.CXType_ULong:
764 typeName = "_Cgo_ulong"
765 case C.CXType_LongLong:
766 typeName = "_Cgo_longlong"
767 case C.CXType_ULongLong:
768 typeName = "_Cgo_ulonglong"
769 case C.CXType_Bool:
770 typeName = "bool"
771 case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble:
772 switch C.clang_Type_getSizeOf(typ) {
773 case 4:
774 typeName = "float32"
775 case 8:
776 typeName = "float64"
777 default:
778 // Don't do anything, rely on the fallback code to show a somewhat
779 // sensible error message like "undeclared name: C.long double".
780 }
781 case C.CXType_Complex:
782 switch C.clang_Type_getSizeOf(typ) {
783 case 8:
784 typeName = "complex64"
785 case 16:
786 typeName = "complex128"
787 }
788 case C.CXType_Pointer:
789 pointeeType := C.clang_getPointeeType(typ)
790 if pointeeType.kind == C.CXType_Void {
791 // void* type is translated to Go as unsafe.Pointer
792 return &ast.SelectorExpr{
793 X: &ast.Ident{
794 NamePos: pos,
795 Name: "unsafe",
796 },
797 Sel: &ast.Ident{
798 NamePos: pos,
799 Name: "Pointer",
800 },
801 }
802 }
803 return &ast.StarExpr{
804 Star: pos,
805 X: f.makeASTType(pointeeType, pos),
806 }
807 case C.CXType_ConstantArray:
808 return &ast.ArrayType{
809 Lbrack: pos,
810 Len: &ast.BasicLit{
811 ValuePos: pos,
812 Kind: token.INT,
813 Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
814 },
815 Elt: f.makeASTType(C.clang_getElementType(typ), pos),
816 }
817 case C.CXType_FunctionProto:
818 // Be compatible with gc, which uses the *[0]byte type for function
819 // pointer types.
820 // Return type [0]byte because this is a function type, not a pointer to
821 // this function type.
822 return &ast.ArrayType{
823 Lbrack: pos,
824 Len: &ast.BasicLit{
825 ValuePos: pos,
826 Kind: token.INT,
827 Value: "0",
828 },
829 Elt: &ast.Ident{
830 NamePos: pos,
831 Name: "byte",
832 },
833 }
834 case C.CXType_Typedef:
835 name := getString(C.clang_getTypedefName(typ))
836 c := C.moxie_clang_getTypeDeclaration(typ)
837 return &ast.Ident{
838 NamePos: pos,
839 Name: f.getASTDeclName(name, c, false),
840 }
841 case C.CXType_Elaborated:
842 underlying := C.clang_Type_getNamedType(typ)
843 switch underlying.kind {
844 case C.CXType_Record:
845 return f.makeASTType(underlying, pos)
846 case C.CXType_Enum:
847 return f.makeASTType(underlying, pos)
848 case C.CXType_Typedef:
849 return f.makeASTType(underlying, pos)
850 default:
851 typeKindSpelling := getString(C.clang_getTypeKindSpelling(underlying.kind))
852 f.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
853 typeName = "<unknown>"
854 }
855 case C.CXType_Record:
856 cursor := C.moxie_clang_getTypeDeclaration(typ)
857 name := getString(C.moxie_clang_getCursorSpelling(cursor))
858 var cgoRecordPrefix string
859 switch C.moxie_clang_getCursorKind(cursor) {
860 case C.CXCursor_StructDecl:
861 cgoRecordPrefix = "struct_"
862 case C.CXCursor_UnionDecl:
863 cgoRecordPrefix = "union_"
864 default:
865 // makeASTRecordType will create an appropriate error.
866 cgoRecordPrefix = "record_"
867 }
868 if name == "" || C.moxie_clang_Cursor_isAnonymous(cursor) != 0 {
869 // Anonymous record, probably inside a typedef.
870 location := f.getUniqueLocationID(pos, cursor)
871 name = f.getUnnamedDeclName("_Ctype_"+cgoRecordPrefix+"__", location)
872 } else {
873 name = cgoRecordPrefix + name
874 }
875 return &ast.Ident{
876 NamePos: pos,
877 Name: f.getASTDeclName(name, cursor, false),
878 }
879 case C.CXType_Enum:
880 cursor := C.moxie_clang_getTypeDeclaration(typ)
881 name := getString(C.moxie_clang_getCursorSpelling(cursor))
882 if name == "" {
883 // Anonymous enum, probably inside a typedef.
884 location := f.getUniqueLocationID(pos, cursor)
885 name = f.getUnnamedDeclName("_Ctype_enum___", location)
886 } else {
887 name = "enum_" + name
888 }
889 return &ast.Ident{
890 NamePos: pos,
891 Name: f.getASTDeclName(name, cursor, false),
892 }
893 }
894 if typeName == "" {
895 // Report this as an error.
896 typeSpelling := getString(C.clang_getTypeSpelling(typ))
897 typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind))
898 f.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling))
899 typeName = "_Cgo_<unknown>"
900 }
901 return &ast.Ident{
902 NamePos: pos,
903 Name: typeName,
904 }
905 }
906
907 // getIntegerType returns an AST node that defines types such as C.int.
908 func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSpec {
909 pos := p.getCursorPosition(cursor)
910
911 // Find a Go type that matches the size and signedness of the given C type.
912 underlyingType := C.moxie_clang_getTypedefDeclUnderlyingType(cursor)
913 var goName string
914 typeSize := C.clang_Type_getSizeOf(underlyingType)
915 switch name {
916 case "_Cgo_char":
917 if typeSize != 1 {
918 // This happens for some very special purpose architectures
919 // (DSPs etc.) that are not currently targeted.
920 // https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/
921 p.addError(pos, fmt.Sprintf("unknown char width: %d", typeSize))
922 }
923 switch underlyingType.kind {
924 case C.CXType_Char_S:
925 goName = "int8"
926 case C.CXType_Char_U:
927 goName = "uint8"
928 }
929 case "_Cgo_schar", "_Cgo_short", "_Cgo_int", "_Cgo_long", "_Cgo_longlong":
930 switch typeSize {
931 case 1:
932 goName = "int8"
933 case 2:
934 goName = "int16"
935 case 4:
936 goName = "int32"
937 case 8:
938 goName = "int64"
939 }
940 case "_Cgo_uchar", "_Cgo_ushort", "_Cgo_uint", "_Cgo_ulong", "_Cgo_ulonglong":
941 switch typeSize {
942 case 1:
943 goName = "uint8"
944 case 2:
945 goName = "uint16"
946 case 4:
947 goName = "uint32"
948 case 8:
949 goName = "uint64"
950 }
951 }
952
953 if goName == "" { // should not happen
954 p.addError(pos, "internal error: did not find Go type for C type "+name)
955 goName = "int"
956 }
957
958 // Construct an *ast.TypeSpec for this type.
959 obj := &ast.Object{
960 Kind: ast.Typ,
961 Name: name,
962 }
963 spec := &ast.TypeSpec{
964 Name: &ast.Ident{
965 NamePos: pos,
966 Name: name,
967 Obj: obj,
968 },
969 Type: &ast.Ident{
970 NamePos: pos,
971 Name: goName,
972 },
973 }
974 obj.Decl = spec
975 return spec
976 }
977
978 // makeASTRecordType parses a C record (struct or union) and translates it into
979 // a Go struct type.
980 func (f *cgoFile) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elaboratedTypeInfo {
981 fieldList := &ast.FieldList{
982 Opening: pos,
983 Closing: pos,
984 }
985 var bitfieldList []bitfieldInfo
986 inBitfield := false
987 bitfieldNum := 0
988 ref := storedRefs.Put(struct {
989 fieldList *ast.FieldList
990 file *cgoFile
991 inBitfield *bool
992 bitfieldNum *int
993 bitfieldList *[]bitfieldInfo
994 }{fieldList, f, &inBitfield, &bitfieldNum, &bitfieldList})
995 defer storedRefs.Remove(ref)
996 C.moxie_clang_visitChildren(cursor, C.CXCursorVisitor(C.moxie_clang_struct_visitor), C.CXClientData(ref))
997 renameFieldKeywords(fieldList)
998 switch C.moxie_clang_getCursorKind(cursor) {
999 case C.CXCursor_StructDecl:
1000 return &elaboratedTypeInfo{
1001 typeExpr: &ast.StructType{
1002 Struct: pos,
1003 Fields: fieldList,
1004 },
1005 pos: pos,
1006 bitfields: bitfieldList,
1007 }
1008 case C.CXCursor_UnionDecl:
1009 typeInfo := &elaboratedTypeInfo{
1010 typeExpr: &ast.StructType{
1011 Struct: pos,
1012 Fields: fieldList,
1013 },
1014 pos: pos,
1015 bitfields: bitfieldList,
1016 }
1017 if len(fieldList.List) <= 1 {
1018 // Useless union, treat it as a regular struct.
1019 return typeInfo
1020 }
1021 if bitfieldList != nil {
1022 // This is valid C... but please don't do this.
1023 f.addError(pos, "bitfield in a union is not supported")
1024 }
1025 typ := C.moxie_clang_getCursorType(cursor)
1026 alignInBytes := int64(C.clang_Type_getAlignOf(typ))
1027 sizeInBytes := int64(C.clang_Type_getSizeOf(typ))
1028 if sizeInBytes == 0 {
1029 f.addError(pos, "zero-length union is not supported")
1030 }
1031 typeInfo.unionSize = sizeInBytes
1032 typeInfo.unionAlign = alignInBytes
1033 return typeInfo
1034 default:
1035 cursorKind := C.moxie_clang_getCursorKind(cursor)
1036 cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
1037 f.addError(pos, fmt.Sprintf("expected StructDecl or UnionDecl, not %s", cursorKindSpelling))
1038 return &elaboratedTypeInfo{
1039 typeExpr: &ast.StructType{
1040 Struct: pos,
1041 },
1042 pos: pos,
1043 }
1044 }
1045 }
1046
1047 //export moxie_clang_struct_visitor
1048 func moxie_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
1049 passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
1050 fieldList *ast.FieldList
1051 file *cgoFile
1052 inBitfield *bool
1053 bitfieldNum *int
1054 bitfieldList *[]bitfieldInfo
1055 })
1056 fieldList := passed.fieldList
1057 f := passed.file
1058 inBitfield := passed.inBitfield
1059 bitfieldNum := passed.bitfieldNum
1060 bitfieldList := passed.bitfieldList
1061 pos := f.getCursorPosition(c)
1062 switch cursorKind := C.moxie_clang_getCursorKind(c); cursorKind {
1063 case C.CXCursor_FieldDecl:
1064 // Expected. This is a regular field.
1065 case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
1066 // Ignore. The next field will be the struct/union itself.
1067 return C.CXChildVisit_Continue
1068 default:
1069 cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
1070 f.addError(pos, fmt.Sprintf("expected FieldDecl in struct or union, not %s", cursorKindSpelling))
1071 return C.CXChildVisit_Continue
1072 }
1073 name := getString(C.moxie_clang_getCursorSpelling(c))
1074 if name == "" {
1075 // Assume this is a bitfield of 0 bits.
1076 // Warning: this is not necessarily true!
1077 return C.CXChildVisit_Continue
1078 }
1079 typ := C.moxie_clang_getCursorType(c)
1080 field := &ast.Field{
1081 Type: f.makeASTType(typ, f.getCursorPosition(c)),
1082 }
1083 offsetof := int64(C.clang_Type_getOffsetOf(C.moxie_clang_getCursorType(parent), C.CString(name)))
1084 alignOf := int64(C.clang_Type_getAlignOf(typ) * 8)
1085 bitfieldOffset := offsetof % alignOf
1086 if bitfieldOffset != 0 {
1087 if C.moxie_clang_Cursor_isBitField(c) != 1 {
1088 f.addError(pos, "expected a bitfield")
1089 return C.CXChildVisit_Continue
1090 }
1091 if !*inBitfield {
1092 *bitfieldNum++
1093 }
1094 bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum)
1095 prevField := fieldList.List[len(fieldList.List)-1]
1096 if !*inBitfield {
1097 // The previous element also was a bitfield, but wasn't noticed
1098 // then. Add it now.
1099 *inBitfield = true
1100 *bitfieldList = append(*bitfieldList, bitfieldInfo{
1101 field: prevField,
1102 name: prevField.Names[0].Name,
1103 startBit: 0,
1104 pos: prevField.Names[0].NamePos,
1105 })
1106 prevField.Names[0].Name = bitfieldName
1107 prevField.Names[0].Obj.Name = bitfieldName
1108 }
1109 prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
1110 prevBitfield.endBit = bitfieldOffset
1111 *bitfieldList = append(*bitfieldList, bitfieldInfo{
1112 field: prevField,
1113 name: name,
1114 startBit: bitfieldOffset,
1115 pos: pos,
1116 })
1117 return C.CXChildVisit_Continue
1118 }
1119 *inBitfield = false
1120 field.Names = []*ast.Ident{
1121 {
1122 NamePos: pos,
1123 Name: name,
1124 Obj: &ast.Object{
1125 Kind: ast.Var,
1126 Name: name,
1127 Decl: field,
1128 },
1129 },
1130 }
1131 fieldList.List = append(fieldList.List, field)
1132 return C.CXChildVisit_Continue
1133 }
1134
1135 //export moxie_clang_inclusion_visitor
1136 func moxie_clang_inclusion_visitor(includedFile C.CXFile, inclusionStack *C.CXSourceLocation, includeLen C.unsigned, clientData C.CXClientData) {
1137 callback := storedRefs.Get(unsafe.Pointer(clientData)).(func(C.CXFile))
1138 callback(includedFile)
1139 }
1140