Skip to content

Commit dd404c2

Browse files
committed
feat: analysis-function recognition + integration with yaml-checkers working
1 parent 1507b06 commit dd404c2

12 files changed

Lines changed: 394 additions & 217 deletions

analysis/directory.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@ package analysis
33
import "fmt"
44

55
type FunctionDirectory struct {
6-
Pool map[string]*AnalysisFunction
6+
// Different languages can have the same type of Analysis function.
7+
// This first maps the Type of function-mode, then the specific language implementation
8+
Pool map[string]map[Language]*AnalysisFunction
79
}
810

911
var AnalysisFuncDirectory = &FunctionDirectory{
10-
Pool: make(map[string]*AnalysisFunction),
12+
Pool: make(map[string]map[Language]*AnalysisFunction),
1113
}
1214

1315
func (fd *FunctionDirectory) AddToDirectory(ana *Analyzer) error {
1416
anaFunc := fd.Pool[ana.Name]
1517
if anaFunc == nil {
1618
return fmt.Errorf("%s method is not supported", ana.Name)
1719
}
18-
anaFunc.Analyzer = ana
20+
anaFunc[ana.Language].Analyzer = ana
1921
return nil
2022
}

analysis/directory_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,7 @@ description: "Runs a taint analysis on the provided function and its parameters.
3232
_, _, err := ReadFromBytes([]byte(checker))
3333
assert.NoError(t, err)
3434
assert.NotNil(t, AnalysisFuncDirectory.Pool["taint"])
35+
assert.NotNil(t, AnalysisFuncDirectory.Pool["taint"][LangJs])
36+
fun := AnalysisFuncDirectory.Pool["taint"][LangJs]
37+
assert.Equal(t, fun.Name, "taint")
3538
}

analysis/testrunner.go

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -285,17 +285,25 @@ type YamlTestCase struct {
285285
TestFile string
286286
}
287287

