Skip to content

Commit aeeb641

Browse files
gadievronclaude
andcommitted
fix(parsers/go): classify entry points by signature, not return type or bare next(
Two over-broad heuristics in classifyUnitType seeded false remote-web entry points: isHTTPHandler matched HTTP type patterns (incl. HandlerFunc) against the RETURN type, tagging factories like func Logger() gin.HandlerFunc as http_handler (F10); isMiddleware tagged any body containing the substring next( as middleware, catching Go 1.23 iterators / lexers / cursors (F7). Match handler patterns only against parameters, and gate the next( heuristic on an HTTP request signature. Genuine handlers and middleware remain classified (covered by new tests). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ad17f3f commit aeeb641

2 files changed

Lines changed: 85 additions & 4 deletions

File tree

libs/openant-core/parsers/go/go_parser/extractor.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,13 @@ var httpHandlerPatterns = []*regexp.Regexp{
273273

274274
func (e *Extractor) isHTTPHandler(params, returns []string, code string) bool {
275275
paramsStr := strings.Join(params, " ")
276-
returnsStr := strings.Join(returns, " ")
277276

278-
// Check parameters for HTTP patterns
277+
// Check parameters for HTTP patterns. A handler is identified by what it
278+
// RECEIVES (request/context types in params), not by what it returns —
279+
// matching these patterns against the return type mis-tagged factories
280+
// such as `func Logger() gin.HandlerFunc` as http_handler entry points (F10).
279281
for _, pattern := range httpHandlerPatterns {
280-
if pattern.MatchString(paramsStr) || pattern.MatchString(returnsStr) {
282+
if pattern.MatchString(paramsStr) {
281283
return true
282284
}
283285
}
@@ -325,11 +327,20 @@ func (e *Extractor) isCLIHandler(params, returns []string, code string) bool {
325327
func (e *Extractor) isMiddleware(params, returns []string, code string) bool {
326328
// Middleware often takes and returns http.Handler or similar
327329
returnsStr := strings.Join(returns, " ")
330+
paramsStr := strings.Join(params, " ")
331+
332+
// A bare `next(` substring previously matched any body with a next() call
333+
// (Go 1.23 iterators, lexers, cursors), mislabelling them middleware (F7).
334+
// Only treat a `next(` call as middleware when the function has an HTTP
335+
// request signature, i.e. it is actually wrapping a handler.
336+
hasHTTPSignature := strings.Contains(paramsStr, "http.ResponseWriter") ||
337+
strings.Contains(paramsStr, "http.Request") ||
338+
strings.Contains(paramsStr, "http.Handler")
328339

329340
if strings.Contains(returnsStr, "http.Handler") ||
330341
strings.Contains(returnsStr, "http.HandlerFunc") ||
331342
strings.Contains(code, "next.ServeHTTP") ||
332-
strings.Contains(code, "next(") {
343+
(hasHTTPSignature && strings.Contains(code, "next(")) {
333344
return true
334345
}
335346

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package main
2+
3+
import "testing"
4+
5+
// Regression tests for F7 and F10 — over-broad unit-type classification in
6+
// classifyUnitType, which seeds false remote-web entry points downstream.
7+
8+
// F10: a factory that *returns* http.HandlerFunc (no request-shaped params,
9+
// no handler body) must NOT be classified http_handler — only a function that
10+
// RECEIVES a request is a handler.
11+
func TestFactoryReturningHandlerFuncIsNotHTTPHandler(t *testing.T) {
12+
e := NewExtractor(".")
13+
got := e.classifyUnitType(
14+
"NewLoggingHandler", "",
15+
[]string{"prefix string"}, // params: no request types
16+
[]string{"http.HandlerFunc"}, // returns a handler (factory)
17+
"{ return func(w http.ResponseWriter, r *http.Request) {} }",
18+
"x.go",
19+
)
20+
if got == UnitTypeHTTPHandler {
21+
t.Fatalf("factory returning http.HandlerFunc classified as %q, want != http_handler", got)
22+
}
23+
}
24+
25+
// F7: an iterator/lexer whose body merely contains a "next(" call must NOT be
26+
// classified middleware. Real middleware returns http.Handler/HandlerFunc or
27+
// calls next.ServeHTTP / next(w, r) with an http signature.
28+
func TestIteratorWithNextCallIsNotMiddleware(t *testing.T) {
29+
e := NewExtractor(".")
30+
got := e.classifyUnitType(
31+
"CutPrefix", "",
32+
[]string{"s string", "prefix string"}, // no http params
33+
[]string{"string", "bool"}, // no http returns
34+
"{ for next, hasNext := iter(); hasNext; { _ = next(); } ; return s, true }",
35+
"seq.go",
36+
)
37+
if got == UnitTypeMiddleware {
38+
t.Fatalf("iterator using next() classified as %q, want != middleware", got)
39+
}
40+
}
41+
42+
// Guard against over-correction: a genuine net/http handler and genuine
43+
// middleware must still be detected.
44+
func TestGenuineHTTPHandlerStillDetected(t *testing.T) {
45+
e := NewExtractor(".")
46+
got := e.classifyUnitType(
47+
"ServeIndex", "",
48+
[]string{"w http.ResponseWriter", "r *http.Request"},
49+
[]string{},
50+
"{ w.Write([]byte(\"ok\")) }",
51+
"h.go",
52+
)
53+
if got != UnitTypeHTTPHandler {
54+
t.Fatalf("genuine handler classified as %q, want http_handler", got)
55+
}
56+
}
57+
58+
func TestGenuineMiddlewareStillDetected(t *testing.T) {
59+
e := NewExtractor(".")
60+
got := e.classifyUnitType(
61+
"Logging", "",
62+
[]string{"next http.Handler"},
63+
[]string{"http.Handler"},
64+
"{ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){ next.ServeHTTP(w, r) }) }",
65+
"mw.go",
66+
)
67+
if got != UnitTypeMiddleware {
68+
t.Fatalf("genuine middleware classified as %q, want middleware", got)
69+
}
70+
}

0 commit comments

Comments
 (0)