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,analyzer: add -debug-imports flag #163

Merged
merged 1 commit into from
Dec 27, 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
36 changes: 23 additions & 13 deletions analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,28 @@ var Analyzer = &analysis.Analyzer{
}

var (
flagRules string
flagE string
flagDebug string
flagRules string
flagE string
flagDebug string
flagDebugImports bool
)

func init() {
Analyzer.Flags.StringVar(&flagRules, "rules", "", "comma-separated list of gorule file paths")
Analyzer.Flags.StringVar(&flagE, "e", "", "execute a single rule from a given string")
Analyzer.Flags.StringVar(&flagDebug, "debug-group", "", "enable debug for the specified function")
Analyzer.Flags.BoolVar(&flagDebugImports, "debug-imports", false, "enable debug for rules compile-time package lookups")
}

type parseRulesResult struct {
rset *ruleguard.GoRuleSet
multiFile bool
}

func debugPrint(s string) {
fmt.Fprintln(os.Stderr, s)
}

func runAnalyzer(pass *analysis.Pass) (interface{}, error) {
// TODO(quasilyte): parse config under sync.Once and
// create rule sets from it.
Expand All @@ -50,14 +56,12 @@ func runAnalyzer(pass *analysis.Pass) (interface{}, error) {
multiFile := parseResult.multiFile

ctx := &ruleguard.Context{
Debug: flagDebug,
DebugPrint: func(s string) {
fmt.Fprintln(os.Stderr, s)
},
Pkg: pass.Pkg,
Types: pass.TypesInfo,
Sizes: pass.TypesSizes,
Fset: pass.Fset,
Debug: flagDebug,
DebugPrint: debugPrint,
Pkg: pass.Pkg,
Types: pass.TypesInfo,
Sizes: pass.TypesSizes,
Fset: pass.Fset,
Report: func(info ruleguard.GoRuleInfo, n ast.Node, msg string, s *ruleguard.Suggestion) {
msg = info.Group + ": " + msg
if multiFile {
Expand Down Expand Up @@ -97,6 +101,12 @@ func runAnalyzer(pass *analysis.Pass) (interface{}, error) {
func readRules() (*parseRulesResult, error) {
fset := token.NewFileSet()

ctx := &ruleguard.ParseContext{
Fset: fset,
DebugImports: flagDebugImports,
DebugPrint: debugPrint,
}

switch {
case flagRules != "":
filenames := strings.Split(flagRules, ",")
Expand All @@ -108,7 +118,7 @@ func readRules() (*parseRulesResult, error) {
if err != nil {
return nil, fmt.Errorf("read rules file: %v", err)
}
rset, err := ruleguard.ParseRules(filename, fset, bytes.NewReader(data))
rset, err := ruleguard.ParseRules(ctx, filename, bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("parse rules file: %v", err)
}
Expand All @@ -132,7 +142,7 @@ func readRules() (*parseRulesResult, error) {
}`,
flagE)
r := strings.NewReader(ruleText)
rset, err := ruleguard.ParseRules(flagRules, fset, r)
rset, err := ruleguard.ParseRules(ctx, flagRules, r)
return &parseRulesResult{rset: rset}, err

default:
Expand Down
4 changes: 2 additions & 2 deletions ruleguard/debug_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ func TestDebug(t *testing.T) {
%s.Report("$$")
}`,
s)
fset := token.NewFileSet()
rset, err := ParseRules("rules.go", fset, strings.NewReader(file))
ctx := &ParseContext{Fset: token.NewFileSet()}
rset, err := ParseRules(ctx, "rules.go", strings.NewReader(file))
if err != nil {
t.Fatalf("parse %s: %v", s, err)
}
Expand Down
35 changes: 28 additions & 7 deletions ruleguard/importer.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package ruleguard

import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"path/filepath"
"runtime"

"github.com/quasilyte/go-ruleguard/internal/golist"
)
Expand All @@ -16,6 +18,8 @@ import (
type goImporter struct {
// TODO(quasilyte): share importers with gogrep?

ctx *ParseContext

// cache contains all imported packages, from any importer.
// Both default and source importers have their own caches,
// but since we use several importers, it's better to
Expand All @@ -28,42 +32,59 @@ type goImporter struct {
srcImporter types.Importer
}

func newGoImporter(fset *token.FileSet) *goImporter {
func newGoImporter(ctx *ParseContext) *goImporter {
return &goImporter{
ctx: ctx,
cache: make(map[string]*types.Package),
fset: fset,
defaultImporter: importer.Default(),
srcImporter: importer.ForCompiler(fset, "source", nil),
srcImporter: importer.ForCompiler(ctx.Fset, "source", nil),
}
}

func (imp *goImporter) Import(path string) (*types.Package, error) {
if pkg := imp.cache[path]; pkg != nil {
if imp.ctx.DebugImports {
imp.ctx.DebugPrint(fmt.Sprintf(`imported "%s" from importer cache`, path))
}
return pkg, nil
}

pkg, err1 := imp.defaultImporter.Import(path)
pkg, err1 := imp.srcImporter.Import(path)
if err1 == nil {
imp.cache[path] = pkg
if imp.ctx.DebugImports {
imp.ctx.DebugPrint(fmt.Sprintf(`imported "%s" from source importer`, path))
}
return pkg, nil
}

pkg, err2 := imp.srcImporter.Import(path)
pkg, err2 := imp.defaultImporter.Import(path)
if err2 == nil {
imp.cache[path] = pkg
if imp.ctx.DebugImports {
imp.ctx.DebugPrint(fmt.Sprintf(`imported "%s" from %s importer`, path, runtime.Compiler))
}
return pkg, nil
}

// Fallback to `go list` as a last resort.
pkg, err3 := imp.golistImport(path)
if err3 == nil {
imp.cache[path] = pkg
if imp.ctx.DebugImports {
imp.ctx.DebugPrint(fmt.Sprintf(`imported "%s" from golist importer`, path))
}
return pkg, nil
}

// TODO: report all err1, err2 and err3 in debug mode.
if imp.ctx.DebugImports {
imp.ctx.DebugPrint(fmt.Sprintf(`failed to import "%s":`, path))
imp.ctx.DebugPrint(fmt.Sprintf(" source importer: %v", err1))
imp.ctx.DebugPrint(fmt.Sprintf(" %s importer: %v", runtime.Compiler, err2))
imp.ctx.DebugPrint(fmt.Sprintf(" golist importer: %v", err3))
}

return nil, err1
return nil, err2
}

func (imp *goImporter) golistImport(path string) (*types.Package, error) {
Expand Down
26 changes: 16 additions & 10 deletions ruleguard/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type parseError string
func (e parseError) Error() string { return string(e) }

type rulesParser struct {
ctx *ParseContext

prefix string // For imported packages, a prefix that is added to a rule group name
importedPkg string // Package path; only for imported packages

Expand All @@ -40,26 +42,29 @@ type rulesParser struct {
}

type rulesParserConfig struct {
ctx *ParseContext

prefix string
importedPkg string

itab *typematch.ImportsTab
importer *goImporter
}

func newRulesParser(cfg rulesParserConfig) *rulesParser {
func newRulesParser(config rulesParserConfig) *rulesParser {
return &rulesParser{
prefix: cfg.prefix,
importedPkg: cfg.importedPkg,
itab: cfg.itab,
importer: cfg.importer,
ctx: config.ctx,
prefix: config.prefix,
importedPkg: config.importedPkg,
itab: config.itab,
importer: config.importer,
}
}

func (p *rulesParser) ParseFile(filename string, fset *token.FileSet, r io.Reader) (*GoRuleSet, error) {
func (p *rulesParser) ParseFile(filename string, r io.Reader) (*GoRuleSet, error) {
p.dslPkgname = "dsl"
p.filename = filename
p.fset = fset
p.fset = p.ctx.Fset
p.res = &GoRuleSet{
local: &scopedGoRuleSet{},
universal: &scopedGoRuleSet{},
Expand All @@ -68,7 +73,7 @@ func (p *rulesParser) ParseFile(filename string, fset *token.FileSet, r io.Reade
}

parserFlags := parser.Mode(0)
f, err := parser.ParseFile(fset, filename, r, parserFlags)
f, err := parser.ParseFile(p.ctx.Fset, filename, r, parserFlags)
if err != nil {
return nil, fmt.Errorf("parse file error: %v", err)
}
Expand All @@ -94,7 +99,7 @@ func (p *rulesParser) ParseFile(filename string, fset *token.FileSet, r io.Reade
Types: map[ast.Expr]types.TypeAndValue{},
Uses: map[*ast.Ident]types.Object{},
}
_, err = typechecker.Check("gorules", fset, []*ast.File{f}, p.types)
_, err = typechecker.Check("gorules", p.ctx.Fset, []*ast.File{f}, p.types)
if err != nil {
return nil, fmt.Errorf("typechecker error: %v", err)
}
Expand Down Expand Up @@ -201,12 +206,13 @@ func (p *rulesParser) importRules(prefix, pkgPath, filename string) (*GoRuleSet,
return nil, err
}
config := rulesParserConfig{
ctx: p.ctx,
prefix: prefix,
importedPkg: pkgPath,
itab: p.itab,
importer: p.importer,
}
rset, err := newRulesParser(config).ParseFile(filename, p.fset, bytes.NewReader(data))
rset, err := newRulesParser(config).ParseFile(filename, bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("%s: %v", p.importedPkg, err)
}
Expand Down
14 changes: 11 additions & 3 deletions ruleguard/ruleguard.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import (
"github.com/quasilyte/go-ruleguard/ruleguard/typematch"
)

type ParseContext struct {
DebugImports bool
DebugPrint func(string)

Fset *token.FileSet
}

type Context struct {
Debug string
DebugPrint func(string)
Expand All @@ -26,13 +33,14 @@ type Suggestion struct {
Replacement []byte
}

func ParseRules(filename string, fset *token.FileSet, r io.Reader) (*GoRuleSet, error) {
func ParseRules(ctx *ParseContext, filename string, r io.Reader) (*GoRuleSet, error) {
config := rulesParserConfig{
ctx: ctx,
itab: typematch.NewImportsTab(stdlibPackages),
importer: newGoImporter(fset),
importer: newGoImporter(ctx),
}
p := newRulesParser(config)
return p.ParseFile(filename, fset, r)
return p.ParseFile(filename, r)
}

func RunRules(ctx *Context, f *ast.File, rules *GoRuleSet) error {
Expand Down
8 changes: 4 additions & 4 deletions ruleguard/ruleguard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ func TestParseRuleError(t *testing.T) {
%s
}`,
test.expr)
fset := token.NewFileSet()
_, err := ParseRules("rules.go", fset, strings.NewReader(file))
ctx := &ParseContext{Fset: token.NewFileSet()}
_, err := ParseRules(ctx, "rules.go", strings.NewReader(file))
if err == nil {
t.Errorf("parse %s: expected %s error, got none", test.expr, test.err)
continue
Expand Down Expand Up @@ -138,8 +138,8 @@ func TestParseFilterError(t *testing.T) {
m.Match("$x + $y[$key]").Where(%s).Report("$$")
}`,
test.expr)
fset := token.NewFileSet()
_, err := ParseRules("rules.go", fset, strings.NewReader(file))
ctx := &ParseContext{Fset: token.NewFileSet()}
_, err := ParseRules(ctx, "rules.go", strings.NewReader(file))
if err == nil {
t.Errorf("parse %s: expected %s error, got none", test.expr, test.err)
continue
Expand Down