Skip to content

Commit 3b011a2

Browse files
shivasuryaclaude
andcommitted
test(go): boost coverage for inferTypeFromParamMethodCall branches
Add targeted tests for uncovered branches in inferTypeFromParamMethodCall: - Package alias receiver (already handled by inferTypeFromFunctionCall) - Method not found in type's method set - Error-only return values (no usable non-error type) - Unqualified parameter type (no dot → extractionSplitGoTypeFQN returns false) Coverage for inferTypeFromParamMethodCall: 83.3% → 90.5%. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e2ea690 commit 3b011a2

1 file changed

Lines changed: 205 additions & 0 deletions

File tree

sast-engine/graph/callgraph/extraction/go_variables_param_test.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,208 @@ func handler(r *http.Request) {
330330
assert.False(t, hasBinding, "no binding for x (not a parameter named r)")
331331
}
332332
}
333+
334+
// TestParamAwareRHSInference_PackageAlias ensures package-qualified calls like
335+
// http.NewRequest() are NOT intercepted by param-aware inference (they're already
336+
// handled by inferTypeFromFunctionCall).
337+
func TestParamAwareRHSInference_PackageAlias(t *testing.T) {
338+
code := `package main
339+
340+
import "net/http"
341+
342+
func makeReq() {
343+
req, _ := http.NewRequest("GET", "/", nil)
344+
_ = req
345+
}
346+
`
347+
reg := core.NewGoModuleRegistry()
348+
reg.ModulePath = "github.com/example/app"
349+
reg.DirToImport = map[string]string{"/test": "main"}
350+
351+
loader := &mockStdlibLoaderWithTypes{
352+
stdlibPkgs: map[string]bool{"net/http": true},
353+
functions: map[string]*core.GoStdlibFunction{
354+
"net/http.NewRequest": {
355+
Name: "NewRequest",
356+
Returns: []*core.GoReturnValue{{Type: "*Request"}, {Type: "error"}},
357+
},
358+
},
359+
types: map[string]*core.GoStdlibType{},
360+
}
361+
reg.StdlibLoader = loader
362+
363+
importMap := &core.GoImportMap{Imports: map[string]string{"http": "net/http"}}
364+
typeEngine := resolution.NewGoTypeInferenceEngine(reg)
365+
366+
callGraph := &core.CallGraph{
367+
Functions: map[string]*graph.Node{
368+
"main.makeReq": {
369+
ID: "makeReq",
370+
Name: "makeReq",
371+
Type: "function_declaration",
372+
File: "/test/main.go",
373+
},
374+
},
375+
}
376+
377+
err := ExtractGoVariableAssignments("/test/main.go", []byte(code), typeEngine, reg, importMap, callGraph)
378+
assert.NoError(t, err)
379+
380+
// req should be resolved via inferTypeFromFunctionCall (stdlib path), not param-aware
381+
scope := typeEngine.GetScope("main.makeReq")
382+
assert.NotNil(t, scope)
383+
bindings, ok := scope.Variables["req"]
384+
assert.True(t, ok, "req should have a binding from stdlib lookup")
385+
if assert.Len(t, bindings, 1) {
386+
assert.Equal(t, "net/http.Request", bindings[0].Type.TypeFQN)
387+
}
388+
}
389+
390+
// TestParamAwareRHSInference_MethodNotFound ensures no binding is created when the
391+
// type is known but does not have the called method.
392+
func TestParamAwareRHSInference_MethodNotFound(t *testing.T) {
393+
code := `package main
394+
395+
import "net/http"
396+
397+
func handler(r *http.Request) {
398+
v := r.UnknownMethod()
399+
_ = v
400+
}
401+
`
402+
reg := core.NewGoModuleRegistry()
403+
reg.ModulePath = "github.com/example/app"
404+
reg.DirToImport = map[string]string{"/test": "main"}
405+
406+
loader := &mockStdlibLoaderWithTypes{
407+
stdlibPkgs: map[string]bool{"net/http": true},
408+
functions: map[string]*core.GoStdlibFunction{},
409+
types: map[string]*core.GoStdlibType{
410+
"net/http.Request": {
411+
Name: "Request",
412+
Methods: map[string]*core.GoStdlibFunction{},
413+
},
414+
},
415+
}
416+
reg.StdlibLoader = loader
417+
418+
importMap := &core.GoImportMap{Imports: map[string]string{"http": "net/http"}}
419+
typeEngine := resolution.NewGoTypeInferenceEngine(reg)
420+
421+
callGraph := &core.CallGraph{
422+
Functions: map[string]*graph.Node{
423+
"main.handler": {
424+
ID: "handler",
425+
Name: "handler",
426+
Type: "function_declaration",
427+
File: "/test/main.go",
428+
MethodArgumentsValue: []string{"r"},
429+
MethodArgumentsType: []string{"r: *http.Request"},
430+
},
431+
},
432+
}
433+
434+
err := ExtractGoVariableAssignments("/test/main.go", []byte(code), typeEngine, reg, importMap, callGraph)
435+
assert.NoError(t, err)
436+
437+
scope := typeEngine.GetScope("main.handler")
438+
if scope != nil {
439+
_, hasBinding := scope.Variables["v"]
440+
assert.False(t, hasBinding, "no binding when method is not in type's method set")
441+
}
442+
}
443+
444+
// TestParamAwareRHSInference_ErrorOnlyReturns ensures no binding is created when
445+
// the method only returns error (no non-error return value to infer from).
446+
func TestParamAwareRHSInference_ErrorOnlyReturns(t *testing.T) {
447+
code := `package main
448+
449+
import "net/http"
450+
451+
func handler(r *http.Request) {
452+
err := r.ParseForm()
453+
_ = err
454+
}
455+
`
456+
reg := core.NewGoModuleRegistry()
457+
reg.ModulePath = "github.com/example/app"
458+
reg.DirToImport = map[string]string{"/test": "main"}
459+
460+
loader := &mockStdlibLoaderWithTypes{
461+
stdlibPkgs: map[string]bool{"net/http": true},
462+
functions: map[string]*core.GoStdlibFunction{},
463+
types: map[string]*core.GoStdlibType{
464+
"net/http.Request": {
465+
Name: "Request",
466+
Methods: map[string]*core.GoStdlibFunction{
467+
"ParseForm": {
468+
Name: "ParseForm",
469+
Returns: []*core.GoReturnValue{{Type: "error"}},
470+
},
471+
},
472+
},
473+
},
474+
}
475+
reg.StdlibLoader = loader
476+
477+
importMap := &core.GoImportMap{Imports: map[string]string{"http": "net/http"}}
478+
typeEngine := resolution.NewGoTypeInferenceEngine(reg)
479+
480+
callGraph := &core.CallGraph{
481+
Functions: map[string]*graph.Node{
482+
"main.handler": {
483+
ID: "handler",
484+
Name: "handler",
485+
Type: "function_declaration",
486+
File: "/test/main.go",
487+
MethodArgumentsValue: []string{"r"},
488+
MethodArgumentsType: []string{"r: *http.Request"},
489+
},
490+
},
491+
}
492+
493+
err := ExtractGoVariableAssignments("/test/main.go", []byte(code), typeEngine, reg, importMap, callGraph)
494+
assert.NoError(t, err)
495+
496+
scope := typeEngine.GetScope("main.handler")
497+
if scope != nil {
498+
_, hasBinding := scope.Variables["err"]
499+
assert.False(t, hasBinding, "no binding when method only returns error")
500+
}
501+
}
502+
503+
// TestParamAwareRHSInference_UnqualifiedParamType verifies graceful handling when
504+
// the parameter type is unqualified (no package prefix, e.g. "MyStruct").
505+
func TestParamAwareRHSInference_UnqualifiedParamType(t *testing.T) {
506+
code := `package main
507+
508+
func process(s MyStruct) {
509+
v := s.Compute()
510+
_ = v
511+
}
512+
`
513+
reg := core.NewGoModuleRegistry()
514+
reg.ModulePath = "github.com/example/app"
515+
reg.DirToImport = map[string]string{"/test": "main"}
516+
517+
importMap := &core.GoImportMap{Imports: map[string]string{}}
518+
typeEngine := resolution.NewGoTypeInferenceEngine(reg)
519+
520+
callGraph := &core.CallGraph{
521+
Functions: map[string]*graph.Node{
522+
"main.process": {
523+
ID: "process",
524+
Name: "process",
525+
Type: "function_declaration",
526+
File: "/test/main.go",
527+
MethodArgumentsValue: []string{"s"},
528+
MethodArgumentsType: []string{"s: MyStruct"},
529+
},
530+
},
531+
}
532+
533+
// Must not panic; type has no dot, so extractionSplitGoTypeFQN returns false.
534+
assert.NotPanics(t, func() {
535+
_ = ExtractGoVariableAssignments("/test/main.go", []byte(code), typeEngine, reg, importMap, callGraph)
536+
})
537+
}

0 commit comments

Comments
 (0)