mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
505 lines
15 KiB
Go
505 lines
15 KiB
Go
// Copyright 2021 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package checklinkname ensures that linkname declarations match their source.
|
|
package checklinkname
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/analysis"
|
|
)
|
|
|
|
// Analyzer implements the checklinkname analyzer.
|
|
var Analyzer = &analysis.Analyzer{
|
|
Name: "checklinkname",
|
|
Doc: "verifies that linkname declarations match their source",
|
|
Run: run,
|
|
FactTypes: []analysis.Fact{
|
|
(*UnresolvedLinknames)(nil),
|
|
(*ResolvedSymbols)(nil),
|
|
},
|
|
}
|
|
|
|
// symbolMap is a map of all known or unknown symbols, with their signature.
|
|
//
|
|
// It is keyed by package, then symbol, with the simplified signature as the value.
|
|
type symbolMap map[string]map[string]string
|
|
|
|
// mergeOrResolve checks all symbol signatures.
|
|
//
|
|
// If merge is true, then the resulting map will be the union of the two maps.
|
|
// If merge is false, then the resulting map will be the first with the second
|
|
// symbolMap subtracted.
|
|
func (s *symbolMap) mergeOrResolve(pass *analysis.Pass, other symbolMap, merge bool, resolvePos func(pkgName, symbolName string) token.Pos) {
|
|
for pkgName, symbols := range other {
|
|
localSymbols, ok := (*s)[pkgName]
|
|
if !ok {
|
|
if merge {
|
|
(*s)[pkgName] = symbols
|
|
}
|
|
continue
|
|
}
|
|
var resolved []string // Used only if !merge.
|
|
for symbolName, otherSig := range symbols {
|
|
localSig, ok := localSymbols[symbolName]
|
|
if !ok {
|
|
if merge {
|
|
localSymbols[symbolName] = otherSig
|
|
}
|
|
continue
|
|
}
|
|
if localSig != otherSig {
|
|
switch symbolName {
|
|
case "ifaceE2I":
|
|
// The runtime uses a different signature for this than other packages, e.g.
|
|
// the runtime has func(uintptr, eface, uintptr) whereas externally it is
|
|
// declared as func(uintptr, any, uintptr). This is a clever way to directly
|
|
// access the interface object, but breaks this change. We ignore this.
|
|
default:
|
|
pass.Reportf(resolvePos(pkgName, symbolName), "symbol %q has signature %q, expected signature %q", symbolName, localSig, otherSig)
|
|
}
|
|
}
|
|
if !merge {
|
|
resolved = append(resolved, symbolName)
|
|
}
|
|
}
|
|
for _, symbolName := range resolved {
|
|
delete(localSymbols, symbolName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ResolvedSymbols is a fact containing known symbols and their simplified type.
|
|
type ResolvedSymbols symbolMap
|
|
|
|
// AFact implements analysis.Fact.AFact.
|
|
func (*ResolvedSymbols) AFact() {}
|
|
|
|
// merge merges all known symbols.
|
|
func (r *ResolvedSymbols) merge(pass *analysis.Pass, other ResolvedSymbols, resolvePos func(pkgName, symbolName string) token.Pos) {
|
|
((*symbolMap)(r)).mergeOrResolve(pass, (symbolMap)(other), true /* merge */, resolvePos)
|
|
}
|
|
|
|
// UnresolvedLinknames is a fact containing symbols that have not been validated.
|
|
type UnresolvedLinknames symbolMap
|
|
|
|
// AFact implements analysis.Fact.AFact.
|
|
func (*UnresolvedLinknames) AFact() {}
|
|
|
|
// merge merges all unknown symbols.
|
|
func (u *UnresolvedLinknames) merge(pass *analysis.Pass, other UnresolvedLinknames, resolvePos func(pkgName, symbolName string) token.Pos) {
|
|
((*symbolMap)(u)).mergeOrResolve(pass, (symbolMap)(other), true /* merge */, resolvePos)
|
|
}
|
|
|
|
// resolve resolves all known symbols.
|
|
func (u *UnresolvedLinknames) resolve(pass *analysis.Pass, other ResolvedSymbols, resolvePos func(pkgName, symbolName string) token.Pos) {
|
|
((*symbolMap)(u)).mergeOrResolve(pass, (symbolMap)(other), false /* merge */, resolvePos)
|
|
}
|
|
|
|
// resolveRemaining resolves all remaining names.
|
|
//
|
|
// This should be called only for final linking, when we expect that all symbols
|
|
// have been included directly or indirectly.
|
|
func (u *UnresolvedLinknames) resolveRemaining(pass *analysis.Pass, resolvePos func(pkgName, symbolName string) token.Pos) {
|
|
for pkgName, pur := range *u {
|
|
for symbolName := range pur {
|
|
switch {
|
|
case pkgName == "main" && symbolName == ".inittask":
|
|
// This seems like a special case; it is not available for analysis.
|
|
case pkgName == "runtime" && strings.HasPrefix(symbolName, "_cgo_"):
|
|
// Ignore all _cgo_-related symbols; see below.
|
|
default:
|
|
pass.Reportf(resolvePos(pkgName, symbolName), "remote symbol %q not defined in package %q", symbolName, pkgName)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// "go:linkname" can be rather confusing. https://pkg.go.dev/cmd/compile says:
|
|
//
|
|
// //go:linkname localname [importpath.name]
|
|
//
|
|
// This special directive does not apply to the Go code that follows it.
|
|
// Instead, the //go:linkname directive instructs the compiler to use
|
|
// “importpath.name” as the object file symbol name for the variable or
|
|
// function declared as “localname” in the source code. If the
|
|
// “importpath.name” argument is omitted, the directive uses the symbol's
|
|
// default object file symbol name and only has the effect of making the symbol
|
|
// accessible to other packages. Because this directive can subvert the type
|
|
// system and package modularity, it is only enabled in files that have
|
|
// imported "unsafe".
|
|
//
|
|
// In the general case, the local symbol is a function declaration, and the
|
|
// remote symbol is a real function in the standard library.
|
|
|
|
// linknameSymbol describes the symbol names in a single //go:linkname
|
|
// directive.
|
|
type linknameSymbols struct {
|
|
pos token.Pos
|
|
local string
|
|
remote string
|
|
}
|
|
|
|
func findLinknames(pass *analysis.Pass, f *ast.File) (names []linknameSymbols) {
|
|
for _, cg := range f.Comments {
|
|
for _, c := range cg.List {
|
|
if len(c.Text) <= 2 || !strings.HasPrefix(c.Text[2:], "go:linkname ") {
|
|
continue
|
|
}
|
|
|
|
f := strings.Fields(c.Text)
|
|
if len(f) < 2 || len(f) > 3 {
|
|
// Malformed linkname. This is the same error the compiler emits.
|
|
pass.Reportf(c.Slash, "usage: //go:linkname localname [linkname]")
|
|
}
|
|
|
|
if len(f) == 2 {
|
|
// "If the “importpath.name” argument is
|
|
// omitted, the directive uses the symbol's
|
|
// default object file symbol name and only has
|
|
// the effect of making the symbol accessible
|
|
// to other packages."
|
|
// -https://golang.org/cmd/compile
|
|
//
|
|
// There is no type-checking to be done here.
|
|
continue
|
|
}
|
|
|
|
names = append(names, linknameSymbols{
|
|
pos: c.Slash,
|
|
local: f[1],
|
|
remote: f[2],
|
|
})
|
|
}
|
|
}
|
|
|
|
return names
|
|
}
|
|
|
|
func splitSymbol(pkg *types.Package, symbol string) (packagePath, name string) {
|
|
// Note that some runtime symbols can have multiple dots. e.g.,
|
|
// runtime..init_task.
|
|
s := strings.SplitN(symbol, ".", 2)
|
|
|
|
switch len(s) {
|
|
case 1:
|
|
// Package name omitted, use current package.
|
|
return pkg.Path(), symbol
|
|
case 2:
|
|
return s[0], s[1]
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
// kindStr stores strings for basic kinds.
|
|
var kindStr = map[types.BasicKind]string{
|
|
types.Bool: "bool",
|
|
types.Int: "int",
|
|
types.Int8: "int8",
|
|
types.Int16: "int16",
|
|
types.Int32: "int32",
|
|
types.Int64: "int64",
|
|
types.Uint: "uint",
|
|
types.Uint8: "uint8",
|
|
types.Uint16: "uint16",
|
|
types.Uint32: "uint32",
|
|
types.Uint64: "uint64",
|
|
types.Uintptr: "uintptr",
|
|
types.Float32: "float32",
|
|
types.Float64: "float64",
|
|
types.Complex64: "complex64",
|
|
types.Complex128: "complex128",
|
|
types.String: "string",
|
|
types.UnsafePointer: "uintptr", // See simplifyType; we cheat for pointers.
|
|
}
|
|
|
|
type memoizer[T comparable] struct {
|
|
recentKeys [8]T
|
|
recentValues [8]string
|
|
nextEviction int
|
|
}
|
|
|
|
func (m *memoizer[T]) get(x T) (string, bool) {
|
|
for i, v := range m.recentKeys {
|
|
if v == x {
|
|
return m.recentValues[i], true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func (m *memoizer[T]) add(x T, s string) {
|
|
m.recentKeys[m.nextEviction%len(m.recentKeys)] = x
|
|
m.recentValues[m.nextEviction%len(m.recentValues)] = s
|
|
m.nextEviction++
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
var (
|
|
memoizedSlices memoizer[*types.Slice]
|
|
memoizedArrays memoizer[*types.Array]
|
|
memoizedMaps memoizer[*types.Map]
|
|
memoizedStructs memoizer[*types.Struct]
|
|
memoizedInterfaces memoizer[*types.Interface]
|
|
memoizedSignatures memoizer[*types.Signature]
|
|
)
|
|
|
|
// simplifyType returns a simplified type string.
|
|
func simplifyType(t types.Type, maxDepth int) (string, int) {
|
|
if maxDepth <= 0 {
|
|
return "...", maxDepth // Don't bother.
|
|
}
|
|
switch x := t.Underlying().(type) {
|
|
case *types.Pointer:
|
|
// Allow cheating for pointers.
|
|
return "uintptr", maxDepth
|
|
case *types.Signature:
|
|
// For functions, recursively simplify.
|
|
if s, ok := memoizedSignatures.get(x); ok {
|
|
return s, maxDepth
|
|
}
|
|
s, minDepth := makeSignature(x, maxDepth-1)
|
|
if minDepth > 0 {
|
|
memoizedSignatures.add(x, s)
|
|
}
|
|
return s, minDepth
|
|
case *types.Struct:
|
|
// Build the simplified struct definition.
|
|
if s, ok := memoizedStructs.get(x); ok {
|
|
return s, maxDepth
|
|
}
|
|
s, minDepth := makeStructSignature(x, maxDepth-1)
|
|
if minDepth > 0 {
|
|
memoizedStructs.add(x, s)
|
|
}
|
|
return s, minDepth
|
|
case *types.Basic:
|
|
// For any basic type (Int8, etc.), we represent as the kind.
|
|
if s, ok := kindStr[x.Kind()]; ok {
|
|
return s, maxDepth
|
|
}
|
|
return fmt.Sprintf("kind#%d", t), maxDepth
|
|
case *types.Interface:
|
|
// Write the interface declaration, it's usually empty.
|
|
if s, ok := memoizedInterfaces.get(x); ok {
|
|
return s, maxDepth
|
|
}
|
|
s, minDepth := makeInterfaceSignature(x, maxDepth-1)
|
|
if minDepth > 0 {
|
|
memoizedInterfaces.add(x, s)
|
|
}
|
|
return s, minDepth
|
|
case *types.Slice:
|
|
// List as a standard slice.
|
|
if s, ok := memoizedSlices.get(x); ok {
|
|
return s, maxDepth
|
|
}
|
|
s, minDepth := simplifyType(x.Elem(), maxDepth-1)
|
|
s = fmt.Sprintf("[]%s", s)
|
|
if minDepth > 0 {
|
|
memoizedSlices.add(x, s)
|
|
}
|
|
return s, minDepth
|
|
case *types.Array:
|
|
// List as an inline array definition.
|
|
if s, ok := memoizedArrays.get(x); ok {
|
|
return s, maxDepth
|
|
}
|
|
s, minDepth := simplifyType(x.Elem(), maxDepth-1)
|
|
s = fmt.Sprintf("[%d]%s", x.Len(), s)
|
|
if minDepth > 0 {
|
|
memoizedArrays.add(x, s)
|
|
}
|
|
return s, minDepth
|
|
case *types.Map:
|
|
// List as the standard map.
|
|
if s, ok := memoizedMaps.get(x); ok {
|
|
return s, maxDepth
|
|
}
|
|
keyS, minDepthKey := simplifyType(x.Key(), maxDepth-1)
|
|
valS, minDepthVal := simplifyType(x.Elem(), maxDepth-1)
|
|
s := fmt.Sprintf("map[%s]%s", keyS, valS)
|
|
minDepth := min(minDepthKey, minDepthVal)
|
|
if minDepth > 0 {
|
|
memoizedMaps.add(x, s)
|
|
}
|
|
return s, minDepth
|
|
default:
|
|
// Anything else, use the full string.
|
|
return t.String(), maxDepth
|
|
}
|
|
}
|
|
|
|
// makeStructSignature makes a struct signature.
|
|
func makeStructSignature(str *types.Struct, maxDepth int) (string, int) {
|
|
parts := make([]string, 0, str.NumFields())
|
|
minDepth := maxDepth
|
|
for i := 0; i < len(parts); i++ {
|
|
s, localMinDepth := simplifyType(str.Field(i).Type(), maxDepth)
|
|
parts = append(parts, s)
|
|
minDepth = min(localMinDepth, minDepth)
|
|
}
|
|
return fmt.Sprintf("struct{%s}", strings.Join(parts, ", ")), minDepth
|
|
}
|
|
|
|
// makeInterfaceSignature makes an interface signature.
|
|
func makeInterfaceSignature(iface *types.Interface, maxDepth int) (string, int) {
|
|
parts := make([]string, 0, iface.NumMethods())
|
|
minDepth := maxDepth
|
|
for i := 0; i < len(parts); i++ {
|
|
s, localMinDepth := makeSignature(iface.Method(i).Type().(*types.Signature), maxDepth)
|
|
parts = append(parts, s)
|
|
minDepth = min(localMinDepth, minDepth)
|
|
}
|
|
return fmt.Sprintf("interface{%s}", strings.Join(parts, ",")), minDepth
|
|
}
|
|
|
|
// makeSignature builds the special signature.
|
|
//
|
|
// This function preserves sensitive to basic types, but relaxes specifics around pointers.
|
|
func makeSignature(sig *types.Signature, maxDepth int) (string, int) {
|
|
params := make([]string, 0)
|
|
results := make([]string, 0)
|
|
minDepth := maxDepth
|
|
if r := sig.Recv(); r != nil {
|
|
s, localMinDepth := simplifyType(r.Type(), maxDepth)
|
|
params = append(params, s)
|
|
minDepth = min(localMinDepth, minDepth)
|
|
}
|
|
if p := sig.Params(); p != nil {
|
|
for i := 0; i < p.Len(); i++ {
|
|
s, localMinDepth := simplifyType(p.At(i).Type(), maxDepth)
|
|
params = append(params, s)
|
|
minDepth = min(localMinDepth, minDepth)
|
|
}
|
|
}
|
|
if r := sig.Results(); r != nil {
|
|
for i := 0; i < r.Len(); i++ {
|
|
s, localMinDepth := simplifyType(r.At(i).Type(), maxDepth)
|
|
results = append(results, s)
|
|
minDepth = min(localMinDepth, minDepth)
|
|
}
|
|
}
|
|
return fmt.Sprintf("func (%s) (%s)", strings.Join(params, ", "), strings.Join(results, ", ")), minDepth
|
|
}
|
|
|
|
// findAllSymbols finds all package-local symbols.
|
|
func findAllSymbols(pkg *types.Package) ResolvedSymbols {
|
|
const initialMaxDepth = 4 // Don't allow infinite recursion.
|
|
localSymbols := make(map[string]string)
|
|
for _, name := range pkg.Scope().Names() {
|
|
obj := pkg.Scope().Lookup(name)
|
|
// Only include unexported, top-level functions. It is possible
|
|
// to define linknames against other types, but we avoid excessive
|
|
// excess data by not type checking these cases, which are rare.
|
|
if _, ok := obj.(*types.Func); !ok || obj.Exported() {
|
|
continue
|
|
}
|
|
localSymbols[name], _ = simplifyType(obj.Type(), initialMaxDepth)
|
|
}
|
|
return ResolvedSymbols{
|
|
pkg.Path(): localSymbols,
|
|
}
|
|
}
|
|
|
|
func run(pass *analysis.Pass) (any, error) {
|
|
// Grab all local symbols.
|
|
rs := findAllSymbols(pass.Pkg)
|
|
|
|
// Check for local //go:linkname directives in this package.
|
|
localSymbols := rs[pass.Pkg.Path()]
|
|
localPos := make(map[string]token.Pos)
|
|
ur := make(UnresolvedLinknames)
|
|
for _, f := range pass.Files {
|
|
for _, sym := range findLinknames(pass, f) {
|
|
localSig, ok := localSymbols[sym.local]
|
|
if !ok {
|
|
// The localSymbols only include unexported functions. If we don't
|
|
// match either of those, then this is a case which can be ignored.
|
|
continue
|
|
}
|
|
|
|
// Note that some of these remote packages may in fact be this
|
|
// package. We still use a single consistent pass for resolution,
|
|
// and just use the location of the declaration if it is local.
|
|
remotePackage, remoteName := splitSymbol(pass.Pkg, sym.remote)
|
|
if _, ok := ur[remotePackage]; !ok {
|
|
ur[remotePackage] = make(map[string]string)
|
|
}
|
|
ur[remotePackage][remoteName] = localSig
|
|
localPos[sym.local] = sym.pos
|
|
|
|
// Remap the local symbols, if required.
|
|
if remotePackage == pass.Pkg.Path() {
|
|
localPos[remoteName] = sym.pos
|
|
localSymbols[remoteName] = localSymbols[sym.local]
|
|
delete(localSymbols, sym.local)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build our resolution function.
|
|
resolvePos := func(pkgName, symbolName string) token.Pos {
|
|
if pkgName == pass.Pkg.Path() {
|
|
return localPos[symbolName]
|
|
}
|
|
// Scan the top-level scope for a relevant import.
|
|
for _, name := range pass.Pkg.Scope().Names() {
|
|
obj := pass.Pkg.Scope().Lookup(name)
|
|
if pn, ok := obj.(*types.PkgName); ok && pn.Pkg().Path() == pkgName {
|
|
return pn.Pos()
|
|
}
|
|
}
|
|
return 0 // No valid location.
|
|
}
|
|
|
|
// Merge in all underlying facts.
|
|
for _, importPkg := range pass.Pkg.Imports() {
|
|
var (
|
|
iur UnresolvedLinknames
|
|
irs ResolvedSymbols
|
|
)
|
|
if pass.ImportPackageFact(importPkg, &iur) {
|
|
ur.merge(pass, iur, resolvePos)
|
|
}
|
|
if pass.ImportPackageFact(importPkg, &irs) {
|
|
rs.merge(pass, irs, resolvePos)
|
|
}
|
|
}
|
|
|
|
// Attempt to resolve the facts.
|
|
ur.resolve(pass, rs, resolvePos)
|
|
if pass.Pkg.Path() == "main" {
|
|
ur.resolveRemaining(pass, resolvePos)
|
|
}
|
|
|
|
// Export all facts.
|
|
pass.ExportPackageFact(&ur)
|
|
pass.ExportPackageFact(&rs)
|
|
|
|
return nil, nil
|
|
}
|