Skip to content

Commit

Permalink
ruleguard,analyzer: add -debug-imports flag (#163)
Browse files Browse the repository at this point in the history
Allows to debug rules compile-time import issues.
  • Loading branch information
quasilyte authored Dec 27, 2020
1 parent 14d69ab commit 17ad251
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 39 deletions.
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

0 comments on commit 17ad251

Please sign in to comment.