github.com/aykevl/tinygo@v0.5.0/loader/libclang.go (about)

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