diff --git a/.github/workflows/release.lock.yml b/.github/workflows/release.lock.yml index 9840a38941a..b670dfbb32e 100644 --- a/.github/workflows/release.lock.yml +++ b/.github/workflows/release.lock.yml @@ -1,5 +1,6 @@ # gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"2d6e82aec6fea9061e1ef1cbb085324e6ed6c079230d70bbbb96611bb18cf7cf","body_hash":"646353d7bb4e5523bc85349c2cce38188190095a303f83cc95961ff145a47043","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"anchore/sbom-action","sha":"e22c389904149dbc22b58101806040fa8d37a610","version":"v0.24.0"},{"repo":"docker/build-push-action","sha":"53b7df96c91f9c12dcc8a07bcb9ccacbed38856a","version":"v7.3.0"},{"repo":"docker/login-action","sha":"af1e73f918a031802d376d3c8bbc3fe56130a9b0","version":"v4.4.0"},{"repo":"docker/metadata-action","sha":"dc802804100637a589fabce1cb79ff13a1411302","version":"v6.2.0"},{"repo":"docker/setup-buildx-action","sha":"bb05f3f5519dd87d3ba754cc423b652a5edd6d2c","version":"v4.2.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}# This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"anchore/sbom-action","sha":"e22c389904149dbc22b58101806040fa8d37a610","version":"v0.24.0"},{"repo":"docker/build-push-action","sha":"53b7df96c91f9c12dcc8a07bcb9ccacbed38856a","version":"v7.3.0"},{"repo":"docker/login-action","sha":"af1e73f918a031802d376d3c8bbc3fe56130a9b0","version":"v4.4.0"},{"repo":"docker/metadata-action","sha":"dc802804100637a589fabce1cb79ff13a1411302","version":"v6.2.0"},{"repo":"docker/setup-buildx-action","sha":"bb05f3f5519dd87d3ba754cc423b652a5edd6d2c","version":"v4.2.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) diff --git a/pkg/linters/appendbytestring/appendbytestring.go b/pkg/linters/appendbytestring/appendbytestring.go index e0c6f4c303c..bb960e2d93b 100644 --- a/pkg/linters/appendbytestring/appendbytestring.go +++ b/pkg/linters/appendbytestring/appendbytestring.go @@ -6,7 +6,6 @@ package appendbytestring import ( "fmt" "go/ast" - "go/types" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" @@ -67,7 +66,7 @@ func run(pass *analysis.Pass) (any, error) { } // The first argument must be []byte. - if !isByteSlice(pass, call.Args[0]) { + if !astutil.IsByteSlice(pass, call.Args[0]) { return } @@ -76,14 +75,14 @@ func run(pass *analysis.Pass) (any, error) { if !ok { return } - if !isByteSliceConversion(pass, conv) { + if !astutil.IsByteSliceConversion(pass, conv) { return } if len(conv.Args) != 1 { return } strArg := conv.Args[0] - if !isStringType(pass, strArg) { + if !astutil.IsStringType(pass, strArg) { return } @@ -103,40 +102,6 @@ func run(pass *analysis.Pass) (any, error) { return nil, nil } -// isByteSlice reports whether expr has type []byte. -func isByteSlice(pass *analysis.Pass, expr ast.Expr) bool { - t := pass.TypesInfo.TypeOf(expr) - if t == nil { - return false - } - sl, ok := t.Underlying().(*types.Slice) - if !ok { - return false - } - elem, ok := sl.Elem().(*types.Basic) - return ok && elem.Kind() == types.Byte -} - -// isByteSliceConversion reports whether conv is a []byte/[]uint8 conversion expression. -func isByteSliceConversion(pass *analysis.Pass, conv *ast.CallExpr) bool { - // A type conversion has a type expression as the call function. - funTypeInfo, ok := pass.TypesInfo.Types[conv.Fun] - if !ok || !funTypeInfo.IsType() { - return false - } - return isByteSlice(pass, conv) -} - -// isStringType reports whether expr has type string. -func isStringType(pass *analysis.Pass, expr ast.Expr) bool { - t := pass.TypesInfo.TypeOf(expr) - if t == nil { - return false - } - basic, ok := t.Underlying().(*types.Basic) - return ok && basic.Kind() == types.String -} - // buildFix returns a SuggestedFix rewriting append(b, []byte(s)...) to append(b, s...). func buildFix(pass *analysis.Pass, conv *ast.CallExpr, strArg ast.Expr) []analysis.SuggestedFix { sText := astutil.NodeText(pass.Fset, strArg) diff --git a/pkg/linters/bytescomparestring/bytescomparestring.go b/pkg/linters/bytescomparestring/bytescomparestring.go index b17f7b48bae..e34af17c94a 100644 --- a/pkg/linters/bytescomparestring/bytescomparestring.go +++ b/pkg/linters/bytescomparestring/bytescomparestring.go @@ -276,23 +276,9 @@ func extractByteSliceStringConv(pass *analysis.Pass, expr ast.Expr) (ast.Expr, b // The argument must be []byte (or []uint8). arg := call.Args[0] - if !isByteSlice(pass, arg) { + if !astutil.IsByteSlice(pass, arg) { return nil, false } return arg, true } - -// isByteSlice reports whether expr has underlying type []byte ([]uint8). -func isByteSlice(pass *analysis.Pass, expr ast.Expr) bool { - t := pass.TypesInfo.TypeOf(expr) - if t == nil { - return false - } - sl, ok := t.Underlying().(*types.Slice) - if !ok { - return false - } - elem, ok := sl.Elem().(*types.Basic) - return ok && elem.Kind() == types.Byte -} diff --git a/pkg/linters/internal/astutil/astutil.go b/pkg/linters/internal/astutil/astutil.go index 4aa9526f620..c9e524defa5 100644 --- a/pkg/linters/internal/astutil/astutil.go +++ b/pkg/linters/internal/astutil/astutil.go @@ -345,6 +345,39 @@ func QualifierShadowed(pkg *types.Package, pos token.Pos, name, importPath strin return pkgName.Imported().Path() != importPath } +// IsByteSlice reports whether expr has underlying type []byte ([]uint8). +func IsByteSlice(pass *analysis.Pass, expr ast.Expr) bool { + t := pass.TypesInfo.TypeOf(expr) + if t == nil { + return false + } + sl, ok := t.Underlying().(*types.Slice) + if !ok { + return false + } + elem, ok := sl.Elem().(*types.Basic) + return ok && elem.Kind() == types.Byte +} + +// IsByteSliceConversion reports whether conv is a []byte or []uint8 conversion expression. +func IsByteSliceConversion(pass *analysis.Pass, conv *ast.CallExpr) bool { + funTypeInfo, ok := pass.TypesInfo.Types[conv.Fun] + if !ok || !funTypeInfo.IsType() { + return false + } + return IsByteSlice(pass, conv) +} + +// IsStringType reports whether expr has underlying type string (or a named string type). +func IsStringType(pass *analysis.Pass, expr ast.Expr) bool { + t := pass.TypesInfo.TypeOf(expr) + if t == nil { + return false + } + basic, ok := t.Underlying().(*types.Basic) + return ok && basic.Kind() == types.String +} + // ConstIntValue returns the integer constant value of expr, if it is a // constant integer. func ConstIntValue(pass *analysis.Pass, expr ast.Expr) (int64, bool) { diff --git a/pkg/linters/internal/astutil/astutil_test.go b/pkg/linters/internal/astutil/astutil_test.go index 2350e9186d0..3a7cad06262 100644 --- a/pkg/linters/internal/astutil/astutil_test.go +++ b/pkg/linters/internal/astutil/astutil_test.go @@ -5,6 +5,8 @@ package astutil import ( "go/ast" "go/constant" + "go/importer" + "go/parser" "go/token" "go/types" "testing" @@ -12,6 +14,28 @@ import ( "golang.org/x/tools/go/analysis" ) +func typecheckSnippet(t *testing.T, src string) (*analysis.Pass, *ast.File) { + t.Helper() + + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "snippet.go", src, parser.SkipObjectResolution) + if err != nil { + t.Fatalf("ParseFile() error = %v", err) + } + + info := &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + } + cfg := types.Config{Importer: importer.Default()} + if _, err := cfg.Check("example.com/p", fset, []*ast.File{file}, info); err != nil { + t.Fatalf("type checking failed: %v", err) + } + + return &analysis.Pass{TypesInfo: info}, file +} + func TestRhsExprForIndex(t *testing.T) { t.Parallel() @@ -424,3 +448,94 @@ func TestBuildContainsFix(t *testing.T) { t.Fatalf("negated Message = %q, want %q", fixes[0].Message, "negated message") } } + +func TestByteStringTypeHelpers(t *testing.T) { + t.Parallel() + + const src = `package p + +func g(s string) []byte { return nil } + +func f(s string, b []byte) { + type myString string + var ms myString + + _ = []byte(s) + _ = g(s) + _ = b + _ = s + _ = ms +} +` + + pass, file := typecheckSnippet(t, src) + + var fn *ast.FuncDecl + for _, decl := range file.Decls { + decl, ok := decl.(*ast.FuncDecl) + if ok && decl.Name.Name == "f" { + fn = decl + break + } + } + if fn == nil || fn.Body == nil { + t.Fatal("failed to find function f in test snippet") + } + + var rhsExprs []ast.Expr + for _, stmt := range fn.Body.List { + assign, ok := stmt.(*ast.AssignStmt) + if !ok || len(assign.Rhs) != 1 { + continue + } + rhsExprs = append(rhsExprs, assign.Rhs[0]) + } + if len(rhsExprs) != 5 { + t.Fatalf("found %d assignment expressions in f, want 5", len(rhsExprs)) + } + + byteConv, ok := rhsExprs[0].(*ast.CallExpr) + if !ok { + t.Fatalf("rhs[0] type = %T, want *ast.CallExpr", rhsExprs[0]) + } + gCall, ok := rhsExprs[1].(*ast.CallExpr) + if !ok { + t.Fatalf("rhs[1] type = %T, want *ast.CallExpr", rhsExprs[1]) + } + bIdent, ok := rhsExprs[2].(*ast.Ident) + if !ok { + t.Fatalf("rhs[2] type = %T, want *ast.Ident", rhsExprs[2]) + } + sIdent, ok := rhsExprs[3].(*ast.Ident) + if !ok { + t.Fatalf("rhs[3] type = %T, want *ast.Ident", rhsExprs[3]) + } + msIdent, ok := rhsExprs[4].(*ast.Ident) + if !ok { + t.Fatalf("rhs[4] type = %T, want *ast.Ident", rhsExprs[4]) + } + + if !IsByteSlice(pass, bIdent) { + t.Fatal("IsByteSlice(b) = false, want true") + } + if IsByteSlice(pass, sIdent) { + t.Fatal("IsByteSlice(s) = true, want false") + } + + if !IsByteSliceConversion(pass, byteConv) { + t.Fatal("IsByteSliceConversion([]byte(s)) = false, want true") + } + if IsByteSliceConversion(pass, gCall) { + t.Fatal("IsByteSliceConversion(g(s)) = true, want false") + } + + if !IsStringType(pass, sIdent) { + t.Fatal("IsStringType(s) = false, want true") + } + if !IsStringType(pass, msIdent) { + t.Fatal("IsStringType(ms) = false, want true for named string") + } + if IsStringType(pass, bIdent) { + t.Fatal("IsStringType(b) = true, want false") + } +} diff --git a/pkg/linters/writebytestring/writebytestring.go b/pkg/linters/writebytestring/writebytestring.go index fcfcb9dc1a6..4a47f363279 100644 --- a/pkg/linters/writebytestring/writebytestring.go +++ b/pkg/linters/writebytestring/writebytestring.go @@ -98,11 +98,11 @@ func run(pass *analysis.Pass) (any, error) { if !ok { return } - if !isByteSliceConversion(pass, conv) { + if !astutil.IsByteSliceConversion(pass, conv) { return } strArg := conv.Args[0] - if !isStringType(pass, strArg) { + if !astutil.IsStringType(pass, strArg) { return } @@ -146,39 +146,6 @@ func run(pass *analysis.Pass) (any, error) { return nil, nil } -// isByteSliceConversion reports whether conv is a []byte or []uint8 conversion expression. -func isByteSliceConversion(pass *analysis.Pass, conv *ast.CallExpr) bool { - funTypeInfo, ok := pass.TypesInfo.Types[conv.Fun] - if !ok || !funTypeInfo.IsType() { - return false - } - return isByteSlice(pass, conv) -} - -// isByteSlice reports whether expr has type []byte ([]uint8). -func isByteSlice(pass *analysis.Pass, expr ast.Expr) bool { - t := pass.TypesInfo.TypeOf(expr) - if t == nil { - return false - } - sl, ok := t.Underlying().(*types.Slice) - if !ok { - return false - } - elem, ok := sl.Elem().(*types.Basic) - return ok && elem.Kind() == types.Byte -} - -// isStringType reports whether expr has type string (or named string type). -func isStringType(pass *analysis.Pass, expr ast.Expr) bool { - t := pass.TypesInfo.TypeOf(expr) - if t == nil { - return false - } - basic, ok := t.Underlying().(*types.Basic) - return ok && basic.Kind() == types.String -} - // isExactString reports whether t is the predeclared string type, not a named // type whose underlying type is string. io.WriteString(w Writer, s string) // requires a predeclared string; named string types need an explicit string(...)