Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

171 lines
5.0 KiB
Go

package languages
import (
"regexp"
"strings"
"github.com/zzet/gortex/internal/graph"
"github.com/zzet/gortex/internal/parser"
)
var (
// ginDispatchRe matches the Gin middleware-chain dispatcher: a method that
// invokes the next handler by indexing a handlers slice — `c.handlers[c.index](c)`
// / `engine.handlers[i](ctx)`. The indexed-call is the indirection static
// analysis cannot follow; the method that contains it is the chain
// dispatcher every request flows through.
ginDispatchRe = regexp.MustCompile(`\bhandlers\s*\[[^\]]+\]\s*\(`)
// ginRegisterRe matches a Gin route/middleware registration verb on a
// router or group — `.GET(`, `.Use(`, `.Handle(`, `.Any(`. The handler
// identifiers are the call's non-string, non-closure arguments.
ginRegisterRe = regexp.MustCompile(`\.(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|Any|Match|Handle|Use)\s*\(`)
)
// mineGinMiddleware stamps the Gin middleware-chain dispatcher and route
// registrations so the resolver's SynthGinMiddleware pass can bridge the
// dispatcher to the handlers it dispatches to (an edge static analysis drops at
// the `handlers[idx](c)` indirection). The dispatcher method gets
// gin_dispatcher=true; each registration's enclosing function accumulates the
// registered handler names in gin_handlers. The two are paired across files by
// the resolver, gated on a dispatcher existing — so this is inert in a repo
// that does not embed a Gin-style chain.
func mineGinMiddleware(src []byte, result *parser.ExtractionResult) {
hasDispatch := ginDispatchRe.Match(src)
hasRegister := ginRegisterRe.Match(src)
if !hasDispatch && !hasRegister {
return
}
funcRanges := buildFuncRanges(result)
nodeByID := map[string]*graph.Node{}
for _, n := range result.Nodes {
if n.Kind == graph.KindMethod || n.Kind == graph.KindFunction {
nodeByID[n.ID] = n
}
}
if hasDispatch {
for _, m := range ginDispatchRe.FindAllIndex(src, -1) {
n := nodeByID[findEnclosingFunc(funcRanges, lineAt(src, m[0]))]
if n == nil {
continue
}
if n.Meta == nil {
n.Meta = map[string]any{}
}
n.Meta["gin_dispatcher"] = true
}
}
if hasRegister {
for _, m := range ginRegisterRe.FindAllIndex(src, -1) {
// m[1] points just past the matched verb + '('; balance-parse the
// call's arguments from there.
args := goBalancedCallArgs(src, m[1]-1)
names := ginHandlerIdents(args)
if len(names) == 0 {
continue
}
n := nodeByID[findEnclosingFunc(funcRanges, lineAt(src, m[0]))]
if n == nil {
continue
}
if n.Meta == nil {
n.Meta = map[string]any{}
}
existing, _ := n.Meta["gin_handlers"].([]string)
n.Meta["gin_handlers"] = dedupAppend(existing, names)
}
}
}
// goBalancedCallArgs returns the source between the '(' at openParenIdx and its
// matching ')', skipping string/rune/raw literals so a paren inside a string is
// ignored. Empty when the call cannot be balanced.
func goBalancedCallArgs(src []byte, openParenIdx int) string {
if openParenIdx < 0 || openParenIdx >= len(src) || src[openParenIdx] != '(' {
return ""
}
depth := 0
i := openParenIdx
for i < len(src) {
switch src[i] {
case '(':
depth++
case ')':
depth--
if depth == 0 {
return string(src[openParenIdx+1 : i])
}
case '"', '\'', '`':
q := src[i]
i++
for i < len(src) && src[i] != q {
if src[i] == '\\' && q != '`' {
i++
}
i++
}
}
i++
}
return ""
}
// ginHandlerIdents extracts the handler identifiers from a registration call's
// argument list: the named functions passed as middleware/handlers. String
// literals (the route path) and inline `func(...)` closures are dropped; a
// `pkg.Handler` / `h.Method` reference is reduced to its final identifier
// (the name the resolver binds against). A trailing `()` factory call
// (`Logger()`) keeps the factory name.
func ginHandlerIdents(args string) []string {
var out []string
for _, part := range splitTopLevelCommas(args) {
part = strings.TrimSpace(part)
if part == "" {
continue
}
// Drop a string-literal path argument and inline closures.
if part[0] == '"' || part[0] == '`' || strings.HasPrefix(part, "func") {
continue
}
// Reduce `pkg.Handler` / `recv.Method` to the final identifier, and
// strip a trailing factory call's parens.
if i := strings.IndexByte(part, '('); i >= 0 {
part = part[:i]
}
if i := strings.LastIndexByte(part, '.'); i >= 0 {
part = part[i+1:]
}
part = strings.TrimSpace(part)
if part != "" && isGoIdent(part) {
out = append(out, part)
}
}
return out
}
func isGoIdent(s string) bool {
for i := 0; i < len(s); i++ {
b := s[i]
if b == '_' || (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (i > 0 && b >= '0' && b <= '9') {
continue
}
return false
}
return len(s) > 0
}
func dedupAppend(existing, add []string) []string {
seen := map[string]bool{}
for _, s := range existing {
seen[s] = true
}
for _, s := range add {
if !seen[s] {
seen[s] = true
existing = append(existing, s)
}
}
return existing
}