-
Notifications
You must be signed in to change notification settings - Fork 1
/
analysis.go
138 lines (116 loc) · 3.2 KB
/
analysis.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package soaap
import (
"fmt"
"regexp"
"strings"
)
// A function that can produce a CallGraph when given a CallGraph.
type Analyser func(*CallGraph) (CallGraph, error)
//
// Apply an analysis to a CallGraph using an already-loaded Results file.
//
// Possible analyses include:
//
// * "+graphtype": union with "graphtype" from the Results
// * "^graphtype": intersection with "graphtype" from the Results
// * ".graphtype": union-of-intersection with "graphtype"
// * ":spec": filter leaf nodes according to "spec" (see Filter)
//
// where "graphtype" can be:
// * vuln: the callgraph of previously-vulnerable code
// * privaccess: the call-and-data-flow graph of access to private data
//
func ApplyAnalysis(spec string, cg *CallGraph, results *Results,
depth int, report func(string)) (CallGraph, error) {
union := func(g *CallGraph) (CallGraph, error) {
err := cg.Union(*g)
return *cg, err
}
switch spec[0] {
case '+':
report("Adding " + spec[1:])
return extractAndCombine(spec[1:], results, report, union)
case '^':
report(fmt.Sprintf("Intersecting (depth %d) with %s", depth, spec[1:]))
return extractAndCombine(spec[1:], results, report,
func(g *CallGraph) (CallGraph, error) {
return cg.Intersect(*g, depth, true)
})
case '.':
report(fmt.Sprintf("Adding intersection (depth %d) with %s", depth, spec[1:]))
return extractAndCombine(spec[1:], results, report,
func(g *CallGraph) (CallGraph, error) {
err := cg.AddIntersecting(*g, depth)
return *cg, err
})
case ':':
report("Filtering with '" + spec + "'")
return Filter(*cg, spec[1:])
default:
report("Adding " + spec)
return extractAndCombine(spec, results, report, union)
}
}
func extractAndCombine(graphname string, r *Results, report func(string),
analyse Analyser) (CallGraph, error) {
g, err := r.ExtractGraph(graphname, report)
if err != nil {
return CallGraph{}, err
}
nodes, edges, flows := g.Size()
report(fmt.Sprintf("'%s': %d nodes, %d edges, %d flows",
graphname, nodes, edges, flows))
return analyse(&g)
}
//
// Filter a graph according to a colon-separated list of filter specifications,
// where each element can be:
//
// * "*": add all leaf nodes in the graph
// * "+regex": keep leaf nodes that match a pattern
// * "-regex": remove leaf nodes that match a pattern
//
// Examples:
//
// ":*:-foo:-bar" keeps all leaf nodes except "foo" and "bar"
//
// ":+.*foo.*:+.*bar.*" keeps only those leaf nodes (plus ancestors)
// with "foo" and "bar" in their names
//
func Filter(cg CallGraph, spec string) (CallGraph, error) {
leaves := make(strset)
for _, s := range strings.Split(spec, ":") {
if s == "*" {
leaves = leaves.Union(cg.leaves)
continue
}
var add bool
switch s[0] {
case '+':
add = true
case '-':
add = false
default:
return CallGraph{},
fmt.Errorf("unknown filter clause: '%s'", s)
}
pattern, err := regexp.Compile(s[1:])
if err != nil {
return cg, err
}
for leaf := range cg.leaves {
if pattern.MatchString(leaf) {
if add {
leaves.Add(leaf)
} else {
leaves.Remove(leaf)
}
}
}
}
keep := make(strset)
for leaf := range leaves {
keep = keep.Union(cg.Ancestors(leaf, -1))
}
return cg.Filter(keep), nil
}