Skip to content

Commit

Permalink
👷 Split AST analysis into three phases
Browse files Browse the repository at this point in the history
  • Loading branch information
ChmielewskiKamil committed Dec 10, 2024
1 parent cbc0440 commit 47ebc28
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 20 deletions.
73 changes: 65 additions & 8 deletions analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"solbot/ast"
"solbot/reporter"
"solbot/symbols"
"solbot/token"
)

type Detector interface {
Expand All @@ -18,18 +19,74 @@ func GetAllDetectors() *[]Detector {
}

func AnalyzeFile(file *ast.File) []reporter.Finding {
var findings []reporter.Finding
globalEnv := symbols.NewEnvironment()

// Phase 1: Get all declarations first to avoid unknown symbol errors if
// the symbols are defined later in a file or somewhere else.
discoverSymbols(file, globalEnv, nil)

// Phase 2: Populate all definitions and references. Resolve overrides and
// inheritance structure.
resolveDefinitions(file, globalEnv)

// Phase 3: The environment is populated with context at this point.
// Diagnose issues with the code. Run detectors.
findings := detectIssues(file, globalEnv)

return findings
}

detectors := *GetAllDetectors()
////////////////////////////////////////////////////////////////////
// PHASE 1 //
////////////////////////////////////////////////////////////////////

for _, detector := range detectors {
finding := detector.Detect(file)
if finding != nil {
findings = append(findings, *finding)
func discoverSymbols(node ast.Node,
env *symbols.Environment, src *token.SourceFile) {
switch n := node.(type) {
case *ast.File:
for _, decl := range n.Declarations {
discoverSymbols(decl, env, n.SourceFile)
}
case *ast.FunctionDeclaration:
populateFunctionDeclaration(n, env, src)
}
}

return findings
func populateFunctionDeclaration(
node *ast.FunctionDeclaration,
env *symbols.Environment,
src *token.SourceFile) {
baseSymbol := symbols.BaseSymbol{
Name: node.Name.Value,
SourceFile: src,
Offset: node.Pos,
AstNode: node,
}

fnSymbol := &symbols.FunctionDeclaration{
BaseSymbol: baseSymbol,
}

env.Set(node.Name.Value, fnSymbol)
}

func PopulateSymbols(node ast.Node, env *symbols.Environment) {}
////////////////////////////////////////////////////////////////////
// PHASE 2 //
////////////////////////////////////////////////////////////////////

func resolveDefinitions(node ast.Node, env *symbols.Environment) {}

func detectIssues(node ast.Node, env *symbols.Environment) []reporter.Finding {
var findings []reporter.Finding

// detectors := *GetAllDetectors()
//
// for _, detector := range detectors {
// finding := detector.Detect(file)
// if finding != nil {
// findings = append(findings, *finding)
// }
// }

return findings
}
56 changes: 53 additions & 3 deletions analyzer/analyzer_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,57 @@
package analyzer

import "testing"
import (
"solbot/parser"
"solbot/symbols"
"solbot/token"
"testing"
)

func Test_PopulateSymbols(t *testing.T) {
// testContractPath := "testdata/foundry/src/001_Counter.sol"
func Test_PopulateSymbols_GetSymbolsByName(t *testing.T) {
testContractPath := "testdata/foundry/src/002_SimpleCounter.sol"
p := parser.Parser{}
sourceFile, err := token.NewSourceFile(testContractPath, "")
if err != nil {
t.Fatalf("Could not create source file: %s", err)
}

p.Init(sourceFile)

file := p.ParseFile()
checkParserErrors(t, &p)

if file == nil {
t.Fatalf("ParseFile() returned nil")
}

env := symbols.NewEnvironment()
discoverSymbols(file, env, nil)

tests := []struct {
expectedSymbolName string
}{
{"increment"},
{"decrement"},
{"reset"},
}

for _, tt := range tests {
_, found := env.Get(tt.expectedSymbolName)
if !found {
t.Fatalf("Symbol: '%s' not found.", tt.expectedSymbolName)
}
}
}

func checkParserErrors(t *testing.T, p *parser.Parser) {
errors := p.Errors()
if len(errors) == 0 {
return
}

t.Errorf("Parser has %d errors", len(errors))
for _, err := range errors {
t.Errorf("Parser error: %s", err.Msg)
}
t.FailNow()
}
1 change: 1 addition & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ func (d *FunctionDeclaration) String() string {
// file.
type File struct {
Name string
SourceFile *token.SourceFile
Declarations []Declaration
}

Expand Down
1 change: 1 addition & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ func (p *Parser) ParseFile() *ast.File {
}

file := &ast.File{}
file.SourceFile = p.file
file.Declarations = []ast.Declaration{}

for p.currTkn.Type != token.EOF {
Expand Down
24 changes: 16 additions & 8 deletions symbols/environment.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package symbols

type Environment struct {
store map[string]Symbol
store map[string][]Symbol
outer *Environment
}

func NewEnvironment() *Environment {
s := make(map[string]Symbol)
s := make(map[string][]Symbol)
return &Environment{store: s, outer: nil}
}

Expand All @@ -17,13 +17,21 @@ func NewEnclosedEnvironment(outer *Environment) *Environment {
}

func (env *Environment) Set(ident string, symbol Symbol) {
env.store[ident] = symbol
env.store[ident] = append(env.store[ident], symbol)
}

func (env *Environment) Get(ident string) (Symbol, bool) {
symbol, ok := env.store[ident]
if !ok && env.outer != nil {
symbol, ok = env.outer.Get(ident)
func (env *Environment) Get(ident string) ([]Symbol, bool) {
// check current env
if symbols, ok := env.store[ident]; ok {
return symbols, true
}
return symbol, ok

// check outer scope
if env.outer != nil {
if symbols, ok := env.outer.Get(ident); ok {
return symbols, true
}
}

return nil, false
}
15 changes: 14 additions & 1 deletion symbols/symbols.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type BaseSymbol struct {
SourceFile *token.SourceFile // Pointer to the source file were symbol was declared.
Offset token.Pos // Offset to the symbol name.
References []Reference // Places where the symbol was used.
AstNode *ast.Node // Pointer to ast node.
AstNode ast.Node // Pointer to ast node.
}

func (bs *BaseSymbol) Location() string {
Expand All @@ -33,6 +33,8 @@ func (bs *BaseSymbol) Location() string {
return fmt.Sprintf("Missing location of symbol: %s. No source file info.", bs.Name)
}

// References are resolved in the second phase of the analysis. They can be
// analyzed to undersand where a symbol is used and how.
type Reference struct {
SourceFile *token.SourceFile // Pointer to the source file were symbol reference was found.
Offset token.Pos // Offset to the place where symbol was referenced in the source file.
Expand All @@ -46,6 +48,17 @@ type Contract struct {

type FunctionDeclaration struct {
BaseSymbol
Parameters []*Param
Results []*Param
Visibility ast.Visibility
Mutability ast.Mutability
Virtual bool
}

type Param struct {
BaseSymbol
// Type
DataLocation ast.DataLocation
}

type ReferenceUsageType int
Expand Down

0 comments on commit 47ebc28

Please sign in to comment.