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