288-
func RunYamlTests(testDir string) (passed bool, err error) {
288+
type YamlIssues struct {
289+
YamlAnalyzer YamlAnalyzer
290+
Got []int
291+
Want []int
292+
}
293+
294+
var IssuesYaml = make(map[YamlTestCase]*YamlIssues)
295+
296+
func RunYamlTests(testDir string) (issues map[YamlTestCase]*YamlIssues, err error) {
289297
tests, err := FindYamlTestFiles(testDir)
290298
if err != nil {
291-
return false, err
299+
return nil, err
292300
}
293301

294302
if len(tests) == 0 {
295-
return false, fmt.Errorf("no test files found")
303+
return nil, fmt.Errorf("no test files found")
296304
}
297305

298-
passed = true
306+
// passed = true
299307
for _, test := range tests {
300308
if test.TestFile == "" {
301309
fmt.Fprintf(os.Stderr, "No test file found for checker '%s'\n", test.YamlCheckerPath)
@@ -304,19 +312,19 @@ func RunYamlTests(testDir string) (passed bool, err error) {
304312

305313
fmt.Fprintf(os.Stderr, "Running test case: %s\n", filepath.Base(test.YamlCheckerPath))
306314

307-
checker, _, err := ReadFromFile(test.YamlCheckerPath)
315+
checker, yamlChecker, err := ReadFromFile(test.YamlCheckerPath)
308316
if err != nil {
309-
return false, err
317+
return nil, err
310318
}
311319

312320
want, err := findExpectedLines(test.TestFile)
313321
if err != nil {
314-
return false, err
322+
return nil, err
315323
}
316324

317325
gotIssues, err := RunAnalyzers(test.TestFile, []*Analyzer{&checker}, nil)
318326
if err != nil {
319-
return false, err
327+
return nil, err
320328
}
321329

322330
var got []int
@@ -326,35 +334,46 @@ func RunYamlTests(testDir string) (passed bool, err error) {
326334

327335
slices.Sort(got)
328336

329-
if len(want) != len(got) {
330-
testName := filepath.Base(test.YamlCheckerPath)
331-
message := fmt.Sprintf(
332-
"(%s): expected issues on the following lines: %v\nbut issues were raised on lines: %v\n",
333-
testName,
334-
want,
335-
got,
336-
)
337-
fmt.Fprintf(os.Stderr, "%s", message)
338-
passed = false
339-
continue
340-
}
341-
for j := 0; j < len(want); j++ {
342-
if want[j] != got[j] {
343-
testName := filepath.Base(test.YamlCheckerPath)
344-
message := fmt.Sprintf(
345-
"(%s): expected issue on line %d, but next occurrence is on line %d\n",
346-
testName,
347-
want[j],
348-
got[j],
349-
)
350-
fmt.Fprintf(os.Stderr, "%s\n", message)
351-
passed = false
337+
if IssuesYaml[test] == nil {
338+
IssuesYaml[test] = &YamlIssues{
339+
Want: want,
340+
Got: got,
341+
YamlAnalyzer: yamlChecker,
352342
}
353-
343+
} else {
344+
IssuesYaml[test].Want = append(IssuesYaml[test].Want, want...)
345+
IssuesYaml[test].Got = append(IssuesYaml[test].Got, got...)
346+
IssuesYaml[test].YamlAnalyzer = yamlChecker
354347
}
348+
// if len(want) != len(got) {
349+
// testName := filepath.Base(test.YamlCheckerPath)
350+
// message := fmt.Sprintf(
351+
// "(%s): expected issues on the following lines: %v\nbut issues were raised on lines: %v\n",
352+
// testName,
353+
// want,
354+
// got,
355+
// )
356+
// fmt.Fprintf(os.Stderr, "%s", message)
357+
// passed = false
358+
// continue
359+
// }
360+
// for j := 0; j < len(want); j++ {
361+
// if want[j] != got[j] {
362+
// testName := filepath.Base(test.YamlCheckerPath)
363+
// message := fmt.Sprintf(
364+
// "(%s): expected issue on line %d, but next occurrence is on line %d\n",
365+
// testName,
366+
// want[j],
367+
// got[j],
368+
// )
369+
// fmt.Fprintf(os.Stderr, "%s\n", message)
370+
// passed = false
371+
// }
372+
373+
// }
355374
}
356375

357-
return passed, nil
376+
return IssuesYaml, nil
358377
}
359378

360379
func FindYamlTestFiles(testDir string) ([]YamlTestCase, error) {

analysis/testrunner_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,16 +204,16 @@ func TestFindYamlTestFiles(t *testing.T) {
204204

205205
func TestRunYamlTestsPass(t *testing.T) {
206206
testDir := "testdata/yaml_tests/pass"
207-
passed, err := RunYamlTests(testDir)
207+
_, err := RunYamlTests(testDir)
208208
assert.NoError(t, err)
209-
assert.True(t, passed)
209+
// assert.True(t, passed)
210210
}
211211

212212
func TestRunYamlTestsFail(t *testing.T) {
213213
testDir := "testdata/yaml_tests/fail"
214-
passed, err := RunYamlTests(testDir)
214+
_, err := RunYamlTests(testDir)
215215
assert.NoError(t, err)
216-
assert.False(t, passed)
216+
// assert.False(t, passed)
217217
}
218218

219219
// Helper function to compare maps

analysis/yaml.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,12 @@ type Yaml struct {
7070
}
7171

7272
type YamlAnalyzer struct {
73-
Analyzer *Analyzer
74-
Patterns []*sitter.Query
75-
NodeFilter []NodeFilter
76-
PathFilter *PathFilter
77-
Message string
73+
Analyzer *Analyzer
74+
Patterns []*sitter.Query
75+
NodeFilter []NodeFilter
76+
PathFilter *PathFilter
77+
Message string
78+
AnalysisFunction *AnalysisFunction
7879
}
7980

8081
// ReadFromFile reads a pattern checker definition from a YAML config file.
@@ -101,7 +102,11 @@ func ReadFromBytes(fileContent []byte) (Analyzer, YamlAnalyzer, error) {
101102

102103
if checker.AnalysisFunction != nil {
103104
name := checker.AnalysisFunction.Name
104-
AnalysisFuncDirectory.Pool[name] = checker.AnalysisFunction
105+
lang := DecodeLanguage(checker.Language)
106+
if AnalysisFuncDirectory.Pool[name] == nil {
107+
AnalysisFuncDirectory.Pool[name] = make(map[Language]*AnalysisFunction)
108+
}
109+
AnalysisFuncDirectory.Pool[name][lang] = checker.AnalysisFunction
105110
}
106111

107112
var patterns []*sitter.Query
@@ -193,11 +198,12 @@ func ReadFromBytes(fileContent []byte) (Analyzer, YamlAnalyzer, error) {
193198
}
194199

195200
yamlAnalyzer := &YamlAnalyzer{
196-
Analyzer: &patternChecker,
197-
Patterns: patterns,
198-
NodeFilter: filters,
199-
PathFilter: pathFilter,
200-
Message: message,
201+
Analyzer: &patternChecker,
202+
Patterns: patterns,
203+
NodeFilter: filters,
204+
PathFilter: pathFilter,
205+
Message: message,
206+
AnalysisFunction: checker.AnalysisFunction,
201207
}
202208

203209
patternChecker.Run = RunYamlAnalyzer(yamlAnalyzer)

checkers/checker.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,62 @@ func RunAnalyzerTests(analyzerRegistry []Analyzer) (bool, []error) {
100100

101101
return passed, errors
102102
}
103+
104+
func RunYamlAnalyzers(dir string) (passed bool, err error) {
105+
issues, err := analysis.RunYamlTests(dir)
106+
if err != nil {
107+
return false, fmt.Errorf("error running yaml tests: %w", err)
108+
}
109+
110+
passed = true
111+
112+
for test, yaml := range issues {
113+
114+
if yaml.YamlAnalyzer.AnalysisFunction != nil {
115+
name := yaml.YamlAnalyzer.AnalysisFunction.Name
116+
lang := yaml.YamlAnalyzer.Analyzer.Language
117+
InitializeAnalysisFunctionDirectory(name, lang)
118+
analysisFuncAnalyzer := yaml.YamlAnalyzer.AnalysisFunction.Analyzer
119+
if analysisFuncAnalyzer == nil {
120+
return false, fmt.Errorf("no analysis function found for %s in %v", name, lang)
121+
}
122+
funcIssues, err := analysis.RunAnalyzers(test.TestFile, []*analysis.Analyzer{analysisFuncAnalyzer}, nil)
123+
if err != nil {
124+
return false, fmt.Errorf("error running analysis function for %s: %w", name, err)
125+
}
126+
for _, issue := range funcIssues {
127+
yaml.Got = append(yaml.Got, int(issue.Node.Range().StartPoint.Row)+1)
128+
}
129+
}
130+
131+
if len(yaml.Want) != len(yaml.Got) {
132+
fmt.Println("Hmm... the number of issues raised is not as expected.")
133+
testName := filepath.Base(test.YamlCheckerPath)
134+
message := fmt.Sprintf(
135+
"(%s): expected issues on the following lines: %v\nbut issues were raised on lines: %v\n",
136+
testName,
137+
yaml.Want,
138+
yaml.Got,
139+
)
140+
fmt.Fprintf(os.Stderr, "%s", message)
141+
passed = false
142+
continue
143+
}
144+
for j := 0; j < len(yaml.Want); j++ {
145+
if yaml.Want[j] != yaml.Got[j] {
146+
testName := filepath.Base(test.YamlCheckerPath)
147+
message := fmt.Sprintf(
148+
"(%s): expected issue on line %d, but next occurrence is on line %d\n",
149+
testName,
150+
yaml.Want[j],
151+
yaml.Got[j],
152+
)
153+
fmt.Fprintf(os.Stderr, "%s\n", message)
154+
passed = false
155+
}
156+
157+
}
158+
}
159+
160+
return passed, nil
161+
}

checkers/checker_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package checkers
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestYamlAnalyzers(t *testing.T) {
10+
path := "./testdata"
11+
12+
passed, err := RunYamlAnalyzers(path)
13+
assert.NoError(t, err)
14+
assert.True(t, passed)
15+
}

checkers/functions.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package checkers
2+
3+
import (
4+
"globstar.dev/analysis"
5+
"globstar.dev/checkers/javascript"
6+
)
7+
8+
func InitializeAnalysisFunctionDirectory(name string, language analysis.Language) {
9+
// Find a way to automate the registration of analyzers than adding them manually
10+
switch name {
11+
case "taint":
12+
switch language {
13+
case analysis.LangJs:
14+
javascript.JsTaintAnalyzer()
15+
}
16+
default:
17+
return
18+
}
19+
}

0 commit comments

Comments
 (0)