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
|
// Copyright 2024 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package declextract
import (
"slices"
"strings"
)
type Interface struct {
Type string
Name string
IdentifyingConst string
Files []string
Func string
Access string
Subsystems []string
ManualDescriptions bool
AutoDescriptions bool
ReachableLOC int
}
const (
IfaceSyscall = "SYSCALL"
IfaceNetlinkOp = "NETLINK"
IfaceIouring = "IOURING"
AccessUnknown = "unknown"
AccessUser = "user"
AccessNsAdmin = "ns_admin"
AccessAdmin = "admin"
)
func (ctx *context) noteInterface(iface *Interface) {
ctx.interfaces = append(ctx.interfaces, iface)
}
func (ctx *context) finishInterfaces() {
for _, iface := range ctx.interfaces {
iface.ReachableLOC = ctx.reachableLOC(iface.Func, iface.Files[0])
slices.Sort(iface.Files)
iface.Files = slices.Compact(iface.Files)
if iface.Access == "" {
iface.Access = AccessUnknown
}
}
ctx.interfaces = sortAndDedupSlice(ctx.interfaces)
}
func (ctx *context) processFunctions() {
for _, fn := range ctx.Functions {
ctx.funcs[fn.File+fn.Name] = fn
// Strictly speaking there may be several different static functions in different headers,
// but we ignore such possibility for now.
if !fn.IsStatic || strings.HasSuffix(fn.File, ".h") {
ctx.funcs[fn.Name] = fn
}
}
nocallers := 0
for _, fn := range ctx.Functions {
for _, callee := range fn.Calls {
called := ctx.findFunc(callee, fn.File)
if called == nil || called == fn {
continue
}
fn.calls = append(fn.calls, called)
called.callers++
}
fn.Calls = nil
if len(fn.calls) == 0 {
nocallers++
}
}
}
func (ctx *context) reachableLOC(name, file string) int {
fn := ctx.findFunc(name, file)
if fn == nil {
ctx.warn("can't find function %v called in %v", name, file)
return 0
}
reachable := make(map[*Function]bool)
ctx.collectRachable(fn, reachable)
loc := 0
for fn := range reachable {
loc += fn.LOC
}
return loc
}
func (ctx *context) collectRachable(fn *Function, reachable map[*Function]bool) {
// Ignore very common functions when computing reachability for complexity analysis.
// Counting kmalloc/printk against each caller is not useful (they have ~10K calls).
// There are also subsystem common functions (e.g. functions called in some parts of fs/net).
// The current threshold is somewhat arbitrary and is based on the number of callers in syzbot kernel:
// 6 callers - 2272 functions
// 5 callers - 3468 functions
// 4 callers - 6295 functions
// 3 callers - 16527 functions
const commonFuncThreshold = 5
reachable[fn] = true
for _, callee := range fn.calls {
if reachable[callee] || callee.callers >= commonFuncThreshold {
continue
}
ctx.collectRachable(callee, reachable)
}
}
func (ctx *context) findFunc(name, file string) *Function {
if fn := ctx.funcs[file+name]; fn != nil {
return fn
}
return ctx.funcs[name]
}
|