aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/declextract/interface.go
blob: e7a859da9bd15dd49370231841ab4aed4262bb27 (plain)
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
// 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

	scopeArg int
	scopeVal string
}

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], iface.scopeArg, iface.scopeVal)
		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
		}
	}
	for _, fn := range ctx.Functions {
		for _, scope := range fn.Scopes {
			for _, callee := range scope.Calls {
				called := ctx.findFunc(callee, fn.File)
				if called == nil || called == fn {
					continue
				}
				scope.calls = append(scope.calls, called)
				called.callers++
			}
		}
	}
}

func (ctx *context) reachableLOC(name, file string, scopeArg int, scopeVal string) int {
	fn := ctx.findFunc(name, file)
	if fn == nil {
		ctx.warn("can't find function %v called in %v", name, file)
		return 0
	}
	scopeFnArgs := ctx.inferArgFlow(fnArg{fn, scopeArg})
	visited := make(map[*Function]bool)
	return ctx.collectLOC(fn, scopeFnArgs, scopeVal, visited)
}

func (ctx *context) collectLOC(fn *Function, scopeFnArgs map[fnArg]bool, scopeVal string,
	visited map[*Function]bool) int {
	// 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

	visited[fn] = true
	loc := max(0, fn.EndLine-fn.StartLine-1)
	for _, scope := range fn.Scopes {
		if !relevantScope(scopeFnArgs, scopeVal, scope) {
			loc -= max(0, scope.EndLine-scope.StartLine)
			continue
		}
		for _, callee := range scope.calls {
			if visited[callee] || callee.callers >= commonFuncThreshold {
				continue
			}
			loc += ctx.collectLOC(callee, scopeFnArgs, scopeVal, visited)
		}
	}
	return loc
}

func (ctx *context) findFunc(name, file string) *Function {
	if fn := ctx.funcs[file+name]; fn != nil {
		return fn
	}
	return ctx.funcs[name]
}