mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-11 19:16:32 +00:00
252 lines
6 KiB
Go
252 lines
6 KiB
Go
|
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
|
||
|
}
|
||
|
}
|