blob: e4988b1c625b0f4f7610e09dedb75de1c8cbb70c [file] [log] [blame]
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"go/ast"
"go/token"
"reflect"
"strings"
)
func init() {
register(cftypeFix)
}
var cftypeFix = fix{
name: "cftype",
date: "2017-09-27",
f: cftypefix,
desc: `Fixes initializers and casts of C.*Ref and JNI types`,
disabled: false,
}
// Old state:
//
// type CFTypeRef unsafe.Pointer
//
// New state:
//
// type CFTypeRef uintptr
//
// and similar for other *Ref types.
// This fix finds nils initializing these types and replaces the nils with 0s.
func cftypefix(f *ast.File) bool {
return typefix(f, func(s string) bool {
return strings.HasPrefix(s, "C.") && strings.HasSuffix(s, "Ref") && s != "C.CFAllocatorRef"
})
}
// typefix replaces nil with 0 for all nils whose type, when passed to badType, returns true.
func typefix(f *ast.File, badType func(string) bool) bool {
if !imports(f, "C") {
return false
}
typeof, _ := typecheck(&TypeConfig{}, f)
changed := false
// step 1: Find all the nils with the offending types.
// Compute their replacement.
badNils := map[any]ast.Expr{}
walk(f, func(n any) {
if i, ok := n.(*ast.Ident); ok && i.Name == "nil" && badType(typeof[n]) {
badNils[n] = &ast.BasicLit{ValuePos: i.NamePos, Kind: token.INT, Value: "0"}
}
})
// step 2: find all uses of the bad nils, replace them with 0.
// There's no easy way to map from an ast.Expr to all the places that use them, so
// we use reflect to find all such references.
if len(badNils) > 0 {
exprType := reflect.TypeOf((*ast.Expr)(nil)).Elem()
exprSliceType := reflect.TypeOf(([]ast.Expr)(nil))
walk(f, func(n any) {
if n == nil {
return
}
v := reflect.ValueOf(n)
if v.Type().Kind() != reflect.Pointer {
return
}
if v.IsNil() {
return
}
v = v.Elem()
if v.Type().Kind() != reflect.Struct {
return
}
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
if f.Type() == exprType {
if r := badNils[f.Interface()]; r != nil {
f.Set(reflect.ValueOf(r))
changed = true
}
}
if f.Type() == exprSliceType {
for j := 0; j < f.Len(); j++ {
e := f.Index(j)
if r := badNils[e.Interface()]; r != nil {
e.Set(reflect.ValueOf(r))
changed = true
}
}
}
}
})
}
// step 3: fix up invalid casts.
// It used to be ok to cast between *unsafe.Pointer and *C.CFTypeRef in a single step.
// Now we need unsafe.Pointer as an intermediate cast.
// (*unsafe.Pointer)(x) where x is type *bad -> (*unsafe.Pointer)(unsafe.Pointer(x))
// (*bad.type)(x) where x is type *unsafe.Pointer -> (*bad.type)(unsafe.Pointer(x))
walk(f, func(n any) {
if n == nil {
return
}
// Find pattern like (*a.b)(x)
c, ok := n.(*ast.CallExpr)
if !ok {
return
}
if len(c.Args) != 1 {
return
}
p, ok := c.Fun.(*ast.ParenExpr)
if !ok {
return
}
s, ok := p.X.(*ast.StarExpr)
if !ok {
return
}
t, ok := s.X.(*ast.SelectorExpr)
if !ok {
return
}
pkg, ok := t.X.(*ast.Ident)
if !ok {
return
}
dst := pkg.Name + "." + t.Sel.Name
src := typeof[c.Args[0]]
if badType(dst) && src == "*unsafe.Pointer" ||
dst == "unsafe.Pointer" && strings.HasPrefix(src, "*") && badType(src[1:]) {
c.Args[0] = &ast.CallExpr{
Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "unsafe"}, Sel: &ast.Ident{Name: "Pointer"}},
Args: []ast.Expr{c.Args[0]},
}
changed = true
}
})
return changed
}