Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ruleguard,dsl: add experimental support for const value filters #116

Merged
merged 1 commit into from
Oct 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions dsl/fluent/dsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ type Var struct {
// Const reports whether expr matched by var is a constant value.
Const bool

// Value is a compile-time computable value of the expression.
Value ExprValue

// Addressable reports whether the corresponding expression is addressable.
// See https://golang.org/ref/spec#Address_operators.
Addressable bool
Expand All @@ -81,6 +84,13 @@ type Var struct {
Text MatchedText
}

// ExprValue describes a compile-time computable value of a matched expr.
type ExprValue struct{}

// Int returns compile-time computable int value of the expression.
// If value can't be computed, condition will fail.
func (ExprValue) Int() int { return intResult }

// ExprType describes a type of a matcher expr.
type ExprType struct {
// Size represents expression type size in bytes.
Expand Down
1 change: 1 addition & 0 deletions dsl/fluent/internal.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package fluent

var boolResult bool
var intResult int

var underlyingType ExprType
2 changes: 1 addition & 1 deletion dslgen/dsl_sources.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package dslgen

var Fluent = []byte("package fluent\n\n// Matcher is a main API group-level entry point.\n// It's used to define and configure the group rules.\n// It also represents a map of all rule-local variables.\ntype Matcher map[string]Var\n\n// Import loads given package path into a rule group imports table.\n//\n// That table is used during the rules compilation.\n//\n// The table has the following effect on the rules:\n//\t* For type expressions, it's used to resolve the\n//\t full package paths of qualified types, like `foo.Bar`.\n//\t If Import(`a/b/foo`) is called, `foo.Bar` will match\n//\t `a/b/foo.Bar` type during the pattern execution.\nfunc (m Matcher) Import(pkgPath string) {}\n\n// Match specifies a set of patterns that match a rule being defined.\n// Pattern matching succeeds if at least 1 pattern matches.\n//\n// If none of the given patterns matched, rule execution stops.\nfunc (m Matcher) Match(pattern string, alternatives ...string) Matcher {\n\treturn m\n}\n\n// Where applies additional constraint to a match.\n// If a given cond is not satisfied, a match is rejected and\n// rule execution stops.\nfunc (m Matcher) Where(cond bool) Matcher {\n\treturn m\n}\n\n// Report prints a message if associated rule match is successful.\n//\n// A message is a string that can contain interpolated expressions.\n// For every matched variable it's possible to interpolate\n// their printed representation into the message text with $<name>.\n// An entire match can be addressed with $$.\nfunc (m Matcher) Report(message string) Matcher {\n\treturn m\n}\n\n// Suggest assigns a quickfix suggestion for the matched code.\nfunc (m Matcher) Suggest(suggestion string) Matcher {\n\treturn m\n}\n\n// At binds the reported node to a named submatch.\n// If no explicit location is given, the outermost node ($$) is used.\nfunc (m Matcher) At(v Var) Matcher {\n\treturn m\n}\n\n// File returns the current file context.\nfunc (m Matcher) File() File { return File{} }\n\n// Var is a pattern variable that describes a named submatch.\ntype Var struct {\n\t// Pure reports whether expr matched by var is side-effect-free.\n\tPure bool\n\n\t// Const reports whether expr matched by var is a constant value.\n\tConst bool\n\n\t// Addressable reports whether the corresponding expression is addressable.\n\t// See https://golang.org/ref/spec#Address_operators.\n\tAddressable bool\n\n\t// Type is a type of a matched expr.\n\t//\n\t// For function call expressions, a type is a function result type,\n\t// but for a function expression itself it's a *types.Signature.\n\t//\n\t// Suppose we have a `a.b()` expression:\n\t//\t`$x()` m[\"x\"].Type is `a.b` function type\n\t//\t`$x` m[\"x\"].Type is `a.b()` function call result type\n\tType ExprType\n\n\t// Text is a captured node text as in the source code.\n\tText MatchedText\n}\n\n// ExprType describes a type of a matcher expr.\ntype ExprType struct {\n\t// Size represents expression type size in bytes.\n\tSize int\n}\n\n// Underlying returns expression type underlying type.\n// See https://golang.org/pkg/go/types/#Type Underlying() method documentation.\n// Read https://golang.org/ref/spec#Types section to learn more about underlying types.\nfunc (ExprType) Underlying() ExprType { return underlyingType }\n\n// AssignableTo reports whether a type is assign-compatible with a given type.\n// See https://golang.org/pkg/go/types/#AssignableTo.\nfunc (ExprType) AssignableTo(typ string) bool { return boolResult }\n\n// ConvertibleTo reports whether a type is conversible to a given type.\n// See https://golang.org/pkg/go/types/#ConvertibleTo.\nfunc (ExprType) ConvertibleTo(typ string) bool { return boolResult }\n\n// Implements reports whether a type implements a given interface.\n// See https://golang.org/pkg/go/types/#Implements.\nfunc (ExprType) Implements(typ string) bool { return boolResult }\n\n// Is reports whether a type is identical to a given type.\nfunc (ExprType) Is(typ string) bool { return boolResult }\n\n// MatchedText represents a source text associated with a matched node.\ntype MatchedText string\n\n// Matches reports whether the text matches the given regexp pattern.\nfunc (MatchedText) Matches(pattern string) bool { return boolResult }\n\n// String represents an arbitrary string-typed data.\ntype String string\n\n// Matches reports whether a string matches the given regexp pattern.\nfunc (String) Matches(pattern string) bool { return boolResult }\n\n// File represents the current Go source file.\ntype File struct {\n\t// Name is a file base name.\n\tName String\n\n\t// PkgPath is a file package path.\n\t// Examples: \"io/ioutil\", \"strings\", \"github.com/quasilyte/go-ruleguard/dsl/fluent\".\n\tPkgPath String\n}\n\n// Imports reports whether the current file imports the given path.\nfunc (File) Imports(path string) bool { return boolResult }\n\n\n\nvar boolResult bool\n\nvar underlyingType ExprType\n\n")
var Fluent = []byte("package fluent\n\n// Matcher is a main API group-level entry point.\n// It's used to define and configure the group rules.\n// It also represents a map of all rule-local variables.\ntype Matcher map[string]Var\n\n// Import loads given package path into a rule group imports table.\n//\n// That table is used during the rules compilation.\n//\n// The table has the following effect on the rules:\n//\t* For type expressions, it's used to resolve the\n//\t full package paths of qualified types, like `foo.Bar`.\n//\t If Import(`a/b/foo`) is called, `foo.Bar` will match\n//\t `a/b/foo.Bar` type during the pattern execution.\nfunc (m Matcher) Import(pkgPath string) {}\n\n// Match specifies a set of patterns that match a rule being defined.\n// Pattern matching succeeds if at least 1 pattern matches.\n//\n// If none of the given patterns matched, rule execution stops.\nfunc (m Matcher) Match(pattern string, alternatives ...string) Matcher {\n\treturn m\n}\n\n// Where applies additional constraint to a match.\n// If a given cond is not satisfied, a match is rejected and\n// rule execution stops.\nfunc (m Matcher) Where(cond bool) Matcher {\n\treturn m\n}\n\n// Report prints a message if associated rule match is successful.\n//\n// A message is a string that can contain interpolated expressions.\n// For every matched variable it's possible to interpolate\n// their printed representation into the message text with $<name>.\n// An entire match can be addressed with $$.\nfunc (m Matcher) Report(message string) Matcher {\n\treturn m\n}\n\n// Suggest assigns a quickfix suggestion for the matched code.\nfunc (m Matcher) Suggest(suggestion string) Matcher {\n\treturn m\n}\n\n// At binds the reported node to a named submatch.\n// If no explicit location is given, the outermost node ($$) is used.\nfunc (m Matcher) At(v Var) Matcher {\n\treturn m\n}\n\n// File returns the current file context.\nfunc (m Matcher) File() File { return File{} }\n\n// Var is a pattern variable that describes a named submatch.\ntype Var struct {\n\t// Pure reports whether expr matched by var is side-effect-free.\n\tPure bool\n\n\t// Const reports whether expr matched by var is a constant value.\n\tConst bool\n\n\t// Value is a compile-time computable value of the expression.\n\tValue ExprValue\n\n\t// Addressable reports whether the corresponding expression is addressable.\n\t// See https://golang.org/ref/spec#Address_operators.\n\tAddressable bool\n\n\t// Type is a type of a matched expr.\n\t//\n\t// For function call expressions, a type is a function result type,\n\t// but for a function expression itself it's a *types.Signature.\n\t//\n\t// Suppose we have a `a.b()` expression:\n\t//\t`$x()` m[\"x\"].Type is `a.b` function type\n\t//\t`$x` m[\"x\"].Type is `a.b()` function call result type\n\tType ExprType\n\n\t// Text is a captured node text as in the source code.\n\tText MatchedText\n}\n\n// ExprValue describes a compile-time computable value of a matched expr.\ntype ExprValue struct{}\n\n// Int returns compile-time computable int value of the expression.\n// If value can't be computed, condition will fail.\nfunc (ExprValue) Int() int { return intResult }\n\n// ExprType describes a type of a matcher expr.\ntype ExprType struct {\n\t// Size represents expression type size in bytes.\n\tSize int\n}\n\n// Underlying returns expression type underlying type.\n// See https://golang.org/pkg/go/types/#Type Underlying() method documentation.\n// Read https://golang.org/ref/spec#Types section to learn more about underlying types.\nfunc (ExprType) Underlying() ExprType { return underlyingType }\n\n// AssignableTo reports whether a type is assign-compatible with a given type.\n// See https://golang.org/pkg/go/types/#AssignableTo.\nfunc (ExprType) AssignableTo(typ string) bool { return boolResult }\n\n// ConvertibleTo reports whether a type is conversible to a given type.\n// See https://golang.org/pkg/go/types/#ConvertibleTo.\nfunc (ExprType) ConvertibleTo(typ string) bool { return boolResult }\n\n// Implements reports whether a type implements a given interface.\n// See https://golang.org/pkg/go/types/#Implements.\nfunc (ExprType) Implements(typ string) bool { return boolResult }\n\n// Is reports whether a type is identical to a given type.\nfunc (ExprType) Is(typ string) bool { return boolResult }\n\n// MatchedText represents a source text associated with a matched node.\ntype MatchedText string\n\n// Matches reports whether the text matches the given regexp pattern.\nfunc (MatchedText) Matches(pattern string) bool { return boolResult }\n\n// String represents an arbitrary string-typed data.\ntype String string\n\n// Matches reports whether a string matches the given regexp pattern.\nfunc (String) Matches(pattern string) bool { return boolResult }\n\n// File represents the current Go source file.\ntype File struct {\n\t// Name is a file base name.\n\tName String\n\n\t// PkgPath is a file package path.\n\t// Examples: \"io/ioutil\", \"strings\", \"github.com/quasilyte/go-ruleguard/dsl/fluent\".\n\tPkgPath String\n}\n\n// Imports reports whether the current file imports the given path.\nfunc (File) Imports(path string) bool { return boolResult }\n\n\n\nvar boolResult bool\nvar intResult int\n\nvar underlyingType ExprType\n\n")
25 changes: 25 additions & 0 deletions ruleguard/debug_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,31 @@ func TestDebug(t *testing.T) {
` $x int: int(10)`,
},
},

`m.Match("$x + $_").Where(m["x"].Value.Int() >= 10)`: {
`sink = 20 + 1`: nil,

// OK: $x is const-folded.
`sink = (2 << 3) + 1`: nil,

// Not an int.
`sink = "20" + "x"`: {
`input.go:4: [rules.go:5] rejected by m["x"].Value.Int() >= 10`,
` $x untyped string: "20"`,
},

// Not a const value.
`sink = f().(int) + 0`: {
`input.go:4: [rules.go:5] rejected by m["x"].Value.Int() >= 10`,
` $x int: f().(int)`,
},

// Less than 10.
`sink = 4 + 1`: {
`input.go:4: [rules.go:5] rejected by m["x"].Value.Int() >= 10`,
` $x untyped int: 4`,
},
},
}

exprToRules := func(s string) *GoRuleSet {
Expand Down
14 changes: 14 additions & 0 deletions ruleguard/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,20 @@ func (p *rulesParser) walkFilter(dst *matchFilter, e ast.Expr, negate bool) erro
})
return nil
}
if operand.path == "Value.Int" && y != nil {
p.appendSubFilter(dst, e, operand.varName, func(params *nodeFilterParams) bool {
tv := params.ctx.Types.Types[params.n]
x := tv.Value
if x == nil {
return false // The value is unknown
}
if x.Kind() != constant.Int {
return false // It's not int
}
return expectedResult == constant.Compare(x, e.Op, y)
})
return nil
}
if operand.path == "Text" && y != nil {
p.appendSubFilter(dst, e, operand.varName, func(params *nodeFilterParams) bool {
s := params.nodeText(params.n)
Expand Down