aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/declextract
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2025-04-09 10:38:03 +0200
committerDmitry Vyukov <dvyukov@google.com>2025-04-10 17:07:48 +0000
commit2d9fa31cf7abaddf944824814053e67b4d5c478e (patch)
tree12c10728542789e81ec4eef0496250933516e217 /pkg/declextract
parenta6ec893839824d1b080d8c0c02360e5db5c6d39f (diff)
pkg/declextract: export syscall variants as separate interfaces
Export each syscall variant (e.g. fcnt$*) as a separate interface. Effectively these are separate syscalls. We will want this for ioctl as well (it's not 1 interface).
Diffstat (limited to 'pkg/declextract')
-rw-r--r--pkg/declextract/declextract.go19
-rw-r--r--pkg/declextract/entity.go23
-rw-r--r--pkg/declextract/interface.go44
-rw-r--r--pkg/declextract/typing.go39
4 files changed, 73 insertions, 52 deletions
diff --git a/pkg/declextract/declextract.go b/pkg/declextract/declextract.go
index 3800ad70c..4f3c66173 100644
--- a/pkg/declextract/declextract.go
+++ b/pkg/declextract/declextract.go
@@ -181,7 +181,7 @@ func (ctx *context) processSyscalls() {
if call.Func == "__do_sys_ioctl" {
suffix = ctx.uniqualize("ioctl cmd", cmd)
}
- ctx.emitSyscall(&syscalls, &variant, "_"+suffix)
+ ctx.emitSyscall(&syscalls, &variant, "_"+suffix, cmd, varArg, cmd)
}
}
call.returnType = ctx.inferReturnType(call.Func, call.SourceFile, -1, "")
@@ -189,21 +189,30 @@ func (ctx *context) processSyscalls() {
typ := ctx.inferArgType(call.Func, call.SourceFile, i, -1, "")
refineFieldType(arg, typ, false)
}
- ctx.emitSyscall(&syscalls, call, "")
+ ctx.emitSyscall(&syscalls, call, "", "", -1, "")
}
ctx.Syscalls = sortAndDedupSlice(syscalls)
}
-func (ctx *context) emitSyscall(syscalls *[]*Syscall, call *Syscall, suffix string) {
+func (ctx *context) emitSyscall(syscalls *[]*Syscall, call *Syscall,
+ suffix, cmd string, scopeArg int, scopeVal string) {
fn := strings.TrimPrefix(call.Func, "__do_sys_")
for _, name := range ctx.syscallRename[fn] {
+ syscallName := name
+ identifyingConst := "__NR_" + name
+ if cmd != "" {
+ syscallName += "$" + cmd
+ identifyingConst = cmd
+ }
ctx.noteInterface(&Interface{
Type: IfaceSyscall,
- Name: name,
- IdentifyingConst: "__NR_" + name,
+ Name: syscallName,
+ IdentifyingConst: identifyingConst,
Files: []string{call.SourceFile},
Func: call.Func,
AutoDescriptions: true,
+ scopeArg: scopeArg,
+ scopeVal: scopeVal,
})
newCall := *call
newCall.Func = name + autoSuffix + suffix
diff --git a/pkg/declextract/entity.go b/pkg/declextract/entity.go
index 740530ca9..25c688cd4 100644
--- a/pkg/declextract/entity.go
+++ b/pkg/declextract/entity.go
@@ -24,15 +24,16 @@ type Output struct {
}
type Function struct {
- Name string `json:"name,omitempty"`
- File string `json:"file,omitempty"`
- IsStatic bool `json:"is_static,omitempty"`
+ Name string `json:"name,omitempty"`
+ File string `json:"file,omitempty"`
+ StartLine int `json:"start_line,omitempty"`
+ EndLine int `json:"end_line,omitempty"`
+ IsStatic bool `json:"is_static,omitempty"`
// Information about function scopes. There is a global scope (with Arg=-1),
// and scope for each switch case on the function argument.
Scopes []*FunctionScope `json:"scopes,omitempty"`
callers int
- calls []*Function
facts map[string]*typingNode
}
@@ -41,12 +42,14 @@ type FunctionScope struct {
Arg int `json:"arg"`
// The set of case values for this scope.
// It's empt for the global scope for the default case scope.
- Values []string `json:"values,omitempty"`
- LOC int `json:"loc,omitempty"`
- Calls []string `json:"calls,omitempty"`
- Facts []*TypingFact `json:"facts,omitempty"`
-
- fn *Function
+ Values []string `json:"values,omitempty"`
+ StartLine int `json:"start_line,omitempty"`
+ EndLine int `json:"end_line,omitempty"`
+ Calls []string `json:"calls,omitempty"`
+ Facts []*TypingFact `json:"facts,omitempty"`
+
+ fn *Function
+ calls []*Function
}
type ConstInfo struct {
diff --git a/pkg/declextract/interface.go b/pkg/declextract/interface.go
index d891b4899..e7a859da9 100644
--- a/pkg/declextract/interface.go
+++ b/pkg/declextract/interface.go
@@ -19,6 +19,9 @@ type Interface struct {
ManualDescriptions bool
AutoDescriptions bool
ReachableLOC int
+
+ scopeArg int
+ scopeVal string
}
const (
@@ -38,7 +41,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])
+ 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 == "" {
@@ -57,7 +60,6 @@ func (ctx *context) processFunctions() {
ctx.funcs[fn.Name] = fn
}
}
- nocallers := 0
for _, fn := range ctx.Functions {
for _, scope := range fn.Scopes {
for _, callee := range scope.Calls {
@@ -65,34 +67,26 @@ func (ctx *context) processFunctions() {
if called == nil || called == fn {
continue
}
- fn.calls = append(fn.calls, called)
+ scope.calls = append(scope.calls, called)
called.callers++
}
}
- if len(fn.calls) == 0 {
- nocallers++
- }
}
}
-func (ctx *context) reachableLOC(name, file string) int {
+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
}
- reachable := make(map[*Function]bool)
- ctx.collectRachable(fn, reachable)
- loc := 0
- for fn := range reachable {
- for _, scope := range fn.Scopes {
- loc += scope.LOC
- }
- }
- return loc
+ scopeFnArgs := ctx.inferArgFlow(fnArg{fn, scopeArg})
+ visited := make(map[*Function]bool)
+ return ctx.collectLOC(fn, scopeFnArgs, scopeVal, visited)
}
-func (ctx *context) collectRachable(fn *Function, reachable map[*Function]bool) {
+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).
@@ -103,13 +97,21 @@ func (ctx *context) collectRachable(fn *Function, reachable map[*Function]bool)
// 3 callers - 16527 functions
const commonFuncThreshold = 5
- reachable[fn] = true
- for _, callee := range fn.calls {
- if reachable[callee] || callee.callers >= commonFuncThreshold {
+ 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
}
- ctx.collectRachable(callee, reachable)
+ 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 {
diff --git a/pkg/declextract/typing.go b/pkg/declextract/typing.go
index 3de53ee62..04a11bbc7 100644
--- a/pkg/declextract/typing.go
+++ b/pkg/declextract/typing.go
@@ -260,7 +260,7 @@ func (ic *inferContext) walk(n *typingNode) {
}
if len(ic.path) < ic.maxDepth {
for e, scopes := range n.flows[ic.flowType] {
- if ic.relevantScope(scopes) {
+ if relevantScopes(ic.scopeFnArgs, ic.scopeVal, scopes) {
ic.walk(e)
}
}
@@ -268,26 +268,33 @@ func (ic *inferContext) walk(n *typingNode) {
ic.path = ic.path[:len(ic.path)-1]
}
-func (ic *inferContext) relevantScope(scopes []*FunctionScope) bool {
- if ic.scopeFnArgs == nil {
- // We are not doing scope-limited walk, so all scopes are relevant.
- return true
- }
+func relevantScopes(scopeFnArgs map[fnArg]bool, scopeVal string, scopes []*FunctionScope) bool {
for _, scope := range scopes {
- if scope.Arg == -1 {
- // Always use global scope.
+ if relevantScope(scopeFnArgs, scopeVal, scope) {
return true
}
- if !ic.scopeFnArgs[fnArg{scope.fn, scope.Arg}] {
- // The scope argument is not related to the current scope.
+ }
+ return false
+}
+
+func relevantScope(scopeFnArgs map[fnArg]bool, scopeVal string, scope *FunctionScope) bool {
+ if scopeFnArgs == nil {
+ // We are not doing scope-limited walk, so all scopes are relevant.
+ return true
+ }
+ if scope.Arg == -1 {
+ // Always use global scope.
+ return true
+ }
+ if !scopeFnArgs[fnArg{scope.fn, scope.Arg}] {
+ // The scope argument is not related to the current scope.
+ return true
+ }
+ // For the scope argument, check that it has the right value.
+ for _, val := range scope.Values {
+ if val == scopeVal {
return true
}
- // For the scope argument, check that it has the right value.
- for _, val := range scope.Values {
- if val == ic.scopeVal {
- return true
- }
- }
}
return false
}