woodpecker/vendor/github.com/quasilyte/go-ruleguard/ruleguard/utils.go

252 lines
6 KiB
Go
Raw Normal View History

package ruleguard
import (
"go/ast"
"go/constant"
"go/parser"
"go/token"
"go/types"
"regexp/syntax"
"strconv"
"strings"
)
func regexpHasCaptureGroups(pattern string) bool {
// regexp.Compile() uses syntax.Perl flags, so
// we use the same flags here.
re, err := syntax.Parse(pattern, syntax.Perl)
if err != nil {
return true // true is more conservative than false
}
found := false
var walkRegexp func(*syntax.Regexp)
walkRegexp = func(re *syntax.Regexp) {
if found {
return
}
// OpCapture handles both named and unnamed capture groups.
if re.Op == syntax.OpCapture {
found = true
return
}
for _, sub := range re.Sub {
walkRegexp(sub)
}
}
walkRegexp(re)
return found
}
func findDependency(pkg *types.Package, path string) *types.Package {
if pkg.Path() == path {
return pkg
}
// It looks like indirect dependencies are always incomplete?
// If it's true, then we don't have to recurse here.
for _, imported := range pkg.Imports() {
if dep := findDependency(imported, path); dep != nil && dep.Complete() {
return dep
}
}
return nil
}
var typeByName = map[string]types.Type{
// Predeclared types.
`error`: types.Universe.Lookup("error").Type(),
`bool`: types.Typ[types.Bool],
`int`: types.Typ[types.Int],
`int8`: types.Typ[types.Int8],
`int16`: types.Typ[types.Int16],
`int32`: types.Typ[types.Int32],
`int64`: types.Typ[types.Int64],
`uint`: types.Typ[types.Uint],
`uint8`: types.Typ[types.Uint8],
`uint16`: types.Typ[types.Uint16],
`uint32`: types.Typ[types.Uint32],
`uint64`: types.Typ[types.Uint64],
`uintptr`: types.Typ[types.Uintptr],
`string`: types.Typ[types.String],
`float32`: types.Typ[types.Float32],
`float64`: types.Typ[types.Float64],
`complex64`: types.Typ[types.Complex64],
`complex128`: types.Typ[types.Complex128],
// Predeclared aliases (provided for convenience).
`byte`: types.Typ[types.Uint8],
`rune`: types.Typ[types.Int32],
}
func typeFromString(s string) (types.Type, error) {
s = strings.ReplaceAll(s, "?", "__any")
n, err := parser.ParseExpr(s)
if err != nil {
return nil, err
}
return typeFromNode(n), nil
}
func typeFromNode(e ast.Expr) types.Type {
switch e := e.(type) {
case *ast.Ident:
typ, ok := typeByName[e.Name]
if ok {
return typ
}
case *ast.ArrayType:
elem := typeFromNode(e.Elt)
if elem == nil {
return nil
}
if e.Len == nil {
return types.NewSlice(elem)
}
lit, ok := e.Len.(*ast.BasicLit)
if !ok || lit.Kind != token.INT {
return nil
}
length, err := strconv.Atoi(lit.Value)
if err != nil {
return nil
}
return types.NewArray(elem, int64(length))
case *ast.MapType:
keyType := typeFromNode(e.Key)
if keyType == nil {
return nil
}
valType := typeFromNode(e.Value)
if valType == nil {
return nil
}
return types.NewMap(keyType, valType)
case *ast.StarExpr:
typ := typeFromNode(e.X)
if typ != nil {
return types.NewPointer(typ)
}
case *ast.ParenExpr:
return typeFromNode(e.X)
case *ast.InterfaceType:
if len(e.Methods.List) == 0 {
return types.NewInterfaceType(nil, nil)
}
}
return nil
}
func intValueOf(info *types.Info, expr ast.Expr) constant.Value {
tv := info.Types[expr]
if tv.Value == nil {
return nil
}
if tv.Value.Kind() != constant.Int {
return nil
}
return tv.Value
}
// isPure reports whether expr is a softly safe expression and contains
// no significant side-effects. As opposed to strictly safe expressions,
// soft safe expressions permit some forms of side-effects, like
// panic possibility during indexing or nil pointer dereference.
//
// Uses types info to determine type conversion expressions that
// are the only permitted kinds of call expressions.
// Note that is does not check whether called function really
// has any side effects. The analysis is very conservative.
func isPure(info *types.Info, expr ast.Expr) bool {
// This list switch is not comprehensive and uses
// whitelist to be on the conservative side.
// Can be extended as needed.
switch expr := expr.(type) {
case *ast.StarExpr:
return isPure(info, expr.X)
case *ast.BinaryExpr:
return isPure(info, expr.X) &&
isPure(info, expr.Y)
case *ast.UnaryExpr:
return expr.Op != token.ARROW &&
isPure(info, expr.X)
case *ast.BasicLit, *ast.Ident:
return true
case *ast.IndexExpr:
return isPure(info, expr.X) &&
isPure(info, expr.Index)
case *ast.SelectorExpr:
return isPure(info, expr.X)
case *ast.ParenExpr:
return isPure(info, expr.X)
case *ast.CompositeLit:
return isPureList(info, expr.Elts)
case *ast.CallExpr:
return isTypeExpr(info, expr.Fun) && isPureList(info, expr.Args)
default:
return false
}
}
// isPureList reports whether every expr in list is safe.
//
// See isPure.
func isPureList(info *types.Info, list []ast.Expr) bool {
for _, expr := range list {
if !isPure(info, expr) {
return false
}
}
return true
}
func isAddressable(info *types.Info, expr ast.Expr) bool {
tv, ok := info.Types[expr]
return ok && tv.Addressable()
}
func isConstant(info *types.Info, expr ast.Expr) bool {
tv, ok := info.Types[expr]
return ok && tv.Value != nil
}
// isTypeExpr reports whether x represents a type expression.
//
// Type expression does not evaluate to any run time value,
// but rather describes a type that is used inside Go expression.
//
// For example, (*T)(v) is a CallExpr that "calls" (*T).
// (*T) is a type expression that tells Go compiler type v should be converted to.
func isTypeExpr(info *types.Info, x ast.Expr) bool {
switch x := x.(type) {
case *ast.StarExpr:
return isTypeExpr(info, x.X)
case *ast.ParenExpr:
return isTypeExpr(info, x.X)
case *ast.SelectorExpr:
return isTypeExpr(info, x.Sel)
case *ast.Ident:
// Identifier may be a type expression if object
// it reffers to is a type name.
_, ok := info.ObjectOf(x).(*types.TypeName)
return ok
case *ast.FuncType, *ast.StructType, *ast.InterfaceType, *ast.ArrayType, *ast.MapType, *ast.ChanType:
return true
default:
return false
}
}