aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/declextract/interface.go
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2024-12-11 16:49:01 +0100
committerDmitry Vyukov <dvyukov@google.com>2024-12-13 14:42:28 +0000
commitef0cd4a7bc26b206a7a5af18beed1589c388a204 (patch)
tree854f3b8475f091f6af07e52a88a033b7652e2da1 /pkg/declextract/interface.go
parenta35f0e6cafe5705ddc9f527bb6cfe297384021ef (diff)
tools/syz-declextract: extract info about all functions
Extract info about all functions, and compute total LOC for each interface. For now only static calls are considered, this doesn't handle indirect calls yet. This is just a groundwork for more complex callgraph/dataflow analysis.
Diffstat (limited to 'pkg/declextract/interface.go')
-rw-r--r--pkg/declextract/interface.go71
1 files changed, 71 insertions, 0 deletions
diff --git a/pkg/declextract/interface.go b/pkg/declextract/interface.go
index dfb223d16..7abce44fb 100644
--- a/pkg/declextract/interface.go
+++ b/pkg/declextract/interface.go
@@ -5,6 +5,7 @@ package declextract
import (
"slices"
+ "strings"
)
type Interface struct {
@@ -17,6 +18,7 @@ type Interface struct {
Subsystems []string
ManualDescriptions bool
AutoDescriptions bool
+ ReachableLOC int
}
const (
@@ -36,6 +38,7 @@ func (ctx *context) noteInterface(iface *Interface) {
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 == "" {
@@ -44,3 +47,71 @@ func (ctx *context) finishInterfaces() {
}
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 in 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]
+}