1// Copyright 2015 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package cgocall defines an Analyzer that detects some violations of
6// the cgo pointer passing rules.
7package cgocall
8
9import (
10	"fmt"
11	"go/ast"
12	"go/format"
13	"go/parser"
14	"go/token"
15	"go/types"
16	"log"
17	"os"
18	"strconv"
19
20	"golang.org/x/tools/go/analysis"
21	"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
22	"golang.org/x/tools/go/ast/astutil"
23)
24
25const debug = false
26
27const Doc = `detect some violations of the cgo pointer passing rules
28
29Check for invalid cgo pointer passing.
30This looks for code that uses cgo to call C code passing values
31whose types are almost always invalid according to the cgo pointer
32sharing rules.
33Specifically, it warns about attempts to pass a Go chan, map, func,
34or slice to C, either directly, or via a pointer, array, or struct.`
35
36var Analyzer = &analysis.Analyzer{
37	Name:             "cgocall",
38	Doc:              Doc,
39	URL:              "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/cgocall",
40	RunDespiteErrors: true,
41	Run:              run,
42}
43
44func run(pass *analysis.Pass) (interface{}, error) {
45	if !analysisutil.Imports(pass.Pkg, "runtime/cgo") {
46		return nil, nil // doesn't use cgo
47	}
48
49	cgofiles, info, err := typeCheckCgoSourceFiles(pass.Fset, pass.Pkg, pass.Files, pass.TypesInfo, pass.TypesSizes)
50	if err != nil {
51		return nil, err
52	}
53	for _, f := range cgofiles {
54		checkCgo(pass.Fset, f, info, pass.Reportf)
55	}
56	return nil, nil
57}
58
59func checkCgo(fset *token.FileSet, f *ast.File, info *types.Info, reportf func(token.Pos, string, ...interface{})) {
60	ast.Inspect(f, func(n ast.Node) bool {
61		call, ok := n.(*ast.CallExpr)
62		if !ok {
63			return true
64		}
65
66		// Is this a C.f() call?
67		var name string
68		if sel, ok := astutil.Unparen(call.Fun).(*ast.SelectorExpr); ok {
69			if id, ok := sel.X.(*ast.Ident); ok && id.Name == "C" {
70				name = sel.Sel.Name
71			}
72		}
73		if name == "" {
74			return true // not a call we need to check
75		}
76
77		// A call to C.CBytes passes a pointer but is always safe.
78		if name == "CBytes" {
79			return true
80		}
81
82		if debug {
83			log.Printf("%s: call to C.%s", fset.Position(call.Lparen), name)
84		}
85
86		for _, arg := range call.Args {
87			if !typeOKForCgoCall(cgoBaseType(info, arg), make(map[types.Type]bool)) {
88				reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
89				break
90			}
91
92			// Check for passing the address of a bad type.
93			if conv, ok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 &&
94				isUnsafePointer(info, conv.Fun) {
95				arg = conv.Args[0]
96			}
97			if u, ok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND {
98				if !typeOKForCgoCall(cgoBaseType(info, u.X), make(map[types.Type]bool)) {
99					reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
100					break
101				}
102			}
103		}
104		return true
105	})
106}
107
108// typeCheckCgoSourceFiles returns type-checked syntax trees for the raw
109// cgo files of a package (those that import "C"). Such files are not
110// Go, so there may be gaps in type information around C.f references.
111//
112// This checker was initially written in vet to inspect raw cgo source
113// files using partial type information. However, Analyzers in the new
114// analysis API are presented with the type-checked, "cooked" Go ASTs
115// resulting from cgo-processing files, so we must choose between
116// working with the cooked file generated by cgo (which was tried but
117// proved fragile) or locating the raw cgo file (e.g. from //line
118// directives) and working with that, as we now do.
119//
120// Specifically, we must type-check the raw cgo source files (or at
121// least the subtrees needed for this analyzer) in an environment that
122// simulates the rest of the already type-checked package.
123//
124// For example, for each raw cgo source file in the original package,
125// such as this one:
126//
127//	package p
128//	import "C"
129//	import "fmt"
130//	type T int
131//	const k = 3
132//	var x, y = fmt.Println()
133//	func f() { ... }
134//	func g() { ... C.malloc(k) ... }
135//	func (T) f(int) string { ... }
136//
137// we synthesize a new ast.File, shown below, that dot-imports the
138// original "cooked" package using a special name ("·this·"), so that all
139// references to package members resolve correctly. (References to
140// unexported names cause an "unexported" error, which we ignore.)
141//
142// To avoid shadowing names imported from the cooked package,
143// package-level declarations in the new source file are modified so
144// that they do not declare any names.
145// (The cgocall analysis is concerned with uses, not declarations.)
146// Specifically, type declarations are discarded;
147// all names in each var and const declaration are blanked out;
148// each method is turned into a regular function by turning
149// the receiver into the first parameter;
150// and all functions are renamed to "_".
151//
152//	package p
153//	import . "·this·" // declares T, k, x, y, f, g, T.f
154//	import "C"
155//	import "fmt"
156//	const _ = 3
157//	var _, _ = fmt.Println()
158//	func _() { ... }
159//	func _() { ... C.malloc(k) ... }
160//	func _(T, int) string { ... }
161//
162// In this way, the raw function bodies and const/var initializer
163// expressions are preserved but refer to the "cooked" objects imported
164// from "·this·", and none of the transformed package-level declarations
165// actually declares anything. In the example above, the reference to k
166// in the argument of the call to C.malloc resolves to "·this·".k, which
167// has an accurate type.
168//
169// This approach could in principle be generalized to more complex
170// analyses on raw cgo files. One could synthesize a "C" package so that
171// C.f would resolve to "·this·"._C_func_f, for example. But we have
172// limited ourselves here to preserving function bodies and initializer
173// expressions since that is all that the cgocall analyzer needs.
174func typeCheckCgoSourceFiles(fset *token.FileSet, pkg *types.Package, files []*ast.File, info *types.Info, sizes types.Sizes) ([]*ast.File, *types.Info, error) {
175	const thispkg = "·this·"
176
177	// Which files are cgo files?
178	var cgoFiles []*ast.File
179	importMap := map[string]*types.Package{thispkg: pkg}
180	for _, raw := range files {
181		// If f is a cgo-generated file, Position reports
182		// the original file, honoring //line directives.
183		filename := fset.Position(raw.Pos()).Filename
184		f, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution)
185		if err != nil {
186			return nil, nil, fmt.Errorf("can't parse raw cgo file: %v", err)
187		}
188		found := false
189		for _, spec := range f.Imports {
190			if spec.Path.Value == `"C"` {
191				found = true
192				break
193			}
194		}
195		if !found {
196			continue // not a cgo file
197		}
198
199		// Record the original import map.
200		for _, spec := range raw.Imports {
201			path, _ := strconv.Unquote(spec.Path.Value)
202			importMap[path] = imported(info, spec)
203		}
204
205		// Add special dot-import declaration:
206		//    import . "·this·"
207		var decls []ast.Decl
208		decls = append(decls, &ast.GenDecl{
209			Tok: token.IMPORT,
210			Specs: []ast.Spec{
211				&ast.ImportSpec{
212					Name: &ast.Ident{Name: "."},
213					Path: &ast.BasicLit{
214						Kind:  token.STRING,
215						Value: strconv.Quote(thispkg),
216					},
217				},
218			},
219		})
220
221		// Transform declarations from the raw cgo file.
222		for _, decl := range f.Decls {
223			switch decl := decl.(type) {
224			case *ast.GenDecl:
225				switch decl.Tok {
226				case token.TYPE:
227					// Discard type declarations.
228					continue
229				case token.IMPORT:
230					// Keep imports.
231				case token.VAR, token.CONST:
232					// Blank the declared var/const names.
233					for _, spec := range decl.Specs {
234						spec := spec.(*ast.ValueSpec)
235						for i := range spec.Names {
236							spec.Names[i].Name = "_"
237						}
238					}
239				}
240			case *ast.FuncDecl:
241				// Blank the declared func name.
242				decl.Name.Name = "_"
243
244				// Turn a method receiver:  func (T) f(P) R {...}
245				// into regular parameter:  func _(T, P) R {...}
246				if decl.Recv != nil {
247					var params []*ast.Field
248					params = append(params, decl.Recv.List...)
249					params = append(params, decl.Type.Params.List...)
250					decl.Type.Params.List = params
251					decl.Recv = nil
252				}
253			}
254			decls = append(decls, decl)
255		}
256		f.Decls = decls
257		if debug {
258			format.Node(os.Stderr, fset, f) // debugging
259		}
260		cgoFiles = append(cgoFiles, f)
261	}
262	if cgoFiles == nil {
263		return nil, nil, nil // nothing to do (can't happen?)
264	}
265
266	// Type-check the synthetic files.
267	tc := &types.Config{
268		FakeImportC: true,
269		Importer: importerFunc(func(path string) (*types.Package, error) {
270			return importMap[path], nil
271		}),
272		Sizes: sizes,
273		Error: func(error) {}, // ignore errors (e.g. unused import)
274	}
275	setGoVersion(tc, pkg)
276
277	// It's tempting to record the new types in the
278	// existing pass.TypesInfo, but we don't own it.
279	altInfo := &types.Info{
280		Types: make(map[ast.Expr]types.TypeAndValue),
281	}
282	tc.Check(pkg.Path(), fset, cgoFiles, altInfo)
283
284	return cgoFiles, altInfo, nil
285}
286
287// cgoBaseType tries to look through type conversions involving
288// unsafe.Pointer to find the real type. It converts:
289//
290//	unsafe.Pointer(x) => x
291//	*(*unsafe.Pointer)(unsafe.Pointer(&x)) => x
292func cgoBaseType(info *types.Info, arg ast.Expr) types.Type {
293	switch arg := arg.(type) {
294	case *ast.CallExpr:
295		if len(arg.Args) == 1 && isUnsafePointer(info, arg.Fun) {
296			return cgoBaseType(info, arg.Args[0])
297		}
298	case *ast.StarExpr:
299		call, ok := arg.X.(*ast.CallExpr)
300		if !ok || len(call.Args) != 1 {
301			break
302		}
303		// Here arg is *f(v).
304		t := info.Types[call.Fun].Type
305		if t == nil {
306			break
307		}
308		ptr, ok := t.Underlying().(*types.Pointer)
309		if !ok {
310			break
311		}
312		// Here arg is *(*p)(v)
313		elem, ok := ptr.Elem().Underlying().(*types.Basic)
314		if !ok || elem.Kind() != types.UnsafePointer {
315			break
316		}
317		// Here arg is *(*unsafe.Pointer)(v)
318		call, ok = call.Args[0].(*ast.CallExpr)
319		if !ok || len(call.Args) != 1 {
320			break
321		}
322		// Here arg is *(*unsafe.Pointer)(f(v))
323		if !isUnsafePointer(info, call.Fun) {
324			break
325		}
326		// Here arg is *(*unsafe.Pointer)(unsafe.Pointer(v))
327		u, ok := call.Args[0].(*ast.UnaryExpr)
328		if !ok || u.Op != token.AND {
329			break
330		}
331		// Here arg is *(*unsafe.Pointer)(unsafe.Pointer(&v))
332		return cgoBaseType(info, u.X)
333	}
334
335	return info.Types[arg].Type
336}
337
338// typeOKForCgoCall reports whether the type of arg is OK to pass to a
339// C function using cgo. This is not true for Go types with embedded
340// pointers. m is used to avoid infinite recursion on recursive types.
341func typeOKForCgoCall(t types.Type, m map[types.Type]bool) bool {
342	if t == nil || m[t] {
343		return true
344	}
345	m[t] = true
346	switch t := t.Underlying().(type) {
347	case *types.Chan, *types.Map, *types.Signature, *types.Slice:
348		return false
349	case *types.Pointer:
350		return typeOKForCgoCall(t.Elem(), m)
351	case *types.Array:
352		return typeOKForCgoCall(t.Elem(), m)
353	case *types.Struct:
354		for i := 0; i < t.NumFields(); i++ {
355			if !typeOKForCgoCall(t.Field(i).Type(), m) {
356				return false
357			}
358		}
359	}
360	return true
361}
362
363func isUnsafePointer(info *types.Info, e ast.Expr) bool {
364	t := info.Types[e].Type
365	return t != nil && t.Underlying() == types.Typ[types.UnsafePointer]
366}
367
368type importerFunc func(path string) (*types.Package, error)
369
370func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
371
372// TODO(adonovan): make this a library function or method of Info.
373func imported(info *types.Info, spec *ast.ImportSpec) *types.Package {
374	obj, ok := info.Implicits[spec]
375	if !ok {
376		obj = info.Defs[spec.Name] // renaming import
377	}
378	return obj.(*types.PkgName).Imported()
379}
380