diff --git a/go.mod b/go.mod index 562478e8e5..c439e3fa13 100644 --- a/go.mod +++ b/go.mod @@ -57,11 +57,11 @@ require ( github.com/nats-io/nats-server/v2 v2.15.0 github.com/nats-io/nats.go v1.54.0 github.com/olekukonko/errors v1.3.0 - github.com/olekukonko/tablewriter v1.1.4 + github.com/olekukonko/tablewriter v1.1.5 github.com/onsi/ginkgo v1.16.5 github.com/onsi/ginkgo/v2 v2.32.1 github.com/onsi/gomega v1.43.1 - github.com/open-policy-agent/opa v1.19.1 + github.com/open-policy-agent/opa v1.21.0 github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260902170011-45af3945a067 github.com/opencloud-eu/reva/v2 v2.50.1-0.20261001091108-11d87fb6b985 @@ -222,7 +222,7 @@ require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-test/deep v1.1.0 // indirect - github.com/gobwas/glob v0.2.3 // indirect + github.com/gobwas/glob v1.0.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.4.0 // indirect @@ -260,11 +260,11 @@ require ( github.com/kovidgoyal/go-shm v1.0.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect - github.com/lestrrat-go/dsig v1.2.1 // indirect + github.com/lestrrat-go/dsig v1.4.0 // indirect github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/httprc/v3 v3.0.5 // indirect - github.com/lestrrat-go/jwx/v3 v3.1.1 // indirect + github.com/lestrrat-go/httprc/v3 v3.0.6 // indirect + github.com/lestrrat-go/jwx/v3 v3.3.0 // indirect github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/libregraph/oidc-go v1.1.0 // indirect github.com/longsleep/go-metrics v1.0.0 // indirect @@ -356,7 +356,7 @@ require ( github.com/trustelem/zxcvbn v1.0.1 // indirect github.com/urfave/cli/v2 v2.27.7 // indirect github.com/valyala/fastjson v1.6.10 // indirect - github.com/vektah/gqlparser/v2 v2.5.36 // indirect + github.com/vektah/gqlparser/v2 v2.5.37 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/wk8/go-ordered-map v1.0.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect diff --git a/go.sum b/go.sum index 3dabebd265..3e525d5726 100644 --- a/go.sum +++ b/go.sum @@ -231,8 +231,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3h github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= -github.com/dgraph-io/badger/v4 v4.9.4 h1:bcw+waCpzRZ2nmcSPbnPvDVhiEsn98TKmvnAhK7r7LM= -github.com/dgraph-io/badger/v4 v4.9.4/go.mod h1:nJjaJTUOSsQEBhsq209FmwCvMJzEA3e74RjZw6V2pQI= +github.com/dgraph-io/badger/v4 v4.9.6 h1:IQqMPVGLNCQr1b4Mu8lHkYm/xyqFRsyKaFEtyLi9CCQ= +github.com/dgraph-io/badger/v4 v4.9.6/go.mod h1:Xa9dAupjbwAacupWFCpa6YEn9E1PjBXkfZYr2I/8aWg= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -395,8 +395,8 @@ github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gobwas/glob v1.0.0 h1:p+FKbLEIsK1yZ39/OINwFvqNb5oyPY4H8xcy6uYu8dg= +github.com/gobwas/glob v1.0.0/go.mod h1:oWCdo522i2P1n/hMXGNWs7yoV4wy/ciZuUIbvKj5rkc= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= @@ -616,16 +616,16 @@ github.com/leonelquinteros/gotext v1.7.3-0.20260422134830-b012b4ccae69 h1:ZLo0bX github.com/leonelquinteros/gotext v1.7.3-0.20260422134830-b012b4ccae69/go.mod h1:ksG5iXViKefoupjy+0qQjAVoaDnylnQ1ejWl9g14wh8= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= -github.com/lestrrat-go/dsig v1.2.1 h1:MwxzZhE4+4fguHi+uDALKVlC3Cn+O1QU1Q/F8D7hVIc= -github.com/lestrrat-go/dsig v1.2.1/go.mod h1:RD2eOaidyPvpc7IJQoO3Qq52RWdy8ZcJs8lrOnoa1Kc= +github.com/lestrrat-go/dsig v1.4.0 h1:g7LUjK8cT74A5DzBXJI5HzsJuLhoYN0Wzj4nuOMIrH8= +github.com/lestrrat-go/dsig v1.4.0/go.mod h1:I8Nddg/vN2cUl/h8N7SRRApLnNNeyZPIqLYpvpOtGGo= github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc/v3 v3.0.5 h1:S+Mb4L2I+bM6JGTibLmxExhyTOqnXjqx+zi9MoXw/TM= -github.com/lestrrat-go/httprc/v3 v3.0.5/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= -github.com/lestrrat-go/jwx/v3 v3.1.1 h1:yd9AdPmZ4INnQ7k42IrzXYpnEG803+SrQ6hdMvzHJzw= -github.com/lestrrat-go/jwx/v3 v3.1.1/go.mod h1:uw/MN2M/Xiu4FhwcIwH11Zsh9JWx9SWzgALl7/uIEkU= +github.com/lestrrat-go/httprc/v3 v3.0.6 h1:4FpLQ18KK/ypPbVU3NLWJNRvH3kcYiqKqWfKGqNWxxI= +github.com/lestrrat-go/httprc/v3 v3.0.6/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= +github.com/lestrrat-go/jwx/v3 v3.3.0 h1:OXcYvQOQ7cxWzeZ/Q9sYk8ABe/kCSI371WmuACiCT+4= +github.com/lestrrat-go/jwx/v3 v3.3.0/go.mod h1:eIJhDcKHBwcgxqv8RiIylV67TVl1wJp/265IAHY1Db8= github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= github.com/libregraph/idm v0.5.0 h1:tDMwKbAOZzdeDYMxVlY5PbSqRKO7dbAW9KT42A51WSk= @@ -741,8 +741,8 @@ github.com/olekukonko/errors v1.3.0 h1:teJvgLGUEqMzBUms+Dj3/3szNqCG/Jdw9iDbum8fR github.com/olekukonko/errors v1.3.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= github.com/olekukonko/ll v0.1.6 h1:lGVTHO+Qc4Qm+fce/2h2m5y9LvqaW+DCN7xW9hsU3uA= github.com/olekukonko/ll v0.1.6/go.mod h1:NVUmjBb/aCtUpjKk75BhWrOlARz3dqsM+OtszpY4o88= -github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= -github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= +github.com/olekukonko/tablewriter v1.1.5 h1:4LoZSfMySpMQY3PT8RWJsJeuEuMIoo9xGRgvmqjg6IQ= +github.com/olekukonko/tablewriter v1.1.5/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= @@ -753,8 +753,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.43.1 h1:vGIPFuYrIO6/0Z09s0I0QQQgFchiX4+tb1re3MScJYo= github.com/onsi/gomega v1.43.1/go.mod h1:e/C2HwaZ1DhvjzXXuFhcR7hY7Sh9pl7MmoWKEjzwcdA= -github.com/open-policy-agent/opa v1.19.1 h1:aB1nOncChnTbQurjRQVJnjTJxditt8VqszlbaM3GGKU= -github.com/open-policy-agent/opa v1.19.1/go.mod h1:pb6Y6klyf7X7X8uXNDflruA9dQC2gMqWROXI5w/kvv0= +github.com/open-policy-agent/opa v1.21.0 h1:k/N0fieTkBPM0H7mIOrMd/xZPaMsxW70jIzIPeOBst4= +github.com/open-policy-agent/opa v1.21.0/go.mod h1:eJL6KUOIaW5YLnhJEA6sm3FOYRDJaHZvYT6geATbpPk= github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a h1:Sakl76blJAaM6NxylVkgSzktjo2dS504iDotEFJsh3M= github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY= github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 h1:W1ms+lP5lUUIzjRGDg93WrQfZJZCaV1ZP3KeyXi8bzY= @@ -981,8 +981,8 @@ github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4= github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE= -github.com/vektah/gqlparser/v2 v2.5.36 h1:CN9mKVHgMkc+XftdOWIhb4HEL8wKSYkFAqhf8booa7s= -github.com/vektah/gqlparser/v2 v2.5.36/go.mod h1:cAJ9qwVgPaUkWv6Gn8vn0mqOE0Ui5Pn56wNy5396XWo= +github.com/vektah/gqlparser/v2 v2.5.37 h1:jbb1Ilv+xBklV6653tKb4oVUupPNTLb5LmrnBKVI12Y= +github.com/vektah/gqlparser/v2 v2.5.37/go.mod h1:9O4Ox6Ngd3Y12bMD3w6i3CRQXh8W1oC1q0m6olCymDM= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= diff --git a/vendor/github.com/gobwas/glob/.gitignore b/vendor/github.com/gobwas/glob/.gitignore index b4ae623be5..534b466c5b 100644 --- a/vendor/github.com/gobwas/glob/.gitignore +++ b/vendor/github.com/gobwas/glob/.gitignore @@ -6,3 +6,5 @@ glob.iml *.dot *.png *.svg +patterns.txt +*.bench diff --git a/vendor/github.com/gobwas/glob/.travis.yml b/vendor/github.com/gobwas/glob/.travis.yml deleted file mode 100644 index e8a276826c..0000000000 --- a/vendor/github.com/gobwas/glob/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -sudo: false - -language: go - -go: - - 1.5.3 - -script: - - go test -v ./... diff --git a/vendor/github.com/gobwas/glob/bench.sh b/vendor/github.com/gobwas/glob/bench.sh index 804cf22e64..8570697b88 100644 --- a/vendor/github.com/gobwas/glob/bench.sh +++ b/vendor/github.com/gobwas/glob/bench.sh @@ -1,26 +1,41 @@ -#! /bin/bash +#!/bin/bash +# +# Compares the benchmarks of the current branch against a git revision: +# +# ./bench.sh v0.2.3 # all the benchmarks +# ./bench.sh master 'Match' # the ones matching a -bench regexp +# +# The results are written to *.bench files in the current directory and +# compared with benchstat (go install golang.org/x/perf/cmd/benchstat@latest). -bench() { - filename="/tmp/$1-$2.bench" - if test -e "${filename}"; - then - echo "Already exists ${filename}" - else - backup=`git rev-parse --abbrev-ref HEAD` - git checkout $1 - echo -n "Creating ${filename}... " - go test ./... -run=NONE -bench=$2 > "${filename}" -benchmem - echo "OK" - git checkout ${backup} - sleep 5 - fi +set -eu + +prev=$1 +what=${2:-.} +curr=$(git rev-parse --abbrev-ref HEAD) +rnd=$(head -c4 "$out" + echo "OK" + git checkout -q "$curr" + sleep 5 +} -to=$1 -current=`git rev-parse --abbrev-ref HEAD` +bench "$prev" +bench "$curr" -bench ${to} $2 -bench ${current} $2 - -benchcmp $3 "/tmp/${to}-$2.bench" "/tmp/${current}-$2.bench" +benchstat "$(file "$prev")" "$(file "$curr")" diff --git a/vendor/github.com/gobwas/glob/compiler/compiler.go b/vendor/github.com/gobwas/glob/compiler/compiler.go deleted file mode 100644 index 02e7de80a0..0000000000 --- a/vendor/github.com/gobwas/glob/compiler/compiler.go +++ /dev/null @@ -1,525 +0,0 @@ -package compiler - -// TODO use constructor with all matchers, and to their structs private -// TODO glue multiple Text nodes (like after QuoteMeta) - -import ( - "fmt" - "reflect" - - "github.com/gobwas/glob/match" - "github.com/gobwas/glob/syntax/ast" - "github.com/gobwas/glob/util/runes" -) - -func optimizeMatcher(matcher match.Matcher) match.Matcher { - switch m := matcher.(type) { - - case match.Any: - if len(m.Separators) == 0 { - return match.NewSuper() - } - - case match.AnyOf: - if len(m.Matchers) == 1 { - return m.Matchers[0] - } - - return m - - case match.List: - if m.Not == false && len(m.List) == 1 { - return match.NewText(string(m.List)) - } - - return m - - case match.BTree: - m.Left = optimizeMatcher(m.Left) - m.Right = optimizeMatcher(m.Right) - - r, ok := m.Value.(match.Text) - if !ok { - return m - } - - var ( - leftNil = m.Left == nil - rightNil = m.Right == nil - ) - if leftNil && rightNil { - return match.NewText(r.Str) - } - - _, leftSuper := m.Left.(match.Super) - lp, leftPrefix := m.Left.(match.Prefix) - la, leftAny := m.Left.(match.Any) - - _, rightSuper := m.Right.(match.Super) - rs, rightSuffix := m.Right.(match.Suffix) - ra, rightAny := m.Right.(match.Any) - - switch { - case leftSuper && rightSuper: - return match.NewContains(r.Str, false) - - case leftSuper && rightNil: - return match.NewSuffix(r.Str) - - case rightSuper && leftNil: - return match.NewPrefix(r.Str) - - case leftNil && rightSuffix: - return match.NewPrefixSuffix(r.Str, rs.Suffix) - - case rightNil && leftPrefix: - return match.NewPrefixSuffix(lp.Prefix, r.Str) - - case rightNil && leftAny: - return match.NewSuffixAny(r.Str, la.Separators) - - case leftNil && rightAny: - return match.NewPrefixAny(r.Str, ra.Separators) - } - - return m - } - - return matcher -} - -func compileMatchers(matchers []match.Matcher) (match.Matcher, error) { - if len(matchers) == 0 { - return nil, fmt.Errorf("compile error: need at least one matcher") - } - if len(matchers) == 1 { - return matchers[0], nil - } - if m := glueMatchers(matchers); m != nil { - return m, nil - } - - idx := -1 - maxLen := -1 - var val match.Matcher - for i, matcher := range matchers { - if l := matcher.Len(); l != -1 && l >= maxLen { - maxLen = l - idx = i - val = matcher - } - } - - if val == nil { // not found matcher with static length - r, err := compileMatchers(matchers[1:]) - if err != nil { - return nil, err - } - return match.NewBTree(matchers[0], nil, r), nil - } - - left := matchers[:idx] - var right []match.Matcher - if len(matchers) > idx+1 { - right = matchers[idx+1:] - } - - var l, r match.Matcher - var err error - if len(left) > 0 { - l, err = compileMatchers(left) - if err != nil { - return nil, err - } - } - - if len(right) > 0 { - r, err = compileMatchers(right) - if err != nil { - return nil, err - } - } - - return match.NewBTree(val, l, r), nil -} - -func glueMatchers(matchers []match.Matcher) match.Matcher { - if m := glueMatchersAsEvery(matchers); m != nil { - return m - } - if m := glueMatchersAsRow(matchers); m != nil { - return m - } - return nil -} - -func glueMatchersAsRow(matchers []match.Matcher) match.Matcher { - if len(matchers) <= 1 { - return nil - } - - var ( - c []match.Matcher - l int - ) - for _, matcher := range matchers { - if ml := matcher.Len(); ml == -1 { - return nil - } else { - c = append(c, matcher) - l += ml - } - } - return match.NewRow(l, c...) -} - -func glueMatchersAsEvery(matchers []match.Matcher) match.Matcher { - if len(matchers) <= 1 { - return nil - } - - var ( - hasAny bool - hasSuper bool - hasSingle bool - min int - separator []rune - ) - - for i, matcher := range matchers { - var sep []rune - - switch m := matcher.(type) { - case match.Super: - sep = []rune{} - hasSuper = true - - case match.Any: - sep = m.Separators - hasAny = true - - case match.Single: - sep = m.Separators - hasSingle = true - min++ - - case match.List: - if !m.Not { - return nil - } - sep = m.List - hasSingle = true - min++ - - default: - return nil - } - - // initialize - if i == 0 { - separator = sep - } - - if runes.Equal(sep, separator) { - continue - } - - return nil - } - - if hasSuper && !hasAny && !hasSingle { - return match.NewSuper() - } - - if hasAny && !hasSuper && !hasSingle { - return match.NewAny(separator) - } - - if (hasAny || hasSuper) && min > 0 && len(separator) == 0 { - return match.NewMin(min) - } - - every := match.NewEveryOf() - - if min > 0 { - every.Add(match.NewMin(min)) - - if !hasAny && !hasSuper { - every.Add(match.NewMax(min)) - } - } - - if len(separator) > 0 { - every.Add(match.NewContains(string(separator), true)) - } - - return every -} - -func minimizeMatchers(matchers []match.Matcher) []match.Matcher { - var done match.Matcher - var left, right, count int - - for l := 0; l < len(matchers); l++ { - for r := len(matchers); r > l; r-- { - if glued := glueMatchers(matchers[l:r]); glued != nil { - var swap bool - - if done == nil { - swap = true - } else { - cl, gl := done.Len(), glued.Len() - swap = cl > -1 && gl > -1 && gl > cl - swap = swap || count < r-l - } - - if swap { - done = glued - left = l - right = r - count = r - l - } - } - } - } - - if done == nil { - return matchers - } - - next := append(append([]match.Matcher{}, matchers[:left]...), done) - if right < len(matchers) { - next = append(next, matchers[right:]...) - } - - if len(next) == len(matchers) { - return next - } - - return minimizeMatchers(next) -} - -// minimizeAnyOf tries to apply some heuristics to minimize number of nodes in given tree -func minimizeTree(tree *ast.Node) *ast.Node { - switch tree.Kind { - case ast.KindAnyOf: - return minimizeTreeAnyOf(tree) - default: - return nil - } -} - -// minimizeAnyOf tries to find common children of given node of AnyOf pattern -// it searches for common children from left and from right -// if any common children are found – then it returns new optimized ast tree -// else it returns nil -func minimizeTreeAnyOf(tree *ast.Node) *ast.Node { - if !areOfSameKind(tree.Children, ast.KindPattern) { - return nil - } - - commonLeft, commonRight := commonChildren(tree.Children) - commonLeftCount, commonRightCount := len(commonLeft), len(commonRight) - if commonLeftCount == 0 && commonRightCount == 0 { // there are no common parts - return nil - } - - var result []*ast.Node - if commonLeftCount > 0 { - result = append(result, ast.NewNode(ast.KindPattern, nil, commonLeft...)) - } - - var anyOf []*ast.Node - for _, child := range tree.Children { - reuse := child.Children[commonLeftCount : len(child.Children)-commonRightCount] - var node *ast.Node - if len(reuse) == 0 { - // this pattern is completely reduced by commonLeft and commonRight patterns - // so it become nothing - node = ast.NewNode(ast.KindNothing, nil) - } else { - node = ast.NewNode(ast.KindPattern, nil, reuse...) - } - anyOf = appendIfUnique(anyOf, node) - } - switch { - case len(anyOf) == 1 && anyOf[0].Kind != ast.KindNothing: - result = append(result, anyOf[0]) - case len(anyOf) > 1: - result = append(result, ast.NewNode(ast.KindAnyOf, nil, anyOf...)) - } - - if commonRightCount > 0 { - result = append(result, ast.NewNode(ast.KindPattern, nil, commonRight...)) - } - - return ast.NewNode(ast.KindPattern, nil, result...) -} - -func commonChildren(nodes []*ast.Node) (commonLeft, commonRight []*ast.Node) { - if len(nodes) <= 1 { - return - } - - // find node that has least number of children - idx := leastChildren(nodes) - if idx == -1 { - return - } - tree := nodes[idx] - treeLength := len(tree.Children) - - // allocate max able size for rightCommon slice - // to get ability insert elements in reverse order (from end to start) - // without sorting - commonRight = make([]*ast.Node, treeLength) - lastRight := treeLength // will use this to get results as commonRight[lastRight:] - - var ( - breakLeft bool - breakRight bool - commonTotal int - ) - for i, j := 0, treeLength-1; commonTotal < treeLength && j >= 0 && !(breakLeft && breakRight); i, j = i+1, j-1 { - treeLeft := tree.Children[i] - treeRight := tree.Children[j] - - for k := 0; k < len(nodes) && !(breakLeft && breakRight); k++ { - // skip least children node - if k == idx { - continue - } - - restLeft := nodes[k].Children[i] - restRight := nodes[k].Children[j+len(nodes[k].Children)-treeLength] - - breakLeft = breakLeft || !treeLeft.Equal(restLeft) - - // disable searching for right common parts, if left part is already overlapping - breakRight = breakRight || (!breakLeft && j <= i) - breakRight = breakRight || !treeRight.Equal(restRight) - } - - if !breakLeft { - commonTotal++ - commonLeft = append(commonLeft, treeLeft) - } - if !breakRight { - commonTotal++ - lastRight = j - commonRight[j] = treeRight - } - } - - commonRight = commonRight[lastRight:] - - return -} - -func appendIfUnique(target []*ast.Node, val *ast.Node) []*ast.Node { - for _, n := range target { - if reflect.DeepEqual(n, val) { - return target - } - } - return append(target, val) -} - -func areOfSameKind(nodes []*ast.Node, kind ast.Kind) bool { - for _, n := range nodes { - if n.Kind != kind { - return false - } - } - return true -} - -func leastChildren(nodes []*ast.Node) int { - min := -1 - idx := -1 - for i, n := range nodes { - if idx == -1 || (len(n.Children) < min) { - min = len(n.Children) - idx = i - } - } - return idx -} - -func compileTreeChildren(tree *ast.Node, sep []rune) ([]match.Matcher, error) { - var matchers []match.Matcher - for _, desc := range tree.Children { - m, err := compile(desc, sep) - if err != nil { - return nil, err - } - matchers = append(matchers, optimizeMatcher(m)) - } - return matchers, nil -} - -func compile(tree *ast.Node, sep []rune) (m match.Matcher, err error) { - switch tree.Kind { - case ast.KindAnyOf: - // todo this could be faster on pattern_alternatives_combine_lite (see glob_test.go) - if n := minimizeTree(tree); n != nil { - return compile(n, sep) - } - matchers, err := compileTreeChildren(tree, sep) - if err != nil { - return nil, err - } - return match.NewAnyOf(matchers...), nil - - case ast.KindPattern: - if len(tree.Children) == 0 { - return match.NewNothing(), nil - } - matchers, err := compileTreeChildren(tree, sep) - if err != nil { - return nil, err - } - m, err = compileMatchers(minimizeMatchers(matchers)) - if err != nil { - return nil, err - } - - case ast.KindAny: - m = match.NewAny(sep) - - case ast.KindSuper: - m = match.NewSuper() - - case ast.KindSingle: - m = match.NewSingle(sep) - - case ast.KindNothing: - m = match.NewNothing() - - case ast.KindList: - l := tree.Value.(ast.List) - m = match.NewList([]rune(l.Chars), l.Not) - - case ast.KindRange: - r := tree.Value.(ast.Range) - m = match.NewRange(r.Lo, r.Hi, r.Not) - - case ast.KindText: - t := tree.Value.(ast.Text) - m = match.NewText(t.Text) - - default: - return nil, fmt.Errorf("could not compile tree: unknown node type") - } - - return optimizeMatcher(m), nil -} - -func Compile(tree *ast.Node, sep []rune) (match.Matcher, error) { - m, err := compile(tree, sep) - if err != nil { - return nil, err - } - - return m, nil -} diff --git a/vendor/github.com/gobwas/glob/glob.go b/vendor/github.com/gobwas/glob/glob.go index 2afde343af..fb3e475546 100644 --- a/vendor/github.com/gobwas/glob/glob.go +++ b/vendor/github.com/gobwas/glob/glob.go @@ -1,80 +1,158 @@ package glob import ( - "github.com/gobwas/glob/compiler" + "fmt" + + "github.com/gobwas/glob/internal/debug" "github.com/gobwas/glob/syntax" ) -// Glob represents compiled glob pattern. -type Glob interface { - Match(string) bool +// SyntaxError is returned by [Compile] when the given pattern can not be +// parsed. Offset points at the place in the pattern the error was detected +// at, so the tooling can do things like: +// +// {a,b +// ----^ unclosed `{` +type SyntaxError struct { + // Offset is a byte offset in the pattern. + Offset int + // Reason describes the error. + Reason string } -// Compile creates Glob for given pattern and strings (if any present after pattern) as separators. +func (s *SyntaxError) Error() string { + return fmt.Sprintf("glob: syntax error at %d: %s", s.Offset, s.Reason) +} + +// Pattern represents a compiled glob pattern. +// +// A pattern is compiled into a tree of matchers: +// +// `a` => "a" +// `a*` => ["a"·*] +// `{a*,b}` => {["a"·*]|"b"} +// +// Matching is a backtracking walk over that tree; see [Pattern.Match]. +type Pattern struct { + // str is the pattern text the Pattern was compiled from; see + // [Pattern.String]. + str string + + // sep are the separators the Pattern was compiled with; see + // [Pattern.Separators]. + sep []rune + + // m is the root of the matcher tree; see [matcher]. + m matcher + + // state tells whether matching m needs the backtracking state, that + // is, whether it may save checkpoints; see [needsState]. A pattern + // without them is matched with a plain call chain. + state bool + + // The match preconditions: every matching string is at least minLen + // bytes and ends with suffix. They fail the obvious mismatches in O(1) + // instead of a backtracking walk -- e.g. `a*a*a*b` requires the + // trailing `b`, no matter how the stars go. + // + // A precondition pays off only when it catches a mismatch earlier than + // the walk would, which is why: + // + // - there is no required prefix: the walk is left-to-right, so a + // leading literal is the first thing checked anyway, while a bad + // suffix or length is discovered last, after the whole + // backtracking exploration; + // + // - they are computed for the stateful patterns only: a stateless + // pattern is a plain call chain whose matchers perform these very + // checks themselves (e.g. suffixMatcher is a HasSuffix), so the + // precondition would only duplicate them. + minLen int + suffix string +} + +// String returns the source text used to compile the pattern, the same way +// [regexp.Regexp.String] does. +// +// Note that separators are not part of String: they are given to Compile +// alongside the pattern text. +func (p *Pattern) String() string { + return p.str +} + +// Separators returns the separators the pattern was compiled with, in the +// order they were given to Compile; nil when there are none. +// +// The returned slice is the very one given to Compile, sharing its backing +// array: it is not copied on the way in or out. Matching does not use it. +func (p *Pattern) Separators() []rune { + return p.sep +} + +func init() { + // The matcher tree is unexported; hand its rendering to the in-module + // tooling (cmd/globtest -v) without widening the public API. + debug.Tree = func(p any) string { + return p.(*Pattern).m.String() + } +} + +// Compile compiles the glob pattern. The separators, if given, are the +// characters `*` and `?` do not match (`**` does); they can not be changed +// after the compilation, see [Pattern.Separators]. A malformed pattern is +// reported with a [*SyntaxError]. +// // The pattern syntax is: // -// pattern: -// { term } +// pattern: +// { term } // -// term: -// `*` matches any sequence of non-separator characters -// `**` matches any sequence of characters -// `?` matches any single non-separator character -// `[` [ `!` ] { character-range } `]` -// character class (must be non-empty) -// `{` pattern-list `}` -// pattern alternatives -// c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`) -// `\` c matches character c +// term: +// `*` matches any sequence of non-separator characters +// `**` matches any sequence of characters +// `?` matches any single non-separator character +// `[` [ `!` ] class `]` +// character class; `!` negates it +// `{` pattern-list `}` +// pattern alternatives +// c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`) +// `\` c matches character c // -// character-range: -// c matches character c (c != `\\`, `-`, `]`) -// `\` c matches character c -// lo `-` hi matches character c for lo <= c <= hi +// class: +// lo `-` hi matches character c for lo <= c <= hi +// { c } matches any of the listed characters (c != `\`, `]`; +// `\` c matches c, `-` is literal here); must be non-empty // -// pattern-list: -// pattern { `,` pattern } -// comma-separated (without spaces) patterns -// -func Compile(pattern string, separators ...rune) (Glob, error) { - ast, err := syntax.Parse(pattern) - if err != nil { - return nil, err - } - - matcher, err := compiler.Compile(ast, separators) - if err != nil { - return nil, err - } - - return matcher, nil +// pattern-list: +// pattern { `,` pattern } +// comma-separated (without spaces) patterns +func Compile(pattern string, separators ...rune) (*Pattern, error) { + return compile(pattern, separators) } -// MustCompile is the same as Compile, except that if Compile returns error, this will panic -func MustCompile(pattern string, separators ...rune) Glob { +// MustCompile is the same as Compile, except that if Compile returns error, +// this will panic. +func MustCompile(pattern string, separators ...rune) *Pattern { g, err := Compile(pattern, separators...) if err != nil { panic(err) } - return g } -// QuoteMeta returns a string that quotes all glob pattern meta characters -// inside the argument text; For example, QuoteMeta(`{foo*}`) returns `\[foo\*\]`. +// QuoteMeta returns a copy of the s having all glob meta characters escaped. func QuoteMeta(s string) string { + // 2 is a pessimistic way of allocating an extra byte per each byte in s. b := make([]byte, 2*len(s)) - - // a byte loop is correct because all meta characters are ASCII j := 0 + // A byte loop is correct here because all meta characters are ASCII. for i := 0; i < len(s); i++ { - if syntax.Special(s[i]) { + if syntax.IsSpecial(s[i]) { b[j] = '\\' j++ } b[j] = s[i] j++ } - return string(b[0:j]) } diff --git a/vendor/github.com/gobwas/glob/internal/debug/debug_disabled.go b/vendor/github.com/gobwas/glob/internal/debug/debug_disabled.go new file mode 100644 index 0000000000..1dcc50d44b --- /dev/null +++ b/vendor/github.com/gobwas/glob/internal/debug/debug_disabled.go @@ -0,0 +1,8 @@ +//go:build !globdebug +// +build !globdebug + +package debug + +const Enabled = false + +func Printf(f string, args ...any) {} diff --git a/vendor/github.com/gobwas/glob/internal/debug/debug_enabled.go b/vendor/github.com/gobwas/glob/internal/debug/debug_enabled.go new file mode 100644 index 0000000000..26aeebd61f --- /dev/null +++ b/vendor/github.com/gobwas/glob/internal/debug/debug_enabled.go @@ -0,0 +1,14 @@ +//go:build globdebug +// +build globdebug + +package debug + +import ( + "fmt" +) + +const Enabled = true + +func Printf(f string, args ...any) { + fmt.Printf(f, args...) +} diff --git a/vendor/github.com/gobwas/glob/internal/debug/tree.go b/vendor/github.com/gobwas/glob/internal/debug/tree.go new file mode 100644 index 0000000000..c716a141ed --- /dev/null +++ b/vendor/github.com/gobwas/glob/internal/debug/tree.go @@ -0,0 +1,8 @@ +package debug + +// Tree renders the matcher tree of a compiled *glob.Pattern. +// +// It is set by package glob at init time: the pattern internals are +// unexported, and this keeps them so while letting the in-module tooling +// (cmd/globtest -v) print them. The format is not stable. +var Tree func(pattern any) string diff --git a/vendor/github.com/gobwas/glob/match.go b/vendor/github.com/gobwas/glob/match.go new file mode 100644 index 0000000000..2b84257062 --- /dev/null +++ b/vendor/github.com/gobwas/glob/match.go @@ -0,0 +1,788 @@ +package glob + +import ( + "fmt" + "slices" + "strconv" + "strings" + "sync" + "unicode/utf8" + "unsafe" + + "github.com/gobwas/glob/internal/debug" +) + +// Match reports whether s matches the pattern. +func (p *Pattern) Match(s string) bool { + var x matchContext + if p.state { + if len(s) < p.minLen || !strings.HasSuffix(s, p.suffix) { + return false + } + state := acquireState() + defer releaseState(state) + x.state = state + } + for { + n, match := p.m.Match(x, s[x.offset:]) + // Note: debug.Enabled is a build-tag constant; when it is false the + // whole block (including the argument evaluation) is compiled away. + if debug.Enabled && x.state != nil { + debug.Printf("stack: %s\n", formatStack(x.state.stack)) + debug.Printf("stars: %s\n", formatStack(x.state.stars)) + } + if match && n == len(s[x.offset:]) { + if debug.Enabled { + debug.Printf("match!\n") + } + return true + } + if x.state == nil { + // The pattern never saves checkpoints (see [needsState]): + // nothing to backtrack to. + return false + } + var ( + c checkpoint + k checkpointKind + ) + switch { + case len(x.state.stars) > 0: + if debug.Enabled { + debug.Printf("has star\n") + } + c = popLast(&x.state.stars) + k = checkpointStars + + case len(x.state.stack) > 0: + if debug.Enabled { + debug.Printf("has stack\n") + } + c = popLast(&x.state.stack) + k = checkpointStack + + default: + if debug.Enabled { + debug.Printf("no match\n") + } + return false + } + x.offset = c.offset + x.kind = k + x.frame = frame{ + path: c.path, + depth: 0, + } + } +} + +// matcher is a node of the tree a pattern compiles into. +// +// Match matches the beginning of s and reports how many bytes it consumed; +// the caller goes on with the rest. A matcher may consume nothing and still +// match: a void does, and so does a non-terminal star -- it stores its +// restart points instead and lets the walk continue, to be resumed from one +// of them on a mismatch later; see [Pattern.Match]. The context tells where +// in the input and in the tree the matcher is; see [matchContext]. +// +// String renders the node for the debug output and cmd/globtest -v; the +// notation is described at [Pattern]. +type matcher interface { + Match(matchContext, string) (n int, matched bool) + String() string +} + +// frame tells where in the matcher tree the walk currently is: the path from +// the root down to the current node, and the depth of the node. +// +// A checkpoint stores the frame's path; resuming it re-enters the tree from +// the root and follows the path back to the very node that saved it (an +// alternative to try, or a star to restart) -- see [Pattern.Match], +// [multiMatcher.Match] and [altMatcher.Match]. +// +// For `{a*b,c}y`, compiled into [{["a"·*·"b"]|"c"}·"y"], the frame at each +// node the walk visits is: +// +// node path depth +// [{["a"·*·"b"]|"c"}·"y"] [] 0 the root +// {["a"·*·"b"]|"c"} [0]* 1 +// ["a"·*·"b"] [0 0]* 2 +// "a" [0 0 0]* 3 +// * [0 0 1] 3 +// "b" [0 0 2] 3 +// "c" [0 1] 2 reached only by resuming the +// checkpoint the alt saved +// "y" [1] 1 +// +// * the zeros are virtual: turning to child #0 records nothing, so the +// path actually stored is [] -- see path below. +type frame struct { + // path addresses the current node: path[d] is the index of the child taken + // at depth d, that is, in the multiMatcher or altMatcher d levels below + // the root. + // + // For `{a*b,c}y`, compiled into [{["a"·*·"b"]|"c"}·"y"]: + // + // [0 1] the "c" alternative -- what the alt checkpoints when it + // enters ["a"·*·"b"], to be tried on a mismatch + // + // [0 0 1] the star: child #1 of ["a"·*·"b"], which is alternative #0 + // of the alt, which is child #0 of the root sequence -- what + // the star's restart points carry + // + // It is recorded lazily: an index is stored only when the walk turns to a + // child other than #0 (see [matchContext.branch]), so the path may be + // shorter than the current depth -- the missing trailing entries are + // implicitly zero, and [frame.index] reads them as such. In the example + // above, at "a" the path is still empty, not [0 0 0]: the alt, alternative + // #0 and "a" were all entered as child #0. + path []int + + // depth is the depth of the current node (the one [frame.path] leads to): + // 0 at the root, 1 at its children, and so on; [matchContext.next] + // increments it on every descent. It is also where the node's own entry in + // path lives: path[depth] is the child to take next -- [frame.index] reads + // it, [matchContext.branch] writes it. + // + // It is not len(path): the path is recorded lazily, so it may fall short + // of the depth, and, when resuming a checkpoint, it is the whole path of + // the checkpoint, reaching beyond the depth all the way down to the node + // to resume at. + // + // To say it the other way, depth is what len(path) would be were the path + // always recorded in full -- the virtual zeros included -- and cut at the + // current node: the length of the node's full address in the tree. + // + // For `{a*b,c}y`, compiled into [{["a"·*·"b"]|"c"}·"y"], the nodes at + // each depth are: + // + // 0 [{["a"·*·"b"]|"c"}·"y"] the root sequence + // 1 {["a"·*·"b"]|"c"}, "y" its children + // 2 ["a"·*·"b"], "c" the alternatives + // 3 "a", *, "b" the children of ["a"·*·"b"] + depth int +} + +// index returns the index of the child to take at the current node: the one +// the path leads to when resuming a checkpoint, #0 otherwise. +func (v frame) index() int { + if v.depth >= len(v.path) { + return 0 + } + return v.path[v.depth] +} + +// checkpoint is a place to resume the walk from on a mismatch. +type checkpoint struct { + // offset is the position in the input to resume at. + offset int + + // path leads to the node that saved the checkpoint; see [frame]. + // + // Unlike a live frame's, it is always at full length -- the virtual zeros + // written out -- so len(path) is the depth of that node, and a checkpoint + // needs no depth of its own: resuming starts at the root and tells it has + // arrived by comparing the walk's depth against len(path); see + // [matchContext.branch], [matchContext.storeStar] and [altMatcher.Match]. + path []int +} + +// checkpointKind tells which pile a checkpoint was taken from during the +// backtracking in [Pattern.Match]. +type checkpointKind int + +const ( + // checkpointStack is an alternative checkpoint saved by altMatcher. + checkpointStack checkpointKind = iota + // checkpointStars is a star restart point saved by starMatcher. + checkpointStars +) + +// matchContext is what a matcher is called with: where in the input and in +// the tree it is, plus the backtracking state shared by the whole walk. It +// is passed by value, so a matcher's changes to it are seen by its +// descendants only. +type matchContext struct { + // offset is the position in the whole input the current node matches from. + // The matchers see only the remainder of the input, so it is what a + // checkpoint records to resume at the same place; see [checkpoint]. + offset int + + // frame is where in the matcher tree the current node is; see [frame]. + frame frame + + // state holds the checkpoint piles and the path arena shared by the + // whole walk. + state *matchState + + // kind tells which pile the checkpoint being resumed was taken from. + // See [altMatcher.Match] for its use. + kind checkpointKind + + // starsFloor is the number of star restart points that existed when + // the walk entered the current alternative. The entries below it were + // born outside of the alternative and must not be discarded by the + // stars inside it; see [matchContext.storeStar]. + starsFloor int +} + +// push saves an alternative checkpoint at the current offset for the node f +// leads to; see [altMatcher.Match]. +func (x matchContext) push(f frame) { + x.state.stack = append(x.state.stack, checkpoint{ + offset: x.offset, + path: f.path, + }) + if debug.Enabled { + debug.Printf( + "checkpoint offset=%d path=%v\n", + x.offset, f.path, + ) + } +} + +// storeStar saves a restart point for the current star at offset bytes +// further in the input; reset tells whether the star may discard the pending +// restart points first, see below. +func (x matchContext) storeStar(offset int, reset bool) { + path := x.frame.path + if d := x.frame.depth; len(path) < d { + // The walk records an index in path only when it turns to a child + // other than the first one; levels entered at child #0 are implicit. + // Store the path at its full length (the missing entries are always + // zeros) so that the alts above can tell this checkpoint from their + // own. See [altMatcher.Match]. + p := x.state.allocPath(d) + copy(p, path) + path = p + } + if reset { + // This star can extend over anything the pending restart points could + // reach -- they are redundant, discard them. See + // research.swtch.com/glob. + // + // However, only the restart points born inside the current alternative + // may be discarded. An outer star, when resumed, re-enters the + // enclosing alt and may pick another alternative -- something this + // star, locked inside its own alternative, can not absorb. See the + // `*{*0,}` test: the outer star must survive the inner one to reach + // the empty alternative. + x.state.stars = x.state.stars[:x.starsFloor] + } + x.state.stars = append(x.state.stars, checkpoint{ + offset: x.offset + offset, + path: path, + }) + if debug.Enabled { + debug.Printf( + "star offset=%d path=%v reset=%t\n", + x.offset+offset, path, reset, + ) + } +} + +// next returns the context for a child of the current node, matching offset +// bytes further in the input: one level deeper in the tree. +func (x matchContext) next(offset int) matchContext { + x.offset = x.offset + offset + x.frame.depth += 1 + return x +} + +// branch returns a copy of the current frame with its path turned to child i +// at the current level, discarding the deeper levels. The new path is +// allocated from the state's arena. +func (x matchContext) branch(i int) frame { + f := x.frame + path := x.state.allocPath(f.depth + 1) + copy(path, f.path) + path[f.depth] = i + f.path = path + return f +} + +// matchState holds the backtracking state of a single [Pattern.Match] call. +// The states are pooled globally: the buffers keep their grown capacity +// between the matches, so a steady-state Match does not allocate them. +type matchState struct { + // stars are the star restart points and stack the alternative + // checkpoints, both LIFO. On a mismatch the walk resumes from the most + // recent restart point, if any, before the most recent alternative; see + // [Pattern.Match]. + stars []checkpoint + stack []checkpoint + + // arena is the buffer the checkpoint paths are allocated from; see + // [matchState.allocPath]. It is bulk-freed when the match ends, which + // spares the per-path lifetime reasoning: a path may be shared between + // the current frame and several checkpoints. + arena []int +} + +// allocPath returns a zeroed []int of length n allocated from the state's +// arena. When the arena runs out of capacity, a fresh chunk is started; the +// paths allocated from the previous chunks stay valid, since the chunks are +// kept alive by the paths referencing them. +func (st *matchState) allocPath(n int) []int { + if cap(st.arena)-len(st.arena) < n { + st.arena = make([]int, 0, max(2*cap(st.arena), n, 32)) + } + p := st.arena[len(st.arena) : len(st.arena)+n : len(st.arena)+n] + st.arena = st.arena[:len(st.arena)+n] + clear(p) + return p +} + +var statePool sync.Pool // Pool[*matchState] + +// acquireState takes a state from the pool, or makes a new one. +func acquireState() *matchState { + if st, _ := statePool.Get().(*matchState); st != nil { + return st + } + return &matchState{} +} + +// releaseState empties the state and puts it back to the pool. +func releaseState(st *matchState) { + resetCheckpoints(&st.stars) + resetCheckpoints(&st.stack) + // The arena holds no references; keep the (largest) chunk as is. + st.arena = st.arena[:0] + statePool.Put(st) +} + +// resetCheckpoints empties s keeping its capacity. The whole backing array +// is zeroed (not only the live part) to drop the references to the +// checkpoint paths popped during the match. +func resetCheckpoints(s *[]checkpoint) { + full := (*s)[:cap(*s)] + clear(full) + *s = full[:0] +} + +// multiMatcher is a sequence, ["a"·*·"b"]: it matches its children one +// after another, each on the input the previous ones left. +type multiMatcher []matcher + +func (ms multiMatcher) String() string { + var sb strings.Builder + sb.WriteByte('[') + for i, m := range ms { + if i > 0 { + sb.WriteString("·") + } + sb.WriteString(m.String()) + } + sb.WriteByte(']') + return sb.String() +} + +func (ms multiMatcher) Match(x matchContext, s string) (n int, ok bool) { + for i := x.frame.index(); i < len(ms); i++ { + if i != x.frame.index() && x.state != nil { + // The path is recorded for the checkpoints the descendants may + // save; in a stateless walk (see [needsState]) there are none + // and nobody would ever read it. + x.frame = x.branch(i) + } + child := x.next(n) + k, ok := ms[i].Match(child, s[n:]) + if debug.Enabled { + debug.Printf( + "[%T@%p] #%d match %#q against %[5]T(%[5]s) at path=%v depth=%d => %d %t\n", + ms, unsafe.SliceData(ms), i, s[n:], ms[i], + child.frame.path, child.frame.depth, k, ok, + ) + } + if !ok { + return 0, false + } + n += k + } + return n, true +} + +// altMatcher is a group of alternatives, {"a"|"b"}: it matches the one the +// walk is at (the first one when entered anew), having saved a checkpoint +// for the next one to be tried on a mismatch later. +type altMatcher []matcher + +func (ms altMatcher) String() string { + var sb strings.Builder + sb.WriteByte('{') + for i, m := range ms { + if i > 0 { + sb.WriteString("|") + } + sb.WriteString(m.String()) + } + sb.WriteByte('}') + return sb.String() +} + +func (ms altMatcher) Match(x matchContext, s string) (int, bool) { + i := x.frame.index() + // Save a checkpoint for the next alternative to consider it in case of + // a mismatch later (if any). This must be done only when: + // + // - the alt is entered for the first time (the resume path ends above + // this level, or there is none); + // + // - the resume path ends exactly at this level with an alternative + // checkpoint -- its job is "try alternative #i", so the one for the + // next alternative must be saved now. Note that a star restart point + // may end at this level too (a star being a direct child of the alt, + // as in `{*,b}`) -- it must not trigger a save. + // + // Otherwise the walk is merely passing through this alt on its way to + // resume a deeper checkpoint -- the one for the next alternative was + // already saved when the alt was entered for the first time, and saving + // it again on every star restart would blow the stack up exponentially. + // See the "alternatives" tests. + if next := i + 1; next < len(ms) { + d := len(x.frame.path) + if x.frame.depth >= d || (x.frame.depth == d-1 && x.kind == checkpointStack) { + x.push(x.branch(next)) + } + } + // The stars below this level may only discard the restart points born + // inside the same alternative; see [matchContext.storeStar]. + x.starsFloor = len(x.state.stars) + child := x.next(0) + n, match := ms[i].Match(child, s) + if debug.Enabled { + debug.Printf( + "[%T@%p] #%d match %#q against %[5]T(%[5]s) at path=%v depth=%d => %d %t\n", + ms, unsafe.SliceData(ms), i, s, ms[i], + child.frame.path, child.frame.depth, n, match, + ) + } + return n, match +} + +// textMatcher is a literal, "abc". +type textMatcher struct { + Text string +} + +func (m *textMatcher) String() string { + return strconv.Quote(m.Text) +} + +func (m *textMatcher) Match(_ matchContext, s string) (int, bool) { + if strings.HasPrefix(s, m.Text) { + return len(m.Text), true + } + return 0, false +} + +// charMatcher is a `?`: any single character but a separator. +type charMatcher struct { + Sep []rune +} + +func (m *charMatcher) String() string { + var sb strings.Builder + sb.WriteByte('?') + if len(m.Sep) > 0 { + sb.WriteByte('(') + formatRunes(&sb, m.Sep) + sb.WriteByte(')') + } + return sb.String() +} + +func (m *charMatcher) Match(_ matchContext, s string) (int, bool) { + if len(s) == 0 { + return 0, false + } + r, n := utf8.DecodeRuneInString(s) + if slices.Contains(m.Sep, r) { + return 0, false + } + return n, true +} + +// starMatcher is a `*` or a `**`: any sequence of characters, but for the +// separators in the former case. +type starMatcher struct { + // Sep are the separators the star may not extend over; empty for `**`. + Sep []rune + // SepStr is Sep as a string, for the byte-wise scans below. + SepStr string + + // Next is the literal the matcher right after this star begins with + // (when it is a textMatcher), set by [annotateStars]. A restart point + // at a position where the literal does not occur is a guaranteed + // mismatch, so the star jumps between its occurrences instead of + // retrying at every rune. + Next string + // Terminal is set by [annotateStars] when nothing follows this star + // anywhere in the pattern: the star then consumes everything in its + // reach at once, and no restart point can change the outcome. + Terminal bool +} + +// reach returns the length of the prefix of s the star may extend over: +// everything up to the nearest separator. +func (m *starMatcher) reach(s string) int { + if m.SepStr == "" { + return len(s) + } + if e := strings.IndexAny(s, m.SepStr); e >= 0 { + return e + } + return len(s) +} + +// storeSkip stores the restart point at the next occurrence of the m.Next +// literal instead of the next rune. +func (m *starMatcher) storeSkip(x matchContext, s string) { + reach := m.reach(s) + // Look for the occurrences starting within the star's reach; the + // literal itself may extend past it (it may contain the separators). + // Note that a valid UTF-8 literal can not match at a mid-rune + // position, so the one-byte skip below is rune-safe. + end := min(reach+len(m.Next), len(s)) + j := strings.Index(s[1:end], m.Next) + if j < 0 || 1+j > reach { + return + } + x.storeStar(1+j, len(m.Sep) == 0) +} + +func (m *starMatcher) String() string { + var sb strings.Builder + sb.WriteByte('*') + if len(m.Sep) > 0 { + sb.WriteByte('(') + formatRunes(&sb, m.Sep) + sb.WriteByte(')') + } + return sb.String() +} + +func (m *starMatcher) Match(x matchContext, s string) (int, bool) { + if m.Terminal { + // Nothing follows this star in the pattern: either it consumes + // the whole remainder within its reach, or the match fails. + return m.reach(s), true + } + if len(s) == 0 { + return 0, true + } + if m.Next != "" { + m.storeSkip(x, s) + return 0, true + } + r, n := utf8.DecodeRuneInString(s) + if !slices.Contains(m.Sep, r) { + // The star may extend over the rune: save the restart point past + // it. A separator-free star (`**`) can extend over anything the + // pending restart points could reach, so it may discard them; see + // [matchContext.storeStar]. + x.storeStar(n, len(m.Sep) == 0) + } + return 0, true +} + +// runeRangeMatcher is a character range class, `[a-z]` or `[!a-z]`. +type runeRangeMatcher struct { + Lo rune + Hi rune + Not bool +} + +func (m *runeRangeMatcher) String() string { + var sb strings.Builder + if m.Not { + sb.WriteByte('!') + } + sb.WriteByte('[') + sb.WriteRune(m.Lo) + sb.WriteByte('-') + sb.WriteRune(m.Hi) + sb.WriteByte(']') + return sb.String() +} + +func (m *runeRangeMatcher) Match(_ matchContext, s string) (int, bool) { + // Note that an invalid byte decodes as U+FFFD, and is matched as such, + // the same way regexp does; only the empty input is a mismatch. + r, n := utf8.DecodeRuneInString(s) + if n == 0 { + return 0, false + } + ok := m.Lo <= r && r <= m.Hi + if ok != m.Not { + return n, true + } + return 0, false +} + +// runeSetMatcher is a character set class, `[abc]` or `[!abc]`. +type runeSetMatcher struct { + Set map[rune]struct{} + Not bool +} + +// formatRunes writes rs, sorted and comma-separated, to sb. +func formatRunes(sb *strings.Builder, rs []rune) { + rs = slices.Clone(rs) // Not to reorder the caller's, e.g. a matcher's Sep. + slices.Sort(rs) + for i, r := range rs { + if i > 0 { + sb.WriteByte(',') + } + sb.WriteRune(r) + } +} + +func (m *runeSetMatcher) String() string { + rs := make([]rune, 0, len(m.Set)) + for r := range m.Set { + rs = append(rs, r) + } + var sb strings.Builder + if m.Not { + sb.WriteByte('!') + } + sb.WriteByte('[') + formatRunes(&sb, rs) + sb.WriteByte(']') + return sb.String() +} + +func (m *runeSetMatcher) Match(_ matchContext, s string) (int, bool) { + // See the note in runeRangeMatcher.Match. + r, n := utf8.DecodeRuneInString(s) + if n == 0 { + return 0, false + } + if _, has := m.Set[r]; has != m.Not { + return n, true + } + return 0, false +} + +// voidMatcher matches the empty string: an empty alternative, `{a,}`. In a +// sequence it is dropped by [normalizeSequence]. +type voidMatcher struct{} + +func (*voidMatcher) String() string { + return "void" +} + +func (*voidMatcher) Match(matchContext, string) (int, bool) { + return 0, true +} + +// The shaped matchers below are the compile-time rewrites of the common +// terminal sub-sequences; see [specialize]. Nothing may follow them in the +// pattern, so each one either consumes the whole remainder of the input or +// fails -- deterministically, storing no checkpoints. + +// prefixMatcher is a terminal `abc*`. +type prefixMatcher struct { + Text string + Sep string +} + +func (m *prefixMatcher) String() string { + return "prefix(" + strconv.Quote(m.Text) + ")" +} + +func (m *prefixMatcher) Match(_ matchContext, s string) (int, bool) { + if strings.HasPrefix(s, m.Text) && noSep(s[len(m.Text):], m.Sep) { + return len(s), true + } + return 0, false +} + +// suffixMatcher is a terminal `*abc`. +type suffixMatcher struct { + Text string + Sep string +} + +func (m *suffixMatcher) String() string { + return "suffix(" + strconv.Quote(m.Text) + ")" +} + +func (m *suffixMatcher) Match(_ matchContext, s string) (int, bool) { + if strings.HasSuffix(s, m.Text) && noSep(s[:len(s)-len(m.Text)], m.Sep) { + return len(s), true + } + return 0, false +} + +// prefixSuffixMatcher is a terminal `abc*def`. +type prefixSuffixMatcher struct { + Prefix string + Suffix string + Sep string +} + +func (m *prefixSuffixMatcher) String() string { + return "prefix_suffix(" + strconv.Quote(m.Prefix) + "," + strconv.Quote(m.Suffix) + ")" +} + +func (m *prefixSuffixMatcher) Match(_ matchContext, s string) (int, bool) { + // The length check keeps the prefix and the suffix from overlapping: + // `a*ant` must not match `ant`. + if len(s) >= len(m.Prefix)+len(m.Suffix) && + strings.HasPrefix(s, m.Prefix) && + strings.HasSuffix(s, m.Suffix) && + noSep(s[len(m.Prefix):len(s)-len(m.Suffix)], m.Sep) { + return len(s), true + } + return 0, false +} + +// containsMatcher is a terminal `*abc*` with the separator-free stars. +type containsMatcher struct { + Text string +} + +func (m *containsMatcher) String() string { + return "contains(" + strconv.Quote(m.Text) + ")" +} + +func (m *containsMatcher) Match(_ matchContext, s string) (int, bool) { + if strings.Contains(s, m.Text) { + return len(s), true + } + return 0, false +} + +// noSep reports whether s contains none of the separators. +func noSep(s, sep string) bool { + return sep == "" || !strings.ContainsAny(s, sep) +} + +// formatCheckpoint renders c as path@offset for the debug output. +func formatCheckpoint(c checkpoint) string { + return fmt.Sprintf("%v@%d", c.path, c.offset) +} + +// formatStack renders the checkpoint pile s for the debug output, the most +// recent one last. +func formatStack(s []checkpoint) string { + var sb strings.Builder + for i, c := range s { + if i > 0 { + sb.WriteString(" -> ") + } + sb.WriteString(formatCheckpoint(c)) + } + return sb.String() +} + +// popLast panics if s is empty. +func popLast[T any, E ~[]T](s *E) T { + n := len(*s) + r := (*s)[n-1] + *s = (*s)[:n-1] + return r +} diff --git a/vendor/github.com/gobwas/glob/match/any.go b/vendor/github.com/gobwas/glob/match/any.go deleted file mode 100644 index 514a9a5c45..0000000000 --- a/vendor/github.com/gobwas/glob/match/any.go +++ /dev/null @@ -1,45 +0,0 @@ -package match - -import ( - "fmt" - "github.com/gobwas/glob/util/strings" -) - -type Any struct { - Separators []rune -} - -func NewAny(s []rune) Any { - return Any{s} -} - -func (self Any) Match(s string) bool { - return strings.IndexAnyRunes(s, self.Separators) == -1 -} - -func (self Any) Index(s string) (int, []int) { - found := strings.IndexAnyRunes(s, self.Separators) - switch found { - case -1: - case 0: - return 0, segments0 - default: - s = s[:found] - } - - segments := acquireSegments(len(s)) - for i := range s { - segments = append(segments, i) - } - segments = append(segments, len(s)) - - return 0, segments -} - -func (self Any) Len() int { - return lenNo -} - -func (self Any) String() string { - return fmt.Sprintf("", string(self.Separators)) -} diff --git a/vendor/github.com/gobwas/glob/match/any_of.go b/vendor/github.com/gobwas/glob/match/any_of.go deleted file mode 100644 index 8e65356cdc..0000000000 --- a/vendor/github.com/gobwas/glob/match/any_of.go +++ /dev/null @@ -1,82 +0,0 @@ -package match - -import "fmt" - -type AnyOf struct { - Matchers Matchers -} - -func NewAnyOf(m ...Matcher) AnyOf { - return AnyOf{Matchers(m)} -} - -func (self *AnyOf) Add(m Matcher) error { - self.Matchers = append(self.Matchers, m) - return nil -} - -func (self AnyOf) Match(s string) bool { - for _, m := range self.Matchers { - if m.Match(s) { - return true - } - } - - return false -} - -func (self AnyOf) Index(s string) (int, []int) { - index := -1 - - segments := acquireSegments(len(s)) - for _, m := range self.Matchers { - idx, seg := m.Index(s) - if idx == -1 { - continue - } - - if index == -1 || idx < index { - index = idx - segments = append(segments[:0], seg...) - continue - } - - if idx > index { - continue - } - - // here idx == index - segments = appendMerge(segments, seg) - } - - if index == -1 { - releaseSegments(segments) - return -1, nil - } - - return index, segments -} - -func (self AnyOf) Len() (l int) { - l = -1 - for _, m := range self.Matchers { - ml := m.Len() - switch { - case l == -1: - l = ml - continue - - case ml == -1: - return -1 - - case l != ml: - return -1 - } - } - - return -} - -func (self AnyOf) String() string { - return fmt.Sprintf("", self.Matchers) -} diff --git a/vendor/github.com/gobwas/glob/match/btree.go b/vendor/github.com/gobwas/glob/match/btree.go deleted file mode 100644 index a8130e93ea..0000000000 --- a/vendor/github.com/gobwas/glob/match/btree.go +++ /dev/null @@ -1,146 +0,0 @@ -package match - -import ( - "fmt" - "unicode/utf8" -) - -type BTree struct { - Value Matcher - Left Matcher - Right Matcher - ValueLengthRunes int - LeftLengthRunes int - RightLengthRunes int - LengthRunes int -} - -func NewBTree(Value, Left, Right Matcher) (tree BTree) { - tree.Value = Value - tree.Left = Left - tree.Right = Right - - lenOk := true - if tree.ValueLengthRunes = Value.Len(); tree.ValueLengthRunes == -1 { - lenOk = false - } - - if Left != nil { - if tree.LeftLengthRunes = Left.Len(); tree.LeftLengthRunes == -1 { - lenOk = false - } - } - - if Right != nil { - if tree.RightLengthRunes = Right.Len(); tree.RightLengthRunes == -1 { - lenOk = false - } - } - - if lenOk { - tree.LengthRunes = tree.LeftLengthRunes + tree.ValueLengthRunes + tree.RightLengthRunes - } else { - tree.LengthRunes = -1 - } - - return tree -} - -func (self BTree) Len() int { - return self.LengthRunes -} - -// todo? -func (self BTree) Index(s string) (int, []int) { - return -1, nil -} - -func (self BTree) Match(s string) bool { - inputLen := len(s) - - // self.Length, self.RLen and self.LLen are values meaning the length of runes for each part - // here we manipulating byte length for better optimizations - // but these checks still works, cause minLen of 1-rune string is 1 byte. - if self.LengthRunes != -1 && self.LengthRunes > inputLen { - return false - } - - // try to cut unnecessary parts - // by knowledge of length of right and left part - var offset, limit int - if self.LeftLengthRunes >= 0 { - offset = self.LeftLengthRunes - } - if self.RightLengthRunes >= 0 { - limit = inputLen - self.RightLengthRunes - } else { - limit = inputLen - } - - for offset < limit { - // search for matching part in substring - index, segments := self.Value.Index(s[offset:limit]) - if index == -1 { - releaseSegments(segments) - return false - } - - l := s[:offset+index] - var left bool - if self.Left != nil { - left = self.Left.Match(l) - } else { - left = l == "" - } - - if left { - for i := len(segments) - 1; i >= 0; i-- { - length := segments[i] - - var right bool - var r string - // if there is no string for the right branch - if inputLen <= offset+index+length { - r = "" - } else { - r = s[offset+index+length:] - } - - if self.Right != nil { - right = self.Right.Match(r) - } else { - right = r == "" - } - - if right { - releaseSegments(segments) - return true - } - } - } - - _, step := utf8.DecodeRuneInString(s[offset+index:]) - offset += index + step - - releaseSegments(segments) - } - - return false -} - -func (self BTree) String() string { - const n string = "" - var l, r string - if self.Left == nil { - l = n - } else { - l = self.Left.String() - } - if self.Right == nil { - r = n - } else { - r = self.Right.String() - } - - return fmt.Sprintf("%s]>", l, self.Value, r) -} diff --git a/vendor/github.com/gobwas/glob/match/contains.go b/vendor/github.com/gobwas/glob/match/contains.go deleted file mode 100644 index 0998e95b0e..0000000000 --- a/vendor/github.com/gobwas/glob/match/contains.go +++ /dev/null @@ -1,58 +0,0 @@ -package match - -import ( - "fmt" - "strings" -) - -type Contains struct { - Needle string - Not bool -} - -func NewContains(needle string, not bool) Contains { - return Contains{needle, not} -} - -func (self Contains) Match(s string) bool { - return strings.Contains(s, self.Needle) != self.Not -} - -func (self Contains) Index(s string) (int, []int) { - var offset int - - idx := strings.Index(s, self.Needle) - - if !self.Not { - if idx == -1 { - return -1, nil - } - - offset = idx + len(self.Needle) - if len(s) <= offset { - return 0, []int{offset} - } - s = s[offset:] - } else if idx != -1 { - s = s[:idx] - } - - segments := acquireSegments(len(s) + 1) - for i := range s { - segments = append(segments, offset+i) - } - - return 0, append(segments, offset+len(s)) -} - -func (self Contains) Len() int { - return lenNo -} - -func (self Contains) String() string { - var not string - if self.Not { - not = "!" - } - return fmt.Sprintf("", not, self.Needle) -} diff --git a/vendor/github.com/gobwas/glob/match/every_of.go b/vendor/github.com/gobwas/glob/match/every_of.go deleted file mode 100644 index 7c968ee368..0000000000 --- a/vendor/github.com/gobwas/glob/match/every_of.go +++ /dev/null @@ -1,99 +0,0 @@ -package match - -import ( - "fmt" -) - -type EveryOf struct { - Matchers Matchers -} - -func NewEveryOf(m ...Matcher) EveryOf { - return EveryOf{Matchers(m)} -} - -func (self *EveryOf) Add(m Matcher) error { - self.Matchers = append(self.Matchers, m) - return nil -} - -func (self EveryOf) Len() (l int) { - for _, m := range self.Matchers { - if ml := m.Len(); l > 0 { - l += ml - } else { - return -1 - } - } - - return -} - -func (self EveryOf) Index(s string) (int, []int) { - var index int - var offset int - - // make `in` with cap as len(s), - // cause it is the maximum size of output segments values - next := acquireSegments(len(s)) - current := acquireSegments(len(s)) - - sub := s - for i, m := range self.Matchers { - idx, seg := m.Index(sub) - if idx == -1 { - releaseSegments(next) - releaseSegments(current) - return -1, nil - } - - if i == 0 { - // we use copy here instead of `current = seg` - // cause seg is a slice from reusable buffer `in` - // and it could be overwritten in next iteration - current = append(current, seg...) - } else { - // clear the next - next = next[:0] - - delta := index - (idx + offset) - for _, ex := range current { - for _, n := range seg { - if ex+delta == n { - next = append(next, n) - } - } - } - - if len(next) == 0 { - releaseSegments(next) - releaseSegments(current) - return -1, nil - } - - current = append(current[:0], next...) - } - - index = idx + offset - sub = s[index:] - offset += idx - } - - releaseSegments(next) - - return index, current -} - -func (self EveryOf) Match(s string) bool { - for _, m := range self.Matchers { - if !m.Match(s) { - return false - } - } - - return true -} - -func (self EveryOf) String() string { - return fmt.Sprintf("", self.Matchers) -} diff --git a/vendor/github.com/gobwas/glob/match/list.go b/vendor/github.com/gobwas/glob/match/list.go deleted file mode 100644 index 7fd763ecd8..0000000000 --- a/vendor/github.com/gobwas/glob/match/list.go +++ /dev/null @@ -1,49 +0,0 @@ -package match - -import ( - "fmt" - "github.com/gobwas/glob/util/runes" - "unicode/utf8" -) - -type List struct { - List []rune - Not bool -} - -func NewList(list []rune, not bool) List { - return List{list, not} -} - -func (self List) Match(s string) bool { - r, w := utf8.DecodeRuneInString(s) - if len(s) > w { - return false - } - - inList := runes.IndexRune(self.List, r) != -1 - return inList == !self.Not -} - -func (self List) Len() int { - return lenOne -} - -func (self List) Index(s string) (int, []int) { - for i, r := range s { - if self.Not == (runes.IndexRune(self.List, r) == -1) { - return i, segmentsByRuneLength[utf8.RuneLen(r)] - } - } - - return -1, nil -} - -func (self List) String() string { - var not string - if self.Not { - not = "!" - } - - return fmt.Sprintf("", not, string(self.List)) -} diff --git a/vendor/github.com/gobwas/glob/match/match.go b/vendor/github.com/gobwas/glob/match/match.go deleted file mode 100644 index f80e007fb8..0000000000 --- a/vendor/github.com/gobwas/glob/match/match.go +++ /dev/null @@ -1,81 +0,0 @@ -package match - -// todo common table of rune's length - -import ( - "fmt" - "strings" -) - -const lenOne = 1 -const lenZero = 0 -const lenNo = -1 - -type Matcher interface { - Match(string) bool - Index(string) (int, []int) - Len() int - String() string -} - -type Matchers []Matcher - -func (m Matchers) String() string { - var s []string - for _, matcher := range m { - s = append(s, fmt.Sprint(matcher)) - } - - return fmt.Sprintf("%s", strings.Join(s, ",")) -} - -// appendMerge merges and sorts given already SORTED and UNIQUE segments. -func appendMerge(target, sub []int) []int { - lt, ls := len(target), len(sub) - out := make([]int, 0, lt+ls) - - for x, y := 0, 0; x < lt || y < ls; { - if x >= lt { - out = append(out, sub[y:]...) - break - } - - if y >= ls { - out = append(out, target[x:]...) - break - } - - xValue := target[x] - yValue := sub[y] - - switch { - - case xValue == yValue: - out = append(out, xValue) - x++ - y++ - - case xValue < yValue: - out = append(out, xValue) - x++ - - case yValue < xValue: - out = append(out, yValue) - y++ - - } - } - - target = append(target[:0], out...) - - return target -} - -func reverseSegments(input []int) { - l := len(input) - m := l / 2 - - for i := 0; i < m; i++ { - input[i], input[l-i-1] = input[l-i-1], input[i] - } -} diff --git a/vendor/github.com/gobwas/glob/match/max.go b/vendor/github.com/gobwas/glob/match/max.go deleted file mode 100644 index d72f69efff..0000000000 --- a/vendor/github.com/gobwas/glob/match/max.go +++ /dev/null @@ -1,49 +0,0 @@ -package match - -import ( - "fmt" - "unicode/utf8" -) - -type Max struct { - Limit int -} - -func NewMax(l int) Max { - return Max{l} -} - -func (self Max) Match(s string) bool { - var l int - for range s { - l += 1 - if l > self.Limit { - return false - } - } - - return true -} - -func (self Max) Index(s string) (int, []int) { - segments := acquireSegments(self.Limit + 1) - segments = append(segments, 0) - var count int - for i, r := range s { - count++ - if count > self.Limit { - break - } - segments = append(segments, i+utf8.RuneLen(r)) - } - - return 0, segments -} - -func (self Max) Len() int { - return lenNo -} - -func (self Max) String() string { - return fmt.Sprintf("", self.Limit) -} diff --git a/vendor/github.com/gobwas/glob/match/min.go b/vendor/github.com/gobwas/glob/match/min.go deleted file mode 100644 index db57ac8eb4..0000000000 --- a/vendor/github.com/gobwas/glob/match/min.go +++ /dev/null @@ -1,57 +0,0 @@ -package match - -import ( - "fmt" - "unicode/utf8" -) - -type Min struct { - Limit int -} - -func NewMin(l int) Min { - return Min{l} -} - -func (self Min) Match(s string) bool { - var l int - for range s { - l += 1 - if l >= self.Limit { - return true - } - } - - return false -} - -func (self Min) Index(s string) (int, []int) { - var count int - - c := len(s) - self.Limit + 1 - if c <= 0 { - return -1, nil - } - - segments := acquireSegments(c) - for i, r := range s { - count++ - if count >= self.Limit { - segments = append(segments, i+utf8.RuneLen(r)) - } - } - - if len(segments) == 0 { - return -1, nil - } - - return 0, segments -} - -func (self Min) Len() int { - return lenNo -} - -func (self Min) String() string { - return fmt.Sprintf("", self.Limit) -} diff --git a/vendor/github.com/gobwas/glob/match/nothing.go b/vendor/github.com/gobwas/glob/match/nothing.go deleted file mode 100644 index 0d4ecd36b8..0000000000 --- a/vendor/github.com/gobwas/glob/match/nothing.go +++ /dev/null @@ -1,27 +0,0 @@ -package match - -import ( - "fmt" -) - -type Nothing struct{} - -func NewNothing() Nothing { - return Nothing{} -} - -func (self Nothing) Match(s string) bool { - return len(s) == 0 -} - -func (self Nothing) Index(s string) (int, []int) { - return 0, segments0 -} - -func (self Nothing) Len() int { - return lenZero -} - -func (self Nothing) String() string { - return fmt.Sprintf("") -} diff --git a/vendor/github.com/gobwas/glob/match/prefix.go b/vendor/github.com/gobwas/glob/match/prefix.go deleted file mode 100644 index a7347250e8..0000000000 --- a/vendor/github.com/gobwas/glob/match/prefix.go +++ /dev/null @@ -1,50 +0,0 @@ -package match - -import ( - "fmt" - "strings" - "unicode/utf8" -) - -type Prefix struct { - Prefix string -} - -func NewPrefix(p string) Prefix { - return Prefix{p} -} - -func (self Prefix) Index(s string) (int, []int) { - idx := strings.Index(s, self.Prefix) - if idx == -1 { - return -1, nil - } - - length := len(self.Prefix) - var sub string - if len(s) > idx+length { - sub = s[idx+length:] - } else { - sub = "" - } - - segments := acquireSegments(len(sub) + 1) - segments = append(segments, length) - for i, r := range sub { - segments = append(segments, length+i+utf8.RuneLen(r)) - } - - return idx, segments -} - -func (self Prefix) Len() int { - return lenNo -} - -func (self Prefix) Match(s string) bool { - return strings.HasPrefix(s, self.Prefix) -} - -func (self Prefix) String() string { - return fmt.Sprintf("", self.Prefix) -} diff --git a/vendor/github.com/gobwas/glob/match/prefix_any.go b/vendor/github.com/gobwas/glob/match/prefix_any.go deleted file mode 100644 index 8ee58fe1b3..0000000000 --- a/vendor/github.com/gobwas/glob/match/prefix_any.go +++ /dev/null @@ -1,55 +0,0 @@ -package match - -import ( - "fmt" - "strings" - "unicode/utf8" - - sutil "github.com/gobwas/glob/util/strings" -) - -type PrefixAny struct { - Prefix string - Separators []rune -} - -func NewPrefixAny(s string, sep []rune) PrefixAny { - return PrefixAny{s, sep} -} - -func (self PrefixAny) Index(s string) (int, []int) { - idx := strings.Index(s, self.Prefix) - if idx == -1 { - return -1, nil - } - - n := len(self.Prefix) - sub := s[idx+n:] - i := sutil.IndexAnyRunes(sub, self.Separators) - if i > -1 { - sub = sub[:i] - } - - seg := acquireSegments(len(sub) + 1) - seg = append(seg, n) - for i, r := range sub { - seg = append(seg, n+i+utf8.RuneLen(r)) - } - - return idx, seg -} - -func (self PrefixAny) Len() int { - return lenNo -} - -func (self PrefixAny) Match(s string) bool { - if !strings.HasPrefix(s, self.Prefix) { - return false - } - return sutil.IndexAnyRunes(s[len(self.Prefix):], self.Separators) == -1 -} - -func (self PrefixAny) String() string { - return fmt.Sprintf("", self.Prefix, string(self.Separators)) -} diff --git a/vendor/github.com/gobwas/glob/match/prefix_suffix.go b/vendor/github.com/gobwas/glob/match/prefix_suffix.go deleted file mode 100644 index 8208085a19..0000000000 --- a/vendor/github.com/gobwas/glob/match/prefix_suffix.go +++ /dev/null @@ -1,62 +0,0 @@ -package match - -import ( - "fmt" - "strings" -) - -type PrefixSuffix struct { - Prefix, Suffix string -} - -func NewPrefixSuffix(p, s string) PrefixSuffix { - return PrefixSuffix{p, s} -} - -func (self PrefixSuffix) Index(s string) (int, []int) { - prefixIdx := strings.Index(s, self.Prefix) - if prefixIdx == -1 { - return -1, nil - } - - suffixLen := len(self.Suffix) - if suffixLen <= 0 { - return prefixIdx, []int{len(s) - prefixIdx} - } - - if (len(s) - prefixIdx) <= 0 { - return -1, nil - } - - segments := acquireSegments(len(s) - prefixIdx) - for sub := s[prefixIdx:]; ; { - suffixIdx := strings.LastIndex(sub, self.Suffix) - if suffixIdx == -1 { - break - } - - segments = append(segments, suffixIdx+suffixLen) - sub = sub[:suffixIdx] - } - - if len(segments) == 0 { - releaseSegments(segments) - return -1, nil - } - - reverseSegments(segments) - - return prefixIdx, segments -} - -func (self PrefixSuffix) Len() int { - return lenNo -} - -func (self PrefixSuffix) Match(s string) bool { - return strings.HasPrefix(s, self.Prefix) && strings.HasSuffix(s, self.Suffix) -} - -func (self PrefixSuffix) String() string { - return fmt.Sprintf("", self.Prefix, self.Suffix) -} diff --git a/vendor/github.com/gobwas/glob/match/range.go b/vendor/github.com/gobwas/glob/match/range.go deleted file mode 100644 index ce30245a40..0000000000 --- a/vendor/github.com/gobwas/glob/match/range.go +++ /dev/null @@ -1,48 +0,0 @@ -package match - -import ( - "fmt" - "unicode/utf8" -) - -type Range struct { - Lo, Hi rune - Not bool -} - -func NewRange(lo, hi rune, not bool) Range { - return Range{lo, hi, not} -} - -func (self Range) Len() int { - return lenOne -} - -func (self Range) Match(s string) bool { - r, w := utf8.DecodeRuneInString(s) - if len(s) > w { - return false - } - - inRange := r >= self.Lo && r <= self.Hi - - return inRange == !self.Not -} - -func (self Range) Index(s string) (int, []int) { - for i, r := range s { - if self.Not != (r >= self.Lo && r <= self.Hi) { - return i, segmentsByRuneLength[utf8.RuneLen(r)] - } - } - - return -1, nil -} - -func (self Range) String() string { - var not string - if self.Not { - not = "!" - } - return fmt.Sprintf("", not, string(self.Lo), string(self.Hi)) -} diff --git a/vendor/github.com/gobwas/glob/match/row.go b/vendor/github.com/gobwas/glob/match/row.go deleted file mode 100644 index 4379042e42..0000000000 --- a/vendor/github.com/gobwas/glob/match/row.go +++ /dev/null @@ -1,77 +0,0 @@ -package match - -import ( - "fmt" -) - -type Row struct { - Matchers Matchers - RunesLength int - Segments []int -} - -func NewRow(len int, m ...Matcher) Row { - return Row{ - Matchers: Matchers(m), - RunesLength: len, - Segments: []int{len}, - } -} - -func (self Row) matchAll(s string) bool { - var idx int - for _, m := range self.Matchers { - length := m.Len() - - var next, i int - for next = range s[idx:] { - i++ - if i == length { - break - } - } - - if i < length || !m.Match(s[idx:idx+next+1]) { - return false - } - - idx += next + 1 - } - - return true -} - -func (self Row) lenOk(s string) bool { - var i int - for range s { - i++ - if i > self.RunesLength { - return false - } - } - return self.RunesLength == i -} - -func (self Row) Match(s string) bool { - return self.lenOk(s) && self.matchAll(s) -} - -func (self Row) Len() (l int) { - return self.RunesLength -} - -func (self Row) Index(s string) (int, []int) { - for i := range s { - if len(s[i:]) < self.RunesLength { - break - } - if self.matchAll(s[i:]) { - return i, self.Segments - } - } - return -1, nil -} - -func (self Row) String() string { - return fmt.Sprintf("", self.RunesLength, self.Matchers) -} diff --git a/vendor/github.com/gobwas/glob/match/segments.go b/vendor/github.com/gobwas/glob/match/segments.go deleted file mode 100644 index 9ea6f30943..0000000000 --- a/vendor/github.com/gobwas/glob/match/segments.go +++ /dev/null @@ -1,91 +0,0 @@ -package match - -import ( - "sync" -) - -type SomePool interface { - Get() []int - Put([]int) -} - -var segmentsPools [1024]sync.Pool - -func toPowerOfTwo(v int) int { - v-- - v |= v >> 1 - v |= v >> 2 - v |= v >> 4 - v |= v >> 8 - v |= v >> 16 - v++ - - return v -} - -const ( - cacheFrom = 16 - cacheToAndHigher = 1024 - cacheFromIndex = 15 - cacheToAndHigherIndex = 1023 -) - -var ( - segments0 = []int{0} - segments1 = []int{1} - segments2 = []int{2} - segments3 = []int{3} - segments4 = []int{4} -) - -var segmentsByRuneLength [5][]int = [5][]int{ - 0: segments0, - 1: segments1, - 2: segments2, - 3: segments3, - 4: segments4, -} - -func init() { - for i := cacheToAndHigher; i >= cacheFrom; i >>= 1 { - func(i int) { - segmentsPools[i-1] = sync.Pool{New: func() interface{} { - return make([]int, 0, i) - }} - }(i) - } -} - -func getTableIndex(c int) int { - p := toPowerOfTwo(c) - switch { - case p >= cacheToAndHigher: - return cacheToAndHigherIndex - case p <= cacheFrom: - return cacheFromIndex - default: - return p - 1 - } -} - -func acquireSegments(c int) []int { - // make []int with less capacity than cacheFrom - // is faster than acquiring it from pool - if c < cacheFrom { - return make([]int, 0, c) - } - - return segmentsPools[getTableIndex(c)].Get().([]int)[:0] -} - -func releaseSegments(s []int) { - c := cap(s) - - // make []int with less capacity than cacheFrom - // is faster than acquiring it from pool - if c < cacheFrom { - return - } - - segmentsPools[getTableIndex(c)].Put(s) -} diff --git a/vendor/github.com/gobwas/glob/match/single.go b/vendor/github.com/gobwas/glob/match/single.go deleted file mode 100644 index ee6e3954c1..0000000000 --- a/vendor/github.com/gobwas/glob/match/single.go +++ /dev/null @@ -1,43 +0,0 @@ -package match - -import ( - "fmt" - "github.com/gobwas/glob/util/runes" - "unicode/utf8" -) - -// single represents ? -type Single struct { - Separators []rune -} - -func NewSingle(s []rune) Single { - return Single{s} -} - -func (self Single) Match(s string) bool { - r, w := utf8.DecodeRuneInString(s) - if len(s) > w { - return false - } - - return runes.IndexRune(self.Separators, r) == -1 -} - -func (self Single) Len() int { - return lenOne -} - -func (self Single) Index(s string) (int, []int) { - for i, r := range s { - if runes.IndexRune(self.Separators, r) == -1 { - return i, segmentsByRuneLength[utf8.RuneLen(r)] - } - } - - return -1, nil -} - -func (self Single) String() string { - return fmt.Sprintf("", string(self.Separators)) -} diff --git a/vendor/github.com/gobwas/glob/match/suffix.go b/vendor/github.com/gobwas/glob/match/suffix.go deleted file mode 100644 index 85bea8c68e..0000000000 --- a/vendor/github.com/gobwas/glob/match/suffix.go +++ /dev/null @@ -1,35 +0,0 @@ -package match - -import ( - "fmt" - "strings" -) - -type Suffix struct { - Suffix string -} - -func NewSuffix(s string) Suffix { - return Suffix{s} -} - -func (self Suffix) Len() int { - return lenNo -} - -func (self Suffix) Match(s string) bool { - return strings.HasSuffix(s, self.Suffix) -} - -func (self Suffix) Index(s string) (int, []int) { - idx := strings.Index(s, self.Suffix) - if idx == -1 { - return -1, nil - } - - return 0, []int{idx + len(self.Suffix)} -} - -func (self Suffix) String() string { - return fmt.Sprintf("", self.Suffix) -} diff --git a/vendor/github.com/gobwas/glob/match/suffix_any.go b/vendor/github.com/gobwas/glob/match/suffix_any.go deleted file mode 100644 index c5106f8196..0000000000 --- a/vendor/github.com/gobwas/glob/match/suffix_any.go +++ /dev/null @@ -1,43 +0,0 @@ -package match - -import ( - "fmt" - "strings" - - sutil "github.com/gobwas/glob/util/strings" -) - -type SuffixAny struct { - Suffix string - Separators []rune -} - -func NewSuffixAny(s string, sep []rune) SuffixAny { - return SuffixAny{s, sep} -} - -func (self SuffixAny) Index(s string) (int, []int) { - idx := strings.Index(s, self.Suffix) - if idx == -1 { - return -1, nil - } - - i := sutil.LastIndexAnyRunes(s[:idx], self.Separators) + 1 - - return i, []int{idx + len(self.Suffix) - i} -} - -func (self SuffixAny) Len() int { - return lenNo -} - -func (self SuffixAny) Match(s string) bool { - if !strings.HasSuffix(s, self.Suffix) { - return false - } - return sutil.IndexAnyRunes(s[:len(s)-len(self.Suffix)], self.Separators) == -1 -} - -func (self SuffixAny) String() string { - return fmt.Sprintf("", string(self.Separators), self.Suffix) -} diff --git a/vendor/github.com/gobwas/glob/match/super.go b/vendor/github.com/gobwas/glob/match/super.go deleted file mode 100644 index 3875950bb8..0000000000 --- a/vendor/github.com/gobwas/glob/match/super.go +++ /dev/null @@ -1,33 +0,0 @@ -package match - -import ( - "fmt" -) - -type Super struct{} - -func NewSuper() Super { - return Super{} -} - -func (self Super) Match(s string) bool { - return true -} - -func (self Super) Len() int { - return lenNo -} - -func (self Super) Index(s string) (int, []int) { - segments := acquireSegments(len(s) + 1) - for i := range s { - segments = append(segments, i) - } - segments = append(segments, len(s)) - - return 0, segments -} - -func (self Super) String() string { - return fmt.Sprintf("") -} diff --git a/vendor/github.com/gobwas/glob/match/text.go b/vendor/github.com/gobwas/glob/match/text.go deleted file mode 100644 index 0a17616d3c..0000000000 --- a/vendor/github.com/gobwas/glob/match/text.go +++ /dev/null @@ -1,45 +0,0 @@ -package match - -import ( - "fmt" - "strings" - "unicode/utf8" -) - -// raw represents raw string to match -type Text struct { - Str string - RunesLength int - BytesLength int - Segments []int -} - -func NewText(s string) Text { - return Text{ - Str: s, - RunesLength: utf8.RuneCountInString(s), - BytesLength: len(s), - Segments: []int{len(s)}, - } -} - -func (self Text) Match(s string) bool { - return self.Str == s -} - -func (self Text) Len() int { - return self.RunesLength -} - -func (self Text) Index(s string) (int, []int) { - index := strings.Index(s, self.Str) - if index == -1 { - return -1, nil - } - - return index, self.Segments -} - -func (self Text) String() string { - return fmt.Sprintf("", self.Str) -} diff --git a/vendor/github.com/gobwas/glob/parse.go b/vendor/github.com/gobwas/glob/parse.go new file mode 100644 index 0000000000..30114dfcf6 --- /dev/null +++ b/vendor/github.com/gobwas/glob/parse.go @@ -0,0 +1,679 @@ +package glob + +import ( + "slices" + "unicode/utf8" + + "github.com/gobwas/glob/internal/debug" + "github.com/gobwas/glob/syntax" +) + +// compile parses the pattern into a matcher tree (see below), simplifies and +// specializes it, and computes the match-time hints and preconditions; see +// [simplify], [specialize], [annotateStars], [needsState], [minLength] and +// [requiredSuffix]. It is what [Compile] wraps. +func compile(str string, sep []rune) (*Pattern, error) { + if debug.Enabled { + debug.Printf("compiling %#q\n", str) + } + // The matchers keep sep and read it while matching, and the variadic slice + // may alias an array owned by the caller: give them a copy of their own. + // + // The pattern itself keeps the slice as given, to return it from + // Separators() without cloning. + var ( + sepCopy = slices.Clone(sep) + sepStr = string(sep) + ) + + type operator struct { + kind int + index int + } + const ( + opTerms = iota + opList + ) + /* + Stack-based parsing is a technique used to evaluate mathematical + expressions by leveraging the properties of the LIFO (Last-In, + First-Out) data structure, the stack. It involves using two stacks: one + for operands (numbers) and one for operators. By processing the + expression from left to right and strategically pushing and popping + elements from the stacks, the expression can be effectively evaluated. + + https://cp-algorithms.com/string/expression_parsing.html + + Here the operands are matchers and the only operators are the braces + and the commas inside them, so it goes as follows: + + - a leaf token (text, `?`, `*`, `**`, `[...]`) pushes its matcher + onto the stack; + + - `{` pushes two operators, both remembering the current stack + length: opTerms marks where the alternatives of the group will + be collected, opList marks where the terms of the current + alternative begin; + + - `,` pops the opList, collapses the terms above its index into a + single multiMatcher (or a voidMatcher when there are none, as in + `{,a}`), and pushes a fresh opList for the next alternative; + + - `}` pops the opList and collapses the last alternative the same + way, then pops the opTerms and collapses everything above its + index -- one matcher per alternative by now -- into an + altMatcher; + + - at the EOF whatever is left on the stack is the top-level + sequence; a leftover operator means an unclosed `{`. + + For example, `a{b*,c}d` goes like this (list@i is an opList with + index i, likewise terms@i): + + token stack operators + a "a" + { "a" terms@1 list@1 + b "a" "b" terms@1 list@1 + * "a" "b" * terms@1 list@1 + , "a" ["b"·*] terms@1 list@2 + c "a" ["b"·*] "c" terms@1 list@2 + } "a" ["b"·*] ["c"] terms@1 + "a" {["b"·*]|["c"]} + d "a" {["b"·*]|["c"]} "d" + EOF ["a"·{["b"·*]|["c"]}·"d"] + + The result is then simplified (["c"] becomes "c") and specialized; + see [simplify] and [specialize]. + */ + var ( + stack []matcher + operators []operator + ) + lex := syntax.NewLexer(str) +parsing: + for { + token := lex.Next() + if debug.Enabled { + debug.Printf("token: %s\n", token) + } + switch token.Type { + case syntax.EOF: + break parsing + + case syntax.Error: + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: token.Data, + } + + case syntax.Single: + stack = append(stack, &charMatcher{ + Sep: sepCopy, + }) + + case syntax.Text: + stack = append(stack, &textMatcher{ + Text: token.Data, + }) + + case syntax.RangeOpen: + m, err := parseRange(lex) + if err != nil { + return nil, err + } + stack = append(stack, m) + + case syntax.Any: + stack = append(stack, &starMatcher{ + Sep: sepCopy, + SepStr: sepStr, + }) + + case syntax.Super: + stack = append(stack, &starMatcher{ + Sep: nil, + }) + + case syntax.TermsOpen: + // Note that the `{` opens both the group and its first + // alternative: every alternative is delimited by an opList + // operator. This way TermsClose always collapses the trailing + // alternative into a single matcher first, even when the group + // has no commas at all, e.g. `{ab*}`. + operators = append(operators, + operator{kind: opTerms, index: len(stack)}, + operator{kind: opList, index: len(stack)}, + ) + if debug.Enabled { + debug.Printf("terms enter: %d\n", len(stack)) + } + + case syntax.TermSeparator: + k := len(operators) - 1 + if k < 0 { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unexpected `,`", + } + } + x := operators[k] + if x.kind == opList { + // Remove the most recent "comma" operator. + // Note that the previous one is the terms operator. + operators = operators[:k] + } + i := x.index + // Handle the `{,a}` case. + if i == len(stack) { + // Empty matchers. + stack = append(stack, &voidMatcher{}) + } else { + stack[i] = multiMatcher(slices.Clone(stack[i:])) + stack = stack[:i+1] + if debug.Enabled { + debug.Printf("terms next: %d: %s\n", i, stack[i]) + } + } + operators = append(operators, operator{ + kind: opList, + index: len(stack), + }) + if debug.Enabled { + debug.Printf("terms separator: %d\n", len(stack)) + } + + case syntax.TermsClose: + for { + k := len(operators) - 1 + if k < 0 { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unexpected `}`", + } + } + x := operators[k] + operators = operators[:k] + + i := x.index + c := slices.Clone(stack[i:]) + var m matcher + switch x.kind { + case opTerms: + m = altMatcher(c) + case opList: + m = multiMatcher(c) + } + // Handle the `{a,}` case. + if i == len(stack) { + stack = append(stack, m) + } else { + stack = stack[:i+1] + stack[i] = m + } + + if debug.Enabled { + debug.Printf( + "terms leave(%d): %d: %s\n", + x.kind, i, stack[i], + ) + } + if x.kind == opTerms { + break + } + } + + default: + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unexpected token " + token.String(), + } + } + } + if len(operators) != 0 { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unclosed `{`", + } + } + m := simplify(multiMatcher(stack)) + m = specialize(m, true) + annotateStars(m, true) + if debug.Enabled { + debug.Printf("compiled %#q: %s\n", str, m) + } + p := &Pattern{ + str: str, + sep: sep, + m: m, + state: needsState(m), + } + if p.state { + p.minLen = minLength(m) + p.suffix = requiredSuffix(m) + } + return p, nil +} + +// parseRange parses a character class, called right after its opening `[` +// was read; it consumes the tokens up to and including the closing `]`. The +// class is either a range, `[a-c]`, or a set, `[abc]`, either possibly +// negated with a leading `!`; see [runeRangeMatcher] and [runeSetMatcher]. +func parseRange(lex *syntax.Lexer) (matcher, error) { + // -1 marks a range boundary as unset: any decoded rune, including + // U+0000, is non-negative. + var ( + not bool + lo, hi rune = -1, -1 + chars map[rune]struct{} + ) + for { + token := lex.Next() + switch token.Type { + case syntax.EOF: + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unclosed `[`", + } + + case syntax.Error: + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: token.Data, + } + + case syntax.Not: + not = true + + case syntax.RangeLo: + r, w := utf8.DecodeRuneInString(token.Data) + if len(token.Data) > w { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unexpected length of range lo character", + } + } + lo = r + + case syntax.RangeBetween: + // The `-` between lo and hi: nothing to do. + + case syntax.RangeHi: + r, w := utf8.DecodeRuneInString(token.Data) + if len(token.Data) > w { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unexpected length of range hi character", + } + } + hi = r + + if hi < lo { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "range hi character is less than lo", + } + } + + case syntax.Text: + chars = make(map[rune]struct{}) + for _, r := range token.Data { + chars[r] = struct{}{} + } + + case syntax.RangeClose: + isRange := lo >= 0 && hi >= 0 + isChars := chars != nil + + if isChars == isRange { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "could not parse range", + } + } + if isRange { + return &runeRangeMatcher{ + Lo: lo, + Hi: hi, + Not: not, + }, nil + } + return &runeSetMatcher{ + Set: chars, + Not: not, + }, nil + } + } +} + +// simplify rewrites the freshly parsed tree into its canonical shape, bottom +// up: the sequences are normalized (see [normalizeSequence]), and a sequence +// or a group of alternatives with a single child is replaced by the child, +// with none -- by a void. +func simplify(m matcher) matcher { + var ( + ms []matcher + isMulti bool + ) + switch v := m.(type) { + case multiMatcher: + ms, isMulti = v, true + case altMatcher: + ms = v + default: + return m + } + for i, m := range ms { + ms[i] = simplify(m) + } + if isMulti { + ms = normalizeSequence(ms) + } + switch len(ms) { + case 0: + return &voidMatcher{} + case 1: + return ms[0] + } + if isMulti { + return multiMatcher(ms) + } + return altMatcher(ms) +} + +// normalizeSequence rewrites a sequence of (already simplified) matchers +// into a simpler equivalent one: +// +// ["a"·["b"·"c"]·"d"] => ["a"·"b"·"c"·"d"] inline the nested sequences +// ["a"·void] => ["a"] drop the void matchers +// ["a"·"b"] => ["ab"] merge the adjacent literals +// [*·**] => [**] coalesce the adjacent stars +// +// Longer literals also make better star jumps; see [annotateStars]. +func normalizeSequence(ms []matcher) []matcher { + if !needsNormalize(ms) { + // The common case: nothing to rewrite, no copy needed. + return ms + } + out := make([]matcher, 0, len(ms)) + var push func(m matcher) + push = func(m matcher) { + switch v := m.(type) { + case multiMatcher: + for _, c := range v { + push(c) + } + return + case *voidMatcher: + return + case *textMatcher: + if len(out) > 0 { + if prev, ok := out[len(out)-1].(*textMatcher); ok { + out[len(out)-1] = &textMatcher{Text: prev.Text + v.Text} + return + } + } + case *starMatcher: + if len(out) > 0 { + if prev, ok := out[len(out)-1].(*starMatcher); ok { + // Adjacent stars are equivalent to the most general + // of them: the one not limited by separators, if any. + if len(prev.Sep) > 0 && len(v.Sep) == 0 { + out[len(out)-1] = v + } + return + } + } + } + out = append(out, m) + } + for _, m := range ms { + push(m) + } + return out +} + +// needsNormalize reports whether [normalizeSequence] would change ms, so +// that the common case skips the copy. +func needsNormalize(ms []matcher) bool { + for i, m := range ms { + switch m.(type) { + case multiMatcher, *voidMatcher: + return true + case *textMatcher: + if i > 0 { + if _, ok := ms[i-1].(*textMatcher); ok { + return true + } + } + case *starMatcher: + if i > 0 { + if _, ok := ms[i-1].(*starMatcher); ok { + return true + } + } + } + } + return false +} + +// specialize rewrites the terminal sub-sequences of the simplified matcher +// tree into the shaped matchers -- [prefixMatcher], [suffixMatcher], +// [prefixSuffixMatcher] and [containsMatcher]; see [foldTail] for the +// rewrites. The tail flag tells whether nothing follows m in the pattern; +// only there the rewrites apply, since a shaped matcher consumes the whole +// remainder of the input. +func specialize(m matcher, tail bool) matcher { + switch v := m.(type) { + case altMatcher: + // Every alternative ends where the alt ends. + for i, c := range v { + v[i] = specialize(c, tail) + } + return v + + case multiMatcher: + for i, c := range v { + v[i] = specialize(c, tail && i == len(v)-1) + } + if !tail { + return v + } + ms := foldTail([]matcher(v)) + if len(ms) == 1 { + return ms[0] + } + return multiMatcher(ms) + } + return m +} + +// foldTail repeatedly folds the two trailing matchers of the terminal +// sequence ms into a shaped one, while possible: +// +// [..·"abc"·*] => [..·prefix("abc")] +// [..·*·"abc"] => [..·suffix("abc")] +// [..·"abc"·prefix("def")] => [..·prefix("abcdef")] +// [..·*·prefix("abc")] => [..·contains("abc")] (separator-free) +// [..·"abc"·suffix("def")] => [..·prefix_suffix("abc","def")] +// [..·*·contains("abc")] => [..·contains("abc")] (separator-free) +func foldTail(ms []matcher) []matcher { + for len(ms) >= 2 { + var ( + prev = ms[len(ms)-2] + folded matcher + ) + switch last := ms[len(ms)-1].(type) { + case *starMatcher: + if t, ok := prev.(*textMatcher); ok { + folded = &prefixMatcher{Text: t.Text, Sep: last.SepStr} + } + + case *textMatcher: + if star, ok := prev.(*starMatcher); ok { + folded = &suffixMatcher{Text: last.Text, Sep: star.SepStr} + } + + case *prefixMatcher: + switch p := prev.(type) { + case *textMatcher: + folded = &prefixMatcher{Text: p.Text + last.Text, Sep: last.Sep} + case *starMatcher: + if p.SepStr == "" && last.Sep == "" { + folded = &containsMatcher{Text: last.Text} + } + } + + case *suffixMatcher: + if t, ok := prev.(*textMatcher); ok { + folded = &prefixSuffixMatcher{ + Prefix: t.Text, + Suffix: last.Text, + Sep: last.Sep, + } + } + + case *containsMatcher: + if star, ok := prev.(*starMatcher); ok && star.SepStr == "" { + folded = last + } + } + if folded == nil { + break + } + ms = ms[:len(ms)-1] + ms[len(ms)-1] = folded + } + return ms +} + +// annotateStars computes the compile-time hints for the star matchers, in +// order to keep the number of restart points they store at match time low: +// +// - a star directly followed by a literal jumps between the literal +// occurrences instead of retrying at every rune (see +// [starMatcher.storeSkip]); +// +// - a star with nothing after it anywhere in the pattern (tail is true +// for m and the star closes it) consumes its whole reach at once and +// stores no restart points at all. +func annotateStars(m matcher, tail bool) { + switch v := m.(type) { + case multiMatcher: + for i, c := range v { + last := i == len(v)-1 + star, ok := c.(*starMatcher) + if !ok { + annotateStars(c, tail && last) + continue + } + star.Terminal = tail && last + if !last { + star.Next = leadingLiteral(v[i+1]) + } + } + case altMatcher: + for _, c := range v { + annotateStars(c, tail) + } + case *starMatcher: + v.Terminal = tail + } +} + +// leadingLiteral returns the literal the given matcher is guaranteed to +// begin its match with, if any. +func leadingLiteral(m matcher) string { + switch v := m.(type) { + case *textMatcher: + return v.Text + case *prefixMatcher: + return v.Text + case *prefixSuffixMatcher: + return v.Prefix + } + return "" +} + +// minLength returns the minimum length in bytes of a string m can match. +func minLength(m matcher) (n int) { + switch v := m.(type) { + case *textMatcher: + return len(v.Text) + case *charMatcher, *runeRangeMatcher, *runeSetMatcher: + return 1 + case *prefixMatcher: + return len(v.Text) + case *suffixMatcher: + return len(v.Text) + case *prefixSuffixMatcher: + return len(v.Prefix) + len(v.Suffix) + case *containsMatcher: + return len(v.Text) + case multiMatcher: + for _, c := range v { + n += minLength(c) + } + return n + case altMatcher: + n = minLength(v[0]) + for _, c := range v[1:] { + n = min(n, minLength(c)) + } + return n + } + return 0 // A star or a void. +} + +// requiredSuffix returns the literal every string m matches must end with. +func requiredSuffix(m matcher) string { + switch v := m.(type) { + case *textMatcher: + return v.Text + case *suffixMatcher: + return v.Text + case *prefixSuffixMatcher: + return v.Suffix + case multiMatcher: + return requiredSuffix(v[len(v)-1]) + case altMatcher: + s := requiredSuffix(v[0]) + for _, c := range v[1:] { + s = commonSuffix(s, requiredSuffix(c)) + if s == "" { + break + } + } + return s + } + return "" // A star, a single-character matcher or a void. +} + +// commonSuffix returns the longest common suffix of a and b, never splitting +// a multi-byte rune. +func commonSuffix(a, b string) string { + i := 0 + for i < len(a) && i < len(b) { + ra, wa := utf8.DecodeLastRuneInString(a[:len(a)-i]) + rb, wb := utf8.DecodeLastRuneInString(b[:len(b)-i]) + if ra != rb || wa != wb { + break + } + i += wa + } + return a[len(a)-i:] +} + +// needsState reports whether matching m may save a checkpoint. Only the +// alts and the non-terminal stars do; a pattern without them is matched +// with a plain call chain -- see [Pattern.Match]. +func needsState(m matcher) bool { + switch v := m.(type) { + case altMatcher: + return true + case multiMatcher: + return slices.ContainsFunc(v, needsState) + case *starMatcher: + return !v.Terminal + } + return false +} diff --git a/vendor/github.com/gobwas/glob/readme.md b/vendor/github.com/gobwas/glob/readme.md index f58144e733..9f692571f1 100644 --- a/vendor/github.com/gobwas/glob/readme.md +++ b/vendor/github.com/gobwas/glob/readme.md @@ -1,6 +1,6 @@ # glob.[go](https://golang.org) -[![GoDoc][godoc-image]][godoc-url] [![Build Status][travis-image]][travis-url] +[![GoDoc][godoc-image]][godoc-url] [![CI][ci-image]][ci-url] > Go Globbing Library. @@ -19,130 +19,225 @@ package main import "github.com/gobwas/glob" func main() { - var g glob.Glob - + var g *glob.Pattern + // create simple glob g = glob.MustCompile("*.github.com") g.Match("api.github.com") // true - - // quote meta characters and then create simple glob + + // quote meta characters and then create simple glob g = glob.MustCompile(glob.QuoteMeta("*.github.com")) g.Match("*.github.com") // true - + // create new glob with set of delimiters as ["."] g = glob.MustCompile("api.*.com", '.') g.Match("api.github.com") // true g.Match("api.gi.hub.com") // false - + // create new glob with set of delimiters as ["."] // but now with super wildcard g = glob.MustCompile("api.**.com", '.') g.Match("api.github.com") // true g.Match("api.gi.hub.com") // true - + // create glob with single symbol wildcard g = glob.MustCompile("?at") g.Match("cat") // true g.Match("fat") // true g.Match("at") // false - + // create glob with single symbol wildcard and delimiters ['f'] g = glob.MustCompile("?at", 'f') g.Match("cat") // true g.Match("fat") // false - g.Match("at") // false - - // create glob with character-list matchers + g.Match("at") // false + + // create glob with character-list matchers g = glob.MustCompile("[abc]at") g.Match("cat") // true g.Match("bat") // true g.Match("fat") // false g.Match("at") // false - - // create glob with character-list matchers + + // create glob with character-list matchers g = glob.MustCompile("[!abc]at") g.Match("cat") // false g.Match("bat") // false g.Match("fat") // true - g.Match("at") // false - - // create glob with character-range matchers + g.Match("at") // false + + // create glob with character-range matchers g = glob.MustCompile("[a-c]at") g.Match("cat") // true g.Match("bat") // true g.Match("fat") // false g.Match("at") // false - - // create glob with character-range matchers + + // create glob with character-range matchers g = glob.MustCompile("[!a-c]at") g.Match("cat") // false g.Match("bat") // false g.Match("fat") // true - g.Match("at") // false - - // create glob with pattern-alternatives list + g.Match("at") // false + + // create glob with pattern-alternatives list g = glob.MustCompile("{cat,bat,[fr]at}") g.Match("cat") // true g.Match("bat") // true g.Match("fat") // true g.Match("rat") // true - g.Match("at") // false - g.Match("zat") // false + g.Match("at") // false + g.Match("zat") // false } ``` -## Performance +`Compile` reports malformed patterns with a `*glob.SyntaxError` carrying the +byte offset and the reason: -This library is created for compile-once patterns. This means, that compilation could take time, but -strings matching is done faster, than in case when always parsing template. +```go +_, err := glob.Compile("{a,b") +// err: glob: syntax error at 4: unclosed `{` +``` -If you will not use compiled `glob.Glob` object, and do `g := glob.MustCompile(pattern); g.Match(...)` every time, then your code will be much more slower. +A compiled `Pattern` captures what it was compiled from, so it can be passed +around instead of the raw arguments and inspected when needed (`String()` makes +it a `fmt.Stringer`, like `regexp.Regexp`): -Run `go test -bench=.` from source root to see the benchmarks: - -Pattern | Fixture | Match | Speed (ns/op) ---------|---------|-------|-------------- -`[a-z][!a-x]*cat*[h][!b]*eyes*` | `my cat has very bright eyes` | `true` | 432 -`[a-z][!a-x]*cat*[h][!b]*eyes*` | `my dog has very bright eyes` | `false` | 199 -`https://*.google.*` | `https://account.google.com` | `true` | 96 -`https://*.google.*` | `https://google.com` | `false` | 66 -`{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}` | `http://yahoo.com` | `true` | 163 -`{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}` | `http://google.com` | `false` | 197 -`{https://*gobwas.com,http://exclude.gobwas.com}` | `https://safe.gobwas.com` | `true` | 22 -`{https://*gobwas.com,http://exclude.gobwas.com}` | `http://safe.gobwas.com` | `false` | 24 -`abc*` | `abcdef` | `true` | 8.15 -`abc*` | `af` | `false` | 5.68 -`*def` | `abcdef` | `true` | 8.84 -`*def` | `af` | `false` | 5.74 -`ab*ef` | `abcdef` | `true` | 15.2 -`ab*ef` | `af` | `false` | 10.4 - -The same things with `regexp` package: - -Pattern | Fixture | Match | Speed (ns/op) ---------|---------|-------|-------------- -`^[a-z][^a-x].*cat.*[h][^b].*eyes.*$` | `my cat has very bright eyes` | `true` | 2553 -`^[a-z][^a-x].*cat.*[h][^b].*eyes.*$` | `my dog has very bright eyes` | `false` | 1383 -`^https:\/\/.*\.google\..*$` | `https://account.google.com` | `true` | 1205 -`^https:\/\/.*\.google\..*$` | `https://google.com` | `false` | 767 -`^(https:\/\/.*\.google\..*|.*yandex\..*|.*yahoo\..*|.*mail\.ru)$` | `http://yahoo.com` | `true` | 1435 -`^(https:\/\/.*\.google\..*|.*yandex\..*|.*yahoo\..*|.*mail\.ru)$` | `http://google.com` | `false` | 1674 -`^(https:\/\/.*gobwas\.com|http://exclude.gobwas.com)$` | `https://safe.gobwas.com` | `true` | 1039 -`^(https:\/\/.*gobwas\.com|http://exclude.gobwas.com)$` | `http://safe.gobwas.com` | `false` | 272 -`^abc.*$` | `abcdef` | `true` | 237 -`^abc.*$` | `af` | `false` | 100 -`^.*def$` | `abcdef` | `true` | 464 -`^.*def$` | `af` | `false` | 265 -`^ab.*ef$` | `abcdef` | `true` | 375 -`^ab.*ef$` | `af` | `false` | 145 - -[godoc-image]: https://godoc.org/github.com/gobwas/glob?status.svg -[godoc-url]: https://godoc.org/github.com/gobwas/glob -[travis-image]: https://travis-ci.org/gobwas/glob.svg?branch=master -[travis-url]: https://travis-ci.org/gobwas/glob +```go +g := glob.MustCompile("*.github.com", '.') +g.String() // "*.github.com" +g.Separators() // []rune{'.'} +``` ## Syntax Syntax is inspired by [standard wildcards](http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm), -except that `**` is aka super-asterisk, that do not sensitive for separators. \ No newline at end of file +with one addition: `**` (the "super-asterisk"), which matches any sequence +of characters *including* the separators, where `*` stops at them. Note that +it is just that -- a `*` that crosses separators -- and not the `**/` +"globstar" of shells and file globbers: `**/x` requires the literal `/`, so +it does not match `x`; use `{**/,}x` for that. The same applies to a +`**` between separators, e.g. `a/**/b` does not match `a/b`. + +``` +pattern: + { term } + +term: + `*` matches any sequence of non-separator characters + `**` matches any sequence of characters + `?` matches any single non-separator character + `[` [ `!` ] class `]` + character class; `!` negates it + `{` pattern-list `}` + pattern alternatives + c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`) + `\` c matches character c + +class: + lo `-` hi matches character c for lo <= c <= hi + { c } matches any of the listed characters (c != `\`, `]`; + `\` c matches c, `-` is literal here); must be non-empty + +pattern-list: + pattern { `,` pattern } + comma-separated (without spaces) patterns +``` + +### Escaping + +The backslash is the escape character: `\*` is a literal asterisk, and a +backslash itself is `\\`. Mind the Go string literals: `"foo\\bar"` is the +pattern `foo\bar`, which is the literal `foobar`, not `foo\bar`. To match a +backslash (e.g. in the Windows paths) write `"foo\\\\bar"` or `` `foo\\bar` ``, +or use `QuoteMeta` on the literal part. + +### Separators + +The separators are not part of the pattern syntax -- they are configured +once, at compilation time, as the extra arguments of `Compile`: + +```go +g := glob.MustCompile("api.*.com", '.', '/') +``` + +They only limit the wildcards: `*` and `?` never match a separator, while +`**` matches across them; the literals and the character classes are not +affected. With no separators given, `*` and `**` are equivalent. A compiled +`*glob.Pattern` keeps its separators for all matches -- to match the same +pattern with different separators, compile it again. + +## Performance + +This library is created for compile-once patterns. This means, that +compilation could take time, but strings matching is done faster, than in +case when always parsing template. + +If you will not use compiled `*glob.Pattern` object, and do +`g := glob.MustCompile(pattern); g.Match(...)` every time, then your code +will be much more slower. + +`Match` performs zero allocations and is safe for concurrent use. Common +pattern shapes (literals, prefixes, suffixes, substrings) are recognized at +compile time and matched with plain string comparisons; the backtracking +engine behind the rest is differentially fuzzed against the `regexp` package +(see `FuzzMatchRegexp`). + +Run `go test -bench=.` from source root to see the benchmarks (the numbers +below are from an Apple M4): + +Pattern | Fixture | Match | Speed (ns/op) +--------|---------|-------|-------------- +`[a-z][!a-x]*cat*[h][!b]*eyes*` | `my cat has very bright eyes` | `true` | 141 +`[a-z][!a-x]*cat*[h][!b]*eyes*` | `my dog has very bright eyes` | `false` | 46 +`https://*.google.*` | `https://account.google.com` | `true` | 16 +`https://*.google.*` | `https://google.com` | `false` | 13 +`{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}` | `http://yahoo.com` | `true` | 61 +`{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}` | `http://google.com` | `false` | 70 +`{https://*gobwas.com,http://exclude.gobwas.com}` | `https://safe.gobwas.com` | `true` | 24 +`{https://*gobwas.com,http://exclude.gobwas.com}` | `http://safe.gobwas.com` | `false` | 32 +`google.com` | `google.com` | `true` | 5.0 +`google.com` | `gobwas.com` | `false` | 3.9 +`abc*` | `abcdef` | `true` | 4.1 +`abc*` | `af` | `false` | 3.0 +`*def` | `abcdef` | `true` | 4.1 +`*def` | `af` | `false` | 2.9 +`ab*ef` | `abcdef` | `true` | 6.0 +`ab*ef` | `af` | `false` | 3.0 + +The same things with the `regexp` package -- not to pick on it (it is a +general-purpose engine with much stronger guarantees), but as a reference +for how the glob-shaped specialization pays off per pattern. The regular +expressions are the exact equivalents: anchored, and with the `s` flag +where there is a `*`, since a `*` matches a newline like any other +character (see `BenchmarkCompareGlobAndRegexp`): + +Pattern | Fixture | Match | Speed (ns/op) | glob is +--------|---------|-------|---------------|-------- +`(?s)^[a-z][^a-x].*cat.*[h][^b].*eyes.*$` | `my cat has very bright eyes` | `true` | 505 | 3.6x faster +`(?s)^[a-z][^a-x].*cat.*[h][^b].*eyes.*$` | `my dog has very bright eyes` | `false` | 221 | 4.9x faster +`(?s)^https://.*\.google\..*$` | `https://account.google.com` | `true` | 251 | 16x faster +`(?s)^https://.*\.google\..*$` | `https://google.com` | `false` | 128 | 9.6x faster +`(?s)^(https://.*\.google\..*\|.*yandex\..*\|.*yahoo\..*\|.*mail\.ru)$` | `http://yahoo.com` | `true` | 396 | 6.5x faster +`(?s)^(https://.*\.google\..*\|.*yandex\..*\|.*yahoo\..*\|.*mail\.ru)$` | `http://google.com` | `false` | 558 | 8.0x faster +`(?s)^(https://.*gobwas\.com\|http://exclude\.gobwas\.com)$` | `https://safe.gobwas.com` | `true` | 210 | 8.8x faster +`(?s)^(https://.*gobwas\.com\|http://exclude\.gobwas\.com)$` | `http://safe.gobwas.com` | `false` | 46 | 1.4x faster +`^google\.com$` | `google.com` | `true` | 25 | 5.0x faster +`^google\.com$` | `gobwas.com` | `false` | 17 | 4.3x faster +`(?s)^abc.*$` | `abcdef` | `true` | 43 | 10x faster +`(?s)^abc.*$` | `af` | `false` | 1.5 | 2.0x slower +`(?s)^.*def$` | `abcdef` | `true` | 73 | 18x faster +`(?s)^.*def$` | `af` | `false` | 1.5 | 1.9x slower +`(?s)^ab.*ef$` | `abcdef` | `true` | 77 | 13x faster +`(?s)^ab.*ef$` | `af` | `false` | 1.5 | 2.0x slower + +(The three `slower` rows are the tiny-mismatch cases. Both engines reject +them with the same literal check; `regexp` just reaches it through less +call overhead. In absolute terms it is 1.5ns vs 3ns -- negligible either +way.) + +[godoc-image]: https://pkg.go.dev/badge/github.com/gobwas/glob.svg +[godoc-url]: https://pkg.go.dev/github.com/gobwas/glob +[ci-image]: https://github.com/gobwas/glob/actions/workflows/ci.yml/badge.svg?branch=master +[ci-url]: https://github.com/gobwas/glob/actions/workflows/ci.yml diff --git a/vendor/github.com/gobwas/glob/syntax/ast/ast.go b/vendor/github.com/gobwas/glob/syntax/ast/ast.go deleted file mode 100644 index 3220a694a9..0000000000 --- a/vendor/github.com/gobwas/glob/syntax/ast/ast.go +++ /dev/null @@ -1,122 +0,0 @@ -package ast - -import ( - "bytes" - "fmt" -) - -type Node struct { - Parent *Node - Children []*Node - Value interface{} - Kind Kind -} - -func NewNode(k Kind, v interface{}, ch ...*Node) *Node { - n := &Node{ - Kind: k, - Value: v, - } - for _, c := range ch { - Insert(n, c) - } - return n -} - -func (a *Node) Equal(b *Node) bool { - if a.Kind != b.Kind { - return false - } - if a.Value != b.Value { - return false - } - if len(a.Children) != len(b.Children) { - return false - } - for i, c := range a.Children { - if !c.Equal(b.Children[i]) { - return false - } - } - return true -} - -func (a *Node) String() string { - var buf bytes.Buffer - buf.WriteString(a.Kind.String()) - if a.Value != nil { - buf.WriteString(" =") - buf.WriteString(fmt.Sprintf("%v", a.Value)) - } - if len(a.Children) > 0 { - buf.WriteString(" [") - for i, c := range a.Children { - if i > 0 { - buf.WriteString(", ") - } - buf.WriteString(c.String()) - } - buf.WriteString("]") - } - return buf.String() -} - -func Insert(parent *Node, children ...*Node) { - parent.Children = append(parent.Children, children...) - for _, ch := range children { - ch.Parent = parent - } -} - -type List struct { - Not bool - Chars string -} - -type Range struct { - Not bool - Lo, Hi rune -} - -type Text struct { - Text string -} - -type Kind int - -const ( - KindNothing Kind = iota - KindPattern - KindList - KindRange - KindText - KindAny - KindSuper - KindSingle - KindAnyOf -) - -func (k Kind) String() string { - switch k { - case KindNothing: - return "Nothing" - case KindPattern: - return "Pattern" - case KindList: - return "List" - case KindRange: - return "Range" - case KindText: - return "Text" - case KindAny: - return "Any" - case KindSuper: - return "Super" - case KindSingle: - return "Single" - case KindAnyOf: - return "AnyOf" - default: - return "" - } -} diff --git a/vendor/github.com/gobwas/glob/syntax/ast/parser.go b/vendor/github.com/gobwas/glob/syntax/ast/parser.go deleted file mode 100644 index 429b409430..0000000000 --- a/vendor/github.com/gobwas/glob/syntax/ast/parser.go +++ /dev/null @@ -1,157 +0,0 @@ -package ast - -import ( - "errors" - "fmt" - "github.com/gobwas/glob/syntax/lexer" - "unicode/utf8" -) - -type Lexer interface { - Next() lexer.Token -} - -type parseFn func(*Node, Lexer) (parseFn, *Node, error) - -func Parse(lexer Lexer) (*Node, error) { - var parser parseFn - - root := NewNode(KindPattern, nil) - - var ( - tree *Node - err error - ) - for parser, tree = parserMain, root; parser != nil; { - parser, tree, err = parser(tree, lexer) - if err != nil { - return nil, err - } - } - - return root, nil -} - -func parserMain(tree *Node, lex Lexer) (parseFn, *Node, error) { - for { - token := lex.Next() - switch token.Type { - case lexer.EOF: - return nil, tree, nil - - case lexer.Error: - return nil, tree, errors.New(token.Raw) - - case lexer.Text: - Insert(tree, NewNode(KindText, Text{token.Raw})) - return parserMain, tree, nil - - case lexer.Any: - Insert(tree, NewNode(KindAny, nil)) - return parserMain, tree, nil - - case lexer.Super: - Insert(tree, NewNode(KindSuper, nil)) - return parserMain, tree, nil - - case lexer.Single: - Insert(tree, NewNode(KindSingle, nil)) - return parserMain, tree, nil - - case lexer.RangeOpen: - return parserRange, tree, nil - - case lexer.TermsOpen: - a := NewNode(KindAnyOf, nil) - Insert(tree, a) - - p := NewNode(KindPattern, nil) - Insert(a, p) - - return parserMain, p, nil - - case lexer.Separator: - p := NewNode(KindPattern, nil) - Insert(tree.Parent, p) - - return parserMain, p, nil - - case lexer.TermsClose: - return parserMain, tree.Parent.Parent, nil - - default: - return nil, tree, fmt.Errorf("unexpected token: %s", token) - } - } - return nil, tree, fmt.Errorf("unknown error") -} - -func parserRange(tree *Node, lex Lexer) (parseFn, *Node, error) { - var ( - not bool - lo rune - hi rune - chars string - ) - for { - token := lex.Next() - switch token.Type { - case lexer.EOF: - return nil, tree, errors.New("unexpected end") - - case lexer.Error: - return nil, tree, errors.New(token.Raw) - - case lexer.Not: - not = true - - case lexer.RangeLo: - r, w := utf8.DecodeRuneInString(token.Raw) - if len(token.Raw) > w { - return nil, tree, fmt.Errorf("unexpected length of lo character") - } - lo = r - - case lexer.RangeBetween: - // - - case lexer.RangeHi: - r, w := utf8.DecodeRuneInString(token.Raw) - if len(token.Raw) > w { - return nil, tree, fmt.Errorf("unexpected length of lo character") - } - - hi = r - - if hi < lo { - return nil, tree, fmt.Errorf("hi character '%s' should be greater than lo '%s'", string(hi), string(lo)) - } - - case lexer.Text: - chars = token.Raw - - case lexer.RangeClose: - isRange := lo != 0 && hi != 0 - isChars := chars != "" - - if isChars == isRange { - return nil, tree, fmt.Errorf("could not parse range") - } - - if isRange { - Insert(tree, NewNode(KindRange, Range{ - Lo: lo, - Hi: hi, - Not: not, - })) - } else { - Insert(tree, NewNode(KindList, List{ - Chars: chars, - Not: not, - })) - } - - return parserMain, tree, nil - } - } -} diff --git a/vendor/github.com/gobwas/glob/syntax/lexer.go b/vendor/github.com/gobwas/glob/syntax/lexer.go new file mode 100644 index 0000000000..4286080237 --- /dev/null +++ b/vendor/github.com/gobwas/glob/syntax/lexer.go @@ -0,0 +1,381 @@ +// Package syntax implements the lexer of the glob pattern syntax. The parser +// lives in package glob; the syntax itself is described at [glob.Compile]. +package syntax + +import ( + "bytes" + "fmt" + "slices" + "unicode/utf8" +) + +// TokenType tells the kind of a [Token]. +type TokenType int + +const ( + // EOF marks the end of the input; the lexer returns it repeatedly. + EOF TokenType = iota + // Error carries an error message in Token.Data; the lexer keeps + // returning it once it happened. Note that the lexer catches only the + // errors local to a token (an invalid UTF-8 sequence, a malformed + // character class): the structural ones, like an unclosed `{`, are for + // the parser to detect. + Error + // Text is a run of literal characters, with the escapes resolved. + Text + // Any is the `*` wildcard. + Any + // Super is the `**` wildcard. + Super + // Single is the `?` wildcard. + Single + // Not is the `!` right after the `[` of a character class. + Not + // TermSeparator is the `,` between the alternatives of a `{...}` group. + // Outside of a group a comma is a plain Text character. + TermSeparator + // RangeOpen and RangeClose are the `[` and `]` of a character class. + // Between them the lexer produces either a Text token (a set of + // characters, `[abc]`) or a RangeLo, RangeBetween, RangeHi triple (a + // range, `[a-c]`), possibly preceded by Not. + RangeOpen + RangeClose + RangeLo + RangeHi + RangeBetween + // TermsOpen and TermsClose are the `{` and `}` of an alternatives group. + TermsOpen + TermsClose +) + +func (tt TokenType) String() string { + switch tt { + case EOF: + return "eof" + case Error: + return "error" + case Text: + return "text" + case Any: + return "any" + case Super: + return "super" + case Single: + return "single" + case Not: + return "not" + case TermSeparator: + return "separator" + case RangeOpen: + return "range_open" + case RangeClose: + return "range_close" + case RangeLo: + return "range_lo" + case RangeHi: + return "range_hi" + case RangeBetween: + return "range_between" + case TermsOpen: + return "terms_open" + case TermsClose: + return "terms_close" + default: + return "" + } +} + +// Token is a lexeme of the pattern: its kind and the source text it was +// read from (or the error message for Error, the literal characters with +// the escapes resolved for Text). +type Token struct { + Type TokenType + Data string +} + +func (t Token) String() string { + return fmt.Sprintf("%v<%q>", t.Type, t.Data) +} + +const ( + char_any = '*' + char_comma = ',' + char_single = '?' + char_escape = '\\' + char_range_open = '[' + char_range_close = ']' + char_terms_open = '{' + char_terms_close = '}' + char_range_not = '!' + char_range_between = '-' +) + +var specials = []byte{ + char_any, + char_single, + char_escape, + char_range_open, + char_range_close, + char_terms_open, + char_terms_close, +} + +// IsSpecial reports whether c is a glob meta character, that is, one that +// [glob.QuoteMeta] escapes. Note that `,`, `!` and `-` are not among them: +// they are special only inside `{...}` and `[...]` respectively, which are. +func IsSpecial(c byte) bool { + return bytes.IndexByte(specials, c) != -1 +} + +type tokens []Token + +func (i *tokens) shift() (ret Token) { + ret = (*i)[0] + copy(*i, (*i)[1:]) + *i = (*i)[:len(*i)-1] + return +} + +func (i *tokens) push(v Token) { + *i = append(*i, v) +} + +func (i *tokens) empty() bool { + return len(*i) == 0 +} + +// eof is the end-of-input sentinel. It must not collide with any rune that +// can appear in a valid pattern -- note that U+0000 can. +const eof rune = -1 + +// Lexer splits a pattern into tokens; see [Lexer.Next]. +type Lexer struct { + data string + pos int + err error + + tokens tokens + termsLevel int + + lastRune rune + lastRuneSize int + hasRune bool +} + +// NewLexer returns a lexer over the source pattern. +func NewLexer(source string) *Lexer { + l := &Lexer{ + data: source, + tokens: tokens(make([]Token, 0, 4)), + } + return l +} + +// Offset returns the byte offset in the source the lexer stopped at, that +// is, the position right after the most recently returned token. +func (l *Lexer) Offset() int { + return l.pos +} + +// Next returns the next token. Once the input is over it returns EOF, and +// once an error happened it returns that Error, repeatedly. +func (l *Lexer) Next() Token { + if l.err != nil { + return Token{Error, l.err.Error()} + } + if !l.tokens.empty() { + return l.tokens.shift() + } + + l.fetchItem() + return l.Next() +} + +func (l *Lexer) peek() (r rune, w int) { + if l.pos == len(l.data) { + return eof, 0 + } + + r, w = utf8.DecodeRuneInString(l.data[l.pos:]) + if r == utf8.RuneError && w == 1 { + // An invalid encoding: a valid U+FFFD decodes at its width of 3. + l.errorf("invalid UTF-8 sequence") + r = eof + w = 0 + } + + return +} + +func (l *Lexer) read() rune { + if l.hasRune { + l.hasRune = false + l.seek(l.lastRuneSize) + return l.lastRune + } + + r, s := l.peek() + l.seek(s) + + l.lastRune = r + l.lastRuneSize = s + + return r +} + +func (l *Lexer) seek(w int) { + l.pos += w +} + +func (l *Lexer) unread() { + if l.hasRune { + l.errorf("could not unread rune") + return + } + l.seek(-l.lastRuneSize) + l.hasRune = true +} + +func (l *Lexer) errorf(f string, v ...any) { + l.err = fmt.Errorf(f, v...) +} + +func (l *Lexer) inTerms() bool { + return l.termsLevel > 0 +} + +func (l *Lexer) termsEnter() { + l.termsLevel++ +} + +func (l *Lexer) termsLeave() { + l.termsLevel-- +} + +var inTextBreakers = []rune{char_single, char_any, char_range_open, char_terms_open} +var inTermsBreakers = append(inTextBreakers, char_terms_close, char_comma) + +func (l *Lexer) fetchItem() { + r := l.read() + switch { + case r == eof: + l.tokens.push(Token{EOF, ""}) + + case r == char_terms_open: + l.termsEnter() + l.tokens.push(Token{TermsOpen, string(r)}) + + case r == char_comma && l.inTerms(): + l.tokens.push(Token{TermSeparator, string(r)}) + + case r == char_terms_close && l.inTerms(): + l.tokens.push(Token{TermsClose, string(r)}) + l.termsLeave() + + case r == char_range_open: + l.tokens.push(Token{RangeOpen, string(r)}) + l.fetchRange() + + case r == char_single: + l.tokens.push(Token{Single, string(r)}) + + case r == char_any: + if l.read() == char_any { + l.tokens.push(Token{Super, string(r) + string(r)}) + } else { + l.unread() + l.tokens.push(Token{Any, string(r)}) + } + + default: + l.unread() + + var breakers []rune + if l.inTerms() { + breakers = inTermsBreakers + } else { + breakers = inTextBreakers + } + l.fetchText(breakers) + } +} + +func (l *Lexer) fetchRange() { + var wantHi bool + var wantClose bool + var seenNot bool + for { + r := l.read() + if r == eof { + l.errorf("unexpected end of input") + return + } + + if wantClose { + if r != char_range_close { + l.errorf("expected close range character") + } else { + l.tokens.push(Token{RangeClose, string(r)}) + } + return + } + + if wantHi { + l.tokens.push(Token{RangeHi, string(r)}) + wantClose = true + continue + } + + if !seenNot && r == char_range_not { + l.tokens.push(Token{Not, string(r)}) + seenNot = true + continue + } + + if n, w := l.peek(); n == char_range_between { + l.seek(w) + l.tokens.push(Token{RangeLo, string(r)}) + l.tokens.push(Token{RangeBetween, string(n)}) + wantHi = true + continue + } + + l.unread() // unread first peek and fetch as text + l.fetchText([]rune{char_range_close}) + wantClose = true + } +} + +func (l *Lexer) fetchText(breakers []rune) { + var data []rune + var escaped bool + +reading: + for { + r := l.read() + if r == eof { + if escaped { + l.errorf("trailing backslash") + } + break + } + + if !escaped { + if r == char_escape { + escaped = true + continue + } + if slices.Index(breakers, r) != -1 { + l.unread() + break reading + } + } + + escaped = false + data = append(data, r) + } + + if len(data) > 0 { + l.tokens.push(Token{Text, string(data)}) + } +} diff --git a/vendor/github.com/gobwas/glob/syntax/lexer/lexer.go b/vendor/github.com/gobwas/glob/syntax/lexer/lexer.go deleted file mode 100644 index a1c8d1962a..0000000000 --- a/vendor/github.com/gobwas/glob/syntax/lexer/lexer.go +++ /dev/null @@ -1,273 +0,0 @@ -package lexer - -import ( - "bytes" - "fmt" - "github.com/gobwas/glob/util/runes" - "unicode/utf8" -) - -const ( - char_any = '*' - char_comma = ',' - char_single = '?' - char_escape = '\\' - char_range_open = '[' - char_range_close = ']' - char_terms_open = '{' - char_terms_close = '}' - char_range_not = '!' - char_range_between = '-' -) - -var specials = []byte{ - char_any, - char_single, - char_escape, - char_range_open, - char_range_close, - char_terms_open, - char_terms_close, -} - -func Special(c byte) bool { - return bytes.IndexByte(specials, c) != -1 -} - -type tokens []Token - -func (i *tokens) shift() (ret Token) { - ret = (*i)[0] - copy(*i, (*i)[1:]) - *i = (*i)[:len(*i)-1] - return -} - -func (i *tokens) push(v Token) { - *i = append(*i, v) -} - -func (i *tokens) empty() bool { - return len(*i) == 0 -} - -var eof rune = 0 - -type lexer struct { - data string - pos int - err error - - tokens tokens - termsLevel int - - lastRune rune - lastRuneSize int - hasRune bool -} - -func NewLexer(source string) *lexer { - l := &lexer{ - data: source, - tokens: tokens(make([]Token, 0, 4)), - } - return l -} - -func (l *lexer) Next() Token { - if l.err != nil { - return Token{Error, l.err.Error()} - } - if !l.tokens.empty() { - return l.tokens.shift() - } - - l.fetchItem() - return l.Next() -} - -func (l *lexer) peek() (r rune, w int) { - if l.pos == len(l.data) { - return eof, 0 - } - - r, w = utf8.DecodeRuneInString(l.data[l.pos:]) - if r == utf8.RuneError { - l.errorf("could not read rune") - r = eof - w = 0 - } - - return -} - -func (l *lexer) read() rune { - if l.hasRune { - l.hasRune = false - l.seek(l.lastRuneSize) - return l.lastRune - } - - r, s := l.peek() - l.seek(s) - - l.lastRune = r - l.lastRuneSize = s - - return r -} - -func (l *lexer) seek(w int) { - l.pos += w -} - -func (l *lexer) unread() { - if l.hasRune { - l.errorf("could not unread rune") - return - } - l.seek(-l.lastRuneSize) - l.hasRune = true -} - -func (l *lexer) errorf(f string, v ...interface{}) { - l.err = fmt.Errorf(f, v...) -} - -func (l *lexer) inTerms() bool { - return l.termsLevel > 0 -} - -func (l *lexer) termsEnter() { - l.termsLevel++ -} - -func (l *lexer) termsLeave() { - l.termsLevel-- -} - -var inTextBreakers = []rune{char_single, char_any, char_range_open, char_terms_open} -var inTermsBreakers = append(inTextBreakers, char_terms_close, char_comma) - -func (l *lexer) fetchItem() { - r := l.read() - switch { - case r == eof: - l.tokens.push(Token{EOF, ""}) - - case r == char_terms_open: - l.termsEnter() - l.tokens.push(Token{TermsOpen, string(r)}) - - case r == char_comma && l.inTerms(): - l.tokens.push(Token{Separator, string(r)}) - - case r == char_terms_close && l.inTerms(): - l.tokens.push(Token{TermsClose, string(r)}) - l.termsLeave() - - case r == char_range_open: - l.tokens.push(Token{RangeOpen, string(r)}) - l.fetchRange() - - case r == char_single: - l.tokens.push(Token{Single, string(r)}) - - case r == char_any: - if l.read() == char_any { - l.tokens.push(Token{Super, string(r) + string(r)}) - } else { - l.unread() - l.tokens.push(Token{Any, string(r)}) - } - - default: - l.unread() - - var breakers []rune - if l.inTerms() { - breakers = inTermsBreakers - } else { - breakers = inTextBreakers - } - l.fetchText(breakers) - } -} - -func (l *lexer) fetchRange() { - var wantHi bool - var wantClose bool - var seenNot bool - for { - r := l.read() - if r == eof { - l.errorf("unexpected end of input") - return - } - - if wantClose { - if r != char_range_close { - l.errorf("expected close range character") - } else { - l.tokens.push(Token{RangeClose, string(r)}) - } - return - } - - if wantHi { - l.tokens.push(Token{RangeHi, string(r)}) - wantClose = true - continue - } - - if !seenNot && r == char_range_not { - l.tokens.push(Token{Not, string(r)}) - seenNot = true - continue - } - - if n, w := l.peek(); n == char_range_between { - l.seek(w) - l.tokens.push(Token{RangeLo, string(r)}) - l.tokens.push(Token{RangeBetween, string(n)}) - wantHi = true - continue - } - - l.unread() // unread first peek and fetch as text - l.fetchText([]rune{char_range_close}) - wantClose = true - } -} - -func (l *lexer) fetchText(breakers []rune) { - var data []rune - var escaped bool - -reading: - for { - r := l.read() - if r == eof { - break - } - - if !escaped { - if r == char_escape { - escaped = true - continue - } - - if runes.IndexRune(breakers, r) != -1 { - l.unread() - break reading - } - } - - escaped = false - data = append(data, r) - } - - if len(data) > 0 { - l.tokens.push(Token{Text, string(data)}) - } -} diff --git a/vendor/github.com/gobwas/glob/syntax/lexer/token.go b/vendor/github.com/gobwas/glob/syntax/lexer/token.go deleted file mode 100644 index 2797c4e83a..0000000000 --- a/vendor/github.com/gobwas/glob/syntax/lexer/token.go +++ /dev/null @@ -1,88 +0,0 @@ -package lexer - -import "fmt" - -type TokenType int - -const ( - EOF TokenType = iota - Error - Text - Char - Any - Super - Single - Not - Separator - RangeOpen - RangeClose - RangeLo - RangeHi - RangeBetween - TermsOpen - TermsClose -) - -func (tt TokenType) String() string { - switch tt { - case EOF: - return "eof" - - case Error: - return "error" - - case Text: - return "text" - - case Char: - return "char" - - case Any: - return "any" - - case Super: - return "super" - - case Single: - return "single" - - case Not: - return "not" - - case Separator: - return "separator" - - case RangeOpen: - return "range_open" - - case RangeClose: - return "range_close" - - case RangeLo: - return "range_lo" - - case RangeHi: - return "range_hi" - - case RangeBetween: - return "range_between" - - case TermsOpen: - return "terms_open" - - case TermsClose: - return "terms_close" - - default: - return "undef" - } -} - -type Token struct { - Type TokenType - Raw string -} - -func (t Token) String() string { - return fmt.Sprintf("%v<%q>", t.Type, t.Raw) -} diff --git a/vendor/github.com/gobwas/glob/syntax/syntax.go b/vendor/github.com/gobwas/glob/syntax/syntax.go deleted file mode 100644 index 1d168b1482..0000000000 --- a/vendor/github.com/gobwas/glob/syntax/syntax.go +++ /dev/null @@ -1,14 +0,0 @@ -package syntax - -import ( - "github.com/gobwas/glob/syntax/ast" - "github.com/gobwas/glob/syntax/lexer" -) - -func Parse(s string) (*ast.Node, error) { - return ast.Parse(lexer.NewLexer(s)) -} - -func Special(b byte) bool { - return lexer.Special(b) -} diff --git a/vendor/github.com/gobwas/glob/util/runes/runes.go b/vendor/github.com/gobwas/glob/util/runes/runes.go deleted file mode 100644 index a723556410..0000000000 --- a/vendor/github.com/gobwas/glob/util/runes/runes.go +++ /dev/null @@ -1,154 +0,0 @@ -package runes - -func Index(s, needle []rune) int { - ls, ln := len(s), len(needle) - - switch { - case ln == 0: - return 0 - case ln == 1: - return IndexRune(s, needle[0]) - case ln == ls: - if Equal(s, needle) { - return 0 - } - return -1 - case ln > ls: - return -1 - } - -head: - for i := 0; i < ls && ls-i >= ln; i++ { - for y := 0; y < ln; y++ { - if s[i+y] != needle[y] { - continue head - } - } - - return i - } - - return -1 -} - -func LastIndex(s, needle []rune) int { - ls, ln := len(s), len(needle) - - switch { - case ln == 0: - if ls == 0 { - return 0 - } - return ls - case ln == 1: - return IndexLastRune(s, needle[0]) - case ln == ls: - if Equal(s, needle) { - return 0 - } - return -1 - case ln > ls: - return -1 - } - -head: - for i := ls - 1; i >= 0 && i >= ln; i-- { - for y := ln - 1; y >= 0; y-- { - if s[i-(ln-y-1)] != needle[y] { - continue head - } - } - - return i - ln + 1 - } - - return -1 -} - -// IndexAny returns the index of the first instance of any Unicode code point -// from chars in s, or -1 if no Unicode code point from chars is present in s. -func IndexAny(s, chars []rune) int { - if len(chars) > 0 { - for i, c := range s { - for _, m := range chars { - if c == m { - return i - } - } - } - } - return -1 -} - -func Contains(s, needle []rune) bool { - return Index(s, needle) >= 0 -} - -func Max(s []rune) (max rune) { - for _, r := range s { - if r > max { - max = r - } - } - - return -} - -func Min(s []rune) rune { - min := rune(-1) - for _, r := range s { - if min == -1 { - min = r - continue - } - - if r < min { - min = r - } - } - - return min -} - -func IndexRune(s []rune, r rune) int { - for i, c := range s { - if c == r { - return i - } - } - return -1 -} - -func IndexLastRune(s []rune, r rune) int { - for i := len(s) - 1; i >= 0; i-- { - if s[i] == r { - return i - } - } - - return -1 -} - -func Equal(a, b []rune) bool { - if len(a) == len(b) { - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return false - } - } - - return true - } - - return false -} - -// HasPrefix tests whether the string s begins with prefix. -func HasPrefix(s, prefix []rune) bool { - return len(s) >= len(prefix) && Equal(s[0:len(prefix)], prefix) -} - -// HasSuffix tests whether the string s ends with suffix. -func HasSuffix(s, suffix []rune) bool { - return len(s) >= len(suffix) && Equal(s[len(s)-len(suffix):], suffix) -} diff --git a/vendor/github.com/gobwas/glob/util/strings/strings.go b/vendor/github.com/gobwas/glob/util/strings/strings.go deleted file mode 100644 index e8ee1920b1..0000000000 --- a/vendor/github.com/gobwas/glob/util/strings/strings.go +++ /dev/null @@ -1,39 +0,0 @@ -package strings - -import ( - "strings" - "unicode/utf8" -) - -func IndexAnyRunes(s string, rs []rune) int { - for _, r := range rs { - if i := strings.IndexRune(s, r); i != -1 { - return i - } - } - - return -1 -} - -func LastIndexAnyRunes(s string, rs []rune) int { - for _, r := range rs { - i := -1 - if 0 <= r && r < utf8.RuneSelf { - i = strings.LastIndexByte(s, byte(r)) - } else { - sub := s - for len(sub) > 0 { - j := strings.IndexRune(s, r) - if j == -1 { - break - } - i = j - sub = sub[i+1:] - } - } - if i != -1 { - return i - } - } - return -1 -} diff --git a/vendor/github.com/lestrrat-go/dsig/.golangci.yml b/vendor/github.com/lestrrat-go/dsig/.golangci.yml new file mode 100644 index 0000000000..8cde331d8e --- /dev/null +++ b/vendor/github.com/lestrrat-go/dsig/.golangci.yml @@ -0,0 +1,14 @@ +version: "2" + +# The linter set is golangci-lint's default. The point of this file is the +# formatters block below: without it nothing checked gofmt, and the drift that +# allowed put a closing code fence on the same line as a line of Go, which +# broke half of README.md once the doc generator started running. +formatters: + enable: + - gofmt + +issues: + # Report every unformatted file. The default caps repeats of one message at + # three, which would hide the tail of exactly this kind of sweep. + max-same-issues: 0 diff --git a/vendor/github.com/lestrrat-go/dsig/Changes b/vendor/github.com/lestrrat-go/dsig/Changes index 5e7a522cd1..7264f1801d 100644 --- a/vendor/github.com/lestrrat-go/dsig/Changes +++ b/vendor/github.com/lestrrat-go/dsig/Changes @@ -1,6 +1,57 @@ Changes ======= +v1.4.0 20 Aug 2026 + * Add ML-DSA (FIPS 204) support: the `MLDSA44`, `MLDSA65`, and `MLDSA87` + algorithms, the `SignMLDSA()` / `VerifyMLDSA()` primitives, and a new + `MLDSAFamily` algorithm family. ML-DSA requires Go 1.27 or later, which is + when `crypto/mldsa` joins the standard library; on earlier toolchains the + constants are not declared and the algorithms are not registered. + + An ML-DSA key carries its own parameter set, so naming an algorithm that + disagrees with the key is an error on both the sign and verify paths. + `SignMLDSA()` takes a `crypto.SignerOpts` so that both signing modes stay + reachable: an `*mldsa.Options` supplies a context string, and + `crypto.MLDSAMu` signs a pre-hashed mu message representative. + `VerifyMLDSA()` takes an `*mldsa.Options` because verification has a single + mode. `SignDigest()` and `VerifyDigest()` return an error for ML-DSA, as + they already do for EdDSA. + + * The minimum Go version is now 1.25. + +v1.3.0 13 Apr 2026 + * Add `SignWithOpts()` and `VerifyWithOpts()`, which thread an optional + `crypto.SignerOpts` through to the underlying signer. For built-in + families (HMAC, RSA, ECDSA, EdDSA) the opts argument is ignored. For + the `Custom` family, opts are forwarded to the algorithm's Meta when + it implements the new `SignerWithOpts` / `VerifierWithOpts` interfaces; + otherwise the dispatcher falls back to the plain `Signer` / `Verifier` + methods and the opts are dropped. The canonical use case is composite + ML-DSA signatures, where a per-call domain-separation context + (`*mldsa.Options`) must reach `filippo.io/mldsa`. + + * `Sign()` is now a one-line wrapper around `SignWithOpts()` (and + `Verify()` likewise wraps `VerifyWithOpts()`). The public signatures + of `Sign` and `Verify` are unchanged; the only observable difference + for existing callers is one extra call frame. + + * `RegisterAlgorithm()` for the `Custom` family now accepts a Meta that + implements only `SignerWithOpts` / `VerifierWithOpts` (in addition to + the existing `Signer` / `Verifier` paths). + + * In dsig v2, `Sign` / `Verify` will absorb the opts parameter and + `SignWithOpts` / `VerifyWithOpts` will be removed. The same migration + is planned for `SignDigest` / `VerifyDigest` once a `DigestSigner` + interface for the `Custom` family lands. Doc comments on all four + entry points flag the upcoming change. + +v1.2.2 13 Apr 2026 + * Add `SignECDSADER()` and `VerifyECDSADER()` primitive helpers for ECDSA + signatures in ASN.1 DER-encoded `Ecdsa-Sig-Value` form (RFC 3279 §2.2.3), + as used by X.509/PKIX and composite signature schemes. The existing + `SignECDSA()`/`VerifyECDSA()` functions remain the canonical entry points + for the JWS-native fixed-length r||s format (RFC 7515 §3.4). + v1.2.1 7 Apr 2026 * Add `SignDigest()` for signing pre-computed digests. Supported for HMAC, RSA (PKCS1v15 and PSS), and ECDSA families. EdDSA and Custom return an error. @@ -25,4 +76,4 @@ v1.1.0 2 Apr 2026 algorithm name. Use `UnregisterAlgorithm()` first if you need to replace it. v1.0.0 - 18 Aug 2025 - * Initial release \ No newline at end of file + * Initial release diff --git a/vendor/github.com/lestrrat-go/dsig/README.md b/vendor/github.com/lestrrat-go/dsig/README.md index b52b998f8f..55b75963e9 100644 --- a/vendor/github.com/lestrrat-go/dsig/README.md +++ b/vendor/github.com/lestrrat-go/dsig/README.md @@ -1,4 +1,4 @@ -# github.com/lestrrat-go/dsig [![CI](https://github.com/lestrrat-go/dsig/actions/workflows/ci.yml/badge.svg)](https://github.com/lestrrat-go/dsig/actions/workflows/ci.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/dsig.svg)](https://pkg.go.dev/github.com/lestrrat-go/dsig) [![codecov.io](https://codecov.io/github/lestrrat-go/dsig/coverage.svg?branch=v1)](https://codecov.io/github/lestrrat-go/dsig?branch=v1) +# github.com/lestrrat-go/dsig [![CI](https://github.com/lestrrat-go/dsig/actions/workflows/ci.yml/badge.svg)](https://github.com/lestrrat-go/dsig/actions/workflows/ci.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/dsig.svg)](https://pkg.go.dev/github.com/lestrrat-go/dsig) Go module providing low-level digital signature operations. @@ -9,6 +9,7 @@ While there are many standards for generating and verifying digital signatures, * RSA signatures (PKCS1v15 and PSS) * ECDSA signatures (P-256, P-384, P-521) * EdDSA signatures (Ed25519, Ed448) +* ML-DSA post-quantum signatures (ML-DSA-44, ML-DSA-65, ML-DSA-87), on Go 1.27 and later * HMAC signatures (SHA-256, SHA-384, SHA-512) * Support for crypto.Signer interface * Custom algorithm registration via `Signer`/`Verifier` interfaces @@ -143,6 +144,53 @@ source: [examples/dsig_readme_example_test.go](https://github.com/lestrrat-go/ds | `ECDSAWithP384AndSHA384` | ECDSA using P-384 and SHA-384 | *ecdsa.PrivateKey / *ecdsa.PublicKey | | `ECDSAWithP521AndSHA512` | ECDSA using P-521 and SHA-512 | *ecdsa.PrivateKey / *ecdsa.PublicKey | | `EdDSA` | EdDSA using Ed25519 or Ed448 | ed25519.PrivateKey / ed25519.PublicKey | +| `MLDSA44` | ML-DSA-44 (FIPS 204), NIST level 2 | *mldsa.PrivateKey / *mldsa.PublicKey | +| `MLDSA65` | ML-DSA-65 (FIPS 204), NIST level 3 | *mldsa.PrivateKey / *mldsa.PublicKey | +| `MLDSA87` | ML-DSA-87 (FIPS 204), NIST level 5 | *mldsa.PrivateKey / *mldsa.PublicKey | + +The three ML-DSA algorithms need Go 1.27 or later, which is when `crypto/mldsa` +joins the standard library. On earlier toolchains the constants are not declared +and the algorithms are not registered. + +Name the constant that matches the key you generated. A key knows its own +parameter set, and naming a different one is an error, so a key cannot be used +under a weaker set by accident: + +```go +sk, _ := mldsa.GenerateKey(mldsa.MLDSA65()) + +sig, _ := dsig.Sign(sk, dsig.MLDSA65, payload, nil) +err := dsig.Verify(sk.PublicKey(), dsig.MLDSA65, payload, sig) + +_, err = dsig.Sign(sk, dsig.MLDSA44, payload, nil) +// dsig.SignWithOpts: ML-DSA parameter set mismatch: key is ML-DSA-65, algorithm is ML-DSA-44 +``` + +## ML-DSA context strings + +ML-DSA can mix a caller-chosen string into the signature. Give each job a +different context and one key can sign for several of them without a signature +made for one job verifying as another, so a login token cannot be presented as +a file receipt. + +Signing and verifying must use the same context. A verifier that supplies the +wrong one, or none at all, sees an ordinary invalid signature and cannot tell +which mistake was made: + +```go +login := &mldsa.Options{Context: "my-app/login-token"} +receipt := &mldsa.Options{Context: "my-app/file-receipt"} + +sig, _ := dsig.SignWithOpts(sk, dsig.MLDSA65, payload, login, nil) + +err := dsig.VerifyWithOpts(sk.PublicKey(), dsig.MLDSA65, payload, sig, login) +// nil + +err = dsig.VerifyWithOpts(sk.PublicKey(), dsig.MLDSA65, payload, sig, receipt) +// mldsa: invalid signature +``` + +A context is at most 255 bytes, and it is empty when opts is nil. # Description @@ -160,4 +208,4 @@ Please include tests that exercise your changes. # Related Libraries -* [github.com/lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) - JOSE (JWA/JWE/JWK/JWS/JWT) implementation \ No newline at end of file +* [github.com/lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) - JOSE (JWA/JWE/JWK/JWS/JWT) implementation diff --git a/vendor/github.com/lestrrat-go/dsig/algorithms.go b/vendor/github.com/lestrrat-go/dsig/algorithms.go index 0895c64764..3cf93b0c7e 100644 --- a/vendor/github.com/lestrrat-go/dsig/algorithms.go +++ b/vendor/github.com/lestrrat-go/dsig/algorithms.go @@ -34,4 +34,4 @@ const ( // EdDSA signature algorithms // These use Edwards-curve Digital Signature Algorithm (supports Ed25519 and Ed448) EdDSA = "EDDSA" -) \ No newline at end of file +) diff --git a/vendor/github.com/lestrrat-go/dsig/dsig.go b/vendor/github.com/lestrrat-go/dsig/dsig.go index a6b54418a7..ee278311be 100644 --- a/vendor/github.com/lestrrat-go/dsig/dsig.go +++ b/vendor/github.com/lestrrat-go/dsig/dsig.go @@ -28,6 +28,12 @@ const ( ECDSA EdDSAFamily Custom + // MLDSAFamily covers the ML-DSA parameter sets. It is deliberately not + // Custom: Custom means this library knows nothing about the algorithm, + // which would be false here and misleads callers that switch on Family. + // + // It sits after Custom so the values earlier releases assigned stay put. + MLDSAFamily maxFamily ) @@ -44,6 +50,8 @@ func (f Family) String() string { return "EdDSA" case Custom: return "Custom" + case MLDSAFamily: + return "ML-DSA" default: return "InvalidFamily" } @@ -85,6 +93,22 @@ type Signer interface { Sign(key any, payload []byte, rand io.Reader) ([]byte, error) } +// SignerWithOpts is an optional interface that Custom-family signers +// can implement to receive a per-call [crypto.SignerOpts]. The +// canonical use case is ML-DSA, whose Sign method accepts an +// *mldsa.Options carrying a domain-separation context that the plain +// [Signer] interface cannot convey. Custom Meta values that do not +// implement this interface still work with [SignWithOpts]: the +// dispatcher falls back to the plain [Signer.Sign] method and the opts +// argument is dropped. +// +// Implementing both [Signer] and SignerWithOpts is supported, but +// implementing only SignerWithOpts is sufficient because the dispatcher +// checks for it first. +type SignerWithOpts interface { + SignWithOpts(key any, payload []byte, opts crypto.SignerOpts, rand io.Reader) ([]byte, error) +} + // Verifier is an interface for custom verification implementations. // For the Custom algorithm family, info.Meta must implement this interface // to support verification. The implementation struct can carry any additional @@ -93,6 +117,12 @@ type Verifier interface { Verify(key any, payload, signature []byte) error } +// VerifierWithOpts is the verification counterpart of [SignerWithOpts]. +// See [SignerWithOpts] for usage notes. +type VerifierWithOpts interface { + VerifyWithOpts(key any, payload, signature []byte, opts crypto.SignerOpts) error +} + var algorithms = make(map[string]AlgorithmInfo) var builtinAlgorithms = make(map[string]struct{}) var muAlgorithms sync.RWMutex @@ -102,7 +132,8 @@ var muAlgorithms sync.RWMutex // info.Meta should contain extra metadata for some algorithms. HMAC, RSA, and ECDSA // families need their respective metadata (HMACFamilyMeta, RSAFamilyMeta, and // ECDSAFamilyMeta). Metadata for EdDSA is optional. For the Custom family, Meta -// must implement at least one of the Signer or Verifier interfaces. +// must implement at least one of the Signer, SignerWithOpts, Verifier, or +// VerifierWithOpts interfaces. // // Re-registration of an already-registered algorithm name is rejected. Use // UnregisterAlgorithm to remove it first if you need to replace it. @@ -130,11 +161,17 @@ func RegisterAlgorithm(name string, info AlgorithmInfo) error { } case EdDSAFamily: // EdDSA metadata is optional for now - case Custom: + case Custom, MLDSAFamily: + // Both families carry their implementation in Meta. The other families + // put passive metadata there. For ML-DSA this is forced: crypto/mldsa + // exists only from Go 1.27, so the algorithm cannot be described by a + // value type this file could name. _, isSigner := info.Meta.(Signer) + _, isSignerWithOpts := info.Meta.(SignerWithOpts) _, isVerifier := info.Meta.(Verifier) - if !isSigner && !isVerifier { - return fmt.Errorf("custom algorithm %s: Meta must implement Signer and/or Verifier", name) + _, isVerifierWithOpts := info.Meta.(VerifierWithOpts) + if !isSigner && !isSignerWithOpts && !isVerifier && !isVerifierWithOpts { + return fmt.Errorf("%s algorithm %s: Meta must implement Signer, SignerWithOpts, Verifier, or VerifierWithOpts", info.Family, name) } default: return fmt.Errorf("unsupported algorithm family %s for algorithm %s", info.Family, name) @@ -272,4 +309,3 @@ func init() { builtinAlgorithms[name] = struct{}{} } } - diff --git a/vendor/github.com/lestrrat-go/dsig/ecdsa.go b/vendor/github.com/lestrrat-go/dsig/ecdsa.go index 4041d9c53d..9da4ac244e 100644 --- a/vendor/github.com/lestrrat-go/dsig/ecdsa.go +++ b/vendor/github.com/lestrrat-go/dsig/ecdsa.go @@ -12,7 +12,6 @@ import ( "github.com/lestrrat-go/dsig/internal/ecutil" ) - func ecdsaGetSignerKey(key any) (*ecdsa.PrivateKey, crypto.Signer, bool, error) { cs, isCryptoSigner := key.(crypto.Signer) if isCryptoSigner { @@ -124,6 +123,47 @@ func SignECDSA(key *ecdsa.PrivateKey, payload []byte, h crypto.Hash, rr io.Reade return PackECDSASignature(r, s, key.Curve.Params().BitSize) } +// SignECDSADER generates an ECDSA signature in ASN.1 DER-encoded Ecdsa-Sig-Value +// format (RFC 3279 §2.2.3), as required by X.509/PKIX and composite signature +// schemes such as draft-ietf-lamps-pq-composite-sigs. For the fixed-length +// JWS r||s format (RFC 7515 §3.4), use SignECDSA instead. +// +// The payload is hashed with h before signing. rr provides randomness; if nil, +// rand.Reader is used. +func SignECDSADER(key *ecdsa.PrivateKey, payload []byte, h crypto.Hash, rr io.Reader) ([]byte, error) { + if !isValidECDSAKey(key) { + return nil, fmt.Errorf(`invalid key type %T for ECDSA algorithm`, key) + } + hh := h.New() + if _, err := hh.Write(payload); err != nil { + return nil, fmt.Errorf(`failed to write payload using ecdsa: %w`, err) + } + digest := hh.Sum(nil) + + if rr == nil { + rr = rand.Reader + } + + sig, err := ecdsa.SignASN1(rr, key, digest) + if err != nil { + return nil, fmt.Errorf(`failed to sign payload using ecdsa: %w`, err) + } + return sig, nil +} + +// VerifyECDSADER verifies an ECDSA signature in ASN.1 DER-encoded +// Ecdsa-Sig-Value format. See SignECDSADER for the format distinction. The +// payload is hashed with h before verification. +func VerifyECDSADER(key *ecdsa.PublicKey, payload, signature []byte, h crypto.Hash) error { + hh := h.New() + hh.Write(payload) + digest := hh.Sum(nil) + if !ecdsa.VerifyASN1(key, digest, signature) { + return NewVerificationError("invalid ECDSA signature") + } + return nil +} + // SignECDSACryptoSigner generates an ECDSA signature using a crypto.Signer interface. // This function works with hardware security modules and other crypto.Signer implementations. // The signature is converted from ASN.1 format to JWS format (r||s). diff --git a/vendor/github.com/lestrrat-go/dsig/mldsa.go b/vendor/github.com/lestrrat-go/dsig/mldsa.go new file mode 100644 index 0000000000..218204774d --- /dev/null +++ b/vendor/github.com/lestrrat-go/dsig/mldsa.go @@ -0,0 +1,193 @@ +//go:build go1.27 + +package dsig + +import ( + "crypto" + "crypto/mldsa" + "fmt" + "io" +) + +// ML-DSA signature algorithms, the post-quantum scheme specified in FIPS 204. +// The three names identify the three parameter sets, which differ in security +// level and in key and signature sizes. +// +// These names match what crypto/mldsa's Parameters.String reports, so the +// parameter set a key carries can be compared against the algorithm name +// directly. +// +// ML-DSA is available only when dsig is built with Go 1.27 or later, which is +// when crypto/mldsa becomes part of the standard library. On earlier +// toolchains these algorithms are not registered and not declared. +const ( + MLDSA44 = "ML-DSA-44" + MLDSA65 = "ML-DSA-65" + MLDSA87 = "ML-DSA-87" +) + +func init() { + for _, params := range []mldsa.Parameters{mldsa.MLDSA44(), mldsa.MLDSA65(), mldsa.MLDSA87()} { + name := params.String() + if err := RegisterAlgorithm(name, AlgorithmInfo{ + Family: MLDSAFamily, + Meta: &mldsaAlgorithm{params: params}, + }); err != nil { + panic(fmt.Sprintf("failed to register algorithm %s: %v", name, err)) + } + builtinAlgorithms[name] = struct{}{} + } +} + +// SignMLDSA generates an ML-DSA signature for the given payload. +// +// opts may be nil, which signs payload directly with no context. Pass an +// *[mldsa.Options] to supply a domain-separation context, which [VerifyMLDSA] +// then requires to match. +// +// opts is a [crypto.SignerOpts] so that both of ML-DSA's signing modes stay +// expressible. Passing [crypto.MLDSAMu] means payload holds a pre-hashed μ +// message representative. That mode is a shortcut for callers who already have +// μ, and it produces an ordinary signature; [VerifyMLDSA] checks it against the +// original message, and the verify side needs no counterpart. +// +// crypto/mldsa rejects any other opts value, so a mistaken type cannot be +// silently downgraded to a context-free signature. +func SignMLDSA(key *mldsa.PrivateKey, payload []byte, opts crypto.SignerOpts) ([]byte, error) { + if key == nil { + return nil, fmt.Errorf(`dsig.SignMLDSA: key cannot be nil`) + } + // The io.Reader argument is ignored by crypto/mldsa; signing draws its own + // randomness. SignDeterministic is the variant that draws none. + return key.Sign(nil, payload, opts) +} + +// VerifyMLDSA verifies an ML-DSA signature for the given payload. +// +// opts may be nil. It must carry the same Context that was used to produce the +// signature, otherwise verification fails. +// +// Verification has a single mode, so opts is a concrete *[mldsa.Options]. μ is +// derived from the message, so a signature made from a pre-hashed μ verifies +// here against the original message. +func VerifyMLDSA(key *mldsa.PublicKey, payload, signature []byte, opts *mldsa.Options) error { + if key == nil { + return fmt.Errorf(`dsig.VerifyMLDSA: key cannot be nil`) + } + return mldsa.Verify(key, payload, signature, opts) +} + +// mldsaAlgorithm is the Custom-family adapter that binds one ML-DSA parameter +// set to the registry. It carries the parameter set so that every operation can +// check the caller's key against the algorithm that was asked for. +type mldsaAlgorithm struct { + params mldsa.Parameters +} + +// requireMLDSAParams reports whether a caller-supplied key belongs to the +// parameter set this algorithm was registered for. crypto/mldsa's Parameters is +// a comparable value naming one of the three FIPS 204 sets, so a plain +// comparison suffices. +// +// The check matters because the key owns the parameter set, and the call only +// names one. Without it, an ML-DSA-65 key would happily produce and verify +// ML-DSA-65 signatures while the caller believed it had selected ML-DSA-44. +// Anything that reads the algorithm name to decide a post-quantum security +// level would then be misled, so the mismatch is an error. +func (a *mldsaAlgorithm) requireMLDSAParams(got mldsa.Parameters) error { + if got != a.params { + return fmt.Errorf(`ML-DSA parameter set mismatch: key is %s, algorithm is %s`, got, a.params) + } + return nil +} + +func (a *mldsaAlgorithm) privateKey(key any) (*mldsa.PrivateKey, error) { + sk, ok := key.(*mldsa.PrivateKey) + if !ok { + return nil, fmt.Errorf(`expected *mldsa.PrivateKey, got %T`, key) + } + if err := a.requireMLDSAParams(sk.PublicKey().Parameters()); err != nil { + return nil, err + } + return sk, nil +} + +// publicKey narrows the key types the verify surface accepts. A private key is +// allowed so callers holding only one half do not have to unwrap it themselves. +func (a *mldsaAlgorithm) publicKey(key any) (*mldsa.PublicKey, error) { + var pk *mldsa.PublicKey + switch k := key.(type) { + case *mldsa.PublicKey: + pk = k + case *mldsa.PrivateKey: + pk = k.PublicKey() + default: + return nil, fmt.Errorf(`expected *mldsa.PublicKey or *mldsa.PrivateKey, got %T`, key) + } + if err := a.requireMLDSAParams(pk.Parameters()); err != nil { + return nil, err + } + return pk, nil +} + +// mldsaOptions narrows a crypto.SignerOpts to the concrete type crypto/mldsa +// accepts. A non-nil value of any other type is an error. Dropping it would let +// a caller believe their Context was in force while the operation actually ran +// with an empty context, which is a signature substitution vector for schemes +// that rely on domain separation. +func mldsaOptions(opts crypto.SignerOpts) (*mldsa.Options, error) { + if opts == nil { + return nil, nil + } + mldsaOpts, ok := opts.(*mldsa.Options) + if !ok { + return nil, fmt.Errorf(`expected *mldsa.Options, got %T`, opts) + } + return mldsaOpts, nil +} + +func (a *mldsaAlgorithm) Sign(key any, payload []byte, _ io.Reader) ([]byte, error) { + sk, err := a.privateKey(key) + if err != nil { + return nil, fmt.Errorf(`dsig.Sign: %w`, err) + } + return SignMLDSA(sk, payload, nil) +} + +// SignWithOpts implements [SignerWithOpts], forwarding an *mldsa.Options +// Context to crypto/mldsa. +func (a *mldsaAlgorithm) SignWithOpts(key any, payload []byte, opts crypto.SignerOpts, _ io.Reader) ([]byte, error) { + sk, err := a.privateKey(key) + if err != nil { + return nil, fmt.Errorf(`dsig.SignWithOpts: %w`, err) + } + // Validated but deliberately not narrowed. SignMLDSA takes a + // crypto.SignerOpts, so converting to a typed nil here would hand + // crypto/mldsa a non-nil interface holding a nil pointer. + if _, err := mldsaOptions(opts); err != nil { + return nil, fmt.Errorf(`dsig.SignWithOpts: %w`, err) + } + return SignMLDSA(sk, payload, opts) +} + +func (a *mldsaAlgorithm) Verify(key any, payload, signature []byte) error { + pk, err := a.publicKey(key) + if err != nil { + return fmt.Errorf(`dsig.Verify: %w`, err) + } + return VerifyMLDSA(pk, payload, signature, nil) +} + +// VerifyWithOpts implements [VerifierWithOpts]. See [SignerWithOpts] for the +// rationale on rejecting a foreign opts type. +func (a *mldsaAlgorithm) VerifyWithOpts(key any, payload, signature []byte, opts crypto.SignerOpts) error { + pk, err := a.publicKey(key) + if err != nil { + return fmt.Errorf(`dsig.VerifyWithOpts: %w`, err) + } + mldsaOpts, err := mldsaOptions(opts) + if err != nil { + return fmt.Errorf(`dsig.VerifyWithOpts: %w`, err) + } + return VerifyMLDSA(pk, payload, signature, mldsaOpts) +} diff --git a/vendor/github.com/lestrrat-go/dsig/sign.go b/vendor/github.com/lestrrat-go/dsig/sign.go index eb57f5eec6..7d8b9340b3 100644 --- a/vendor/github.com/lestrrat-go/dsig/sign.go +++ b/vendor/github.com/lestrrat-go/dsig/sign.go @@ -14,10 +14,34 @@ import ( // rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader. // Not all algorithms require this parameter, but it is included for consistency. // 99% of the time, you can pass nil for rr, and it will work fine. +// +// Deprecated in spirit: in the next major release of dsig (v2), the +// signature of Sign will change to match [SignWithOpts], i.e. it will +// accept an additional [crypto.SignerOpts] parameter immediately before +// rr. Callers that need to pass per-call options today should use +// [SignWithOpts]; callers that do not can keep using Sign and migrate +// when v2 ships by threading a nil opts argument through at the call +// site. func Sign(key any, alg string, payload []byte, rr io.Reader) ([]byte, error) { + return SignWithOpts(key, alg, payload, nil, rr) +} + +// SignWithOpts is like [Sign] but threads an optional [crypto.SignerOpts] +// through to the underlying signer. For built-in families (HMAC, RSA, +// ECDSA, EdDSA) the opts argument is ignored — those algorithms have no +// per-call options the dsig layer understands. For Custom-family +// algorithms whose Meta implements [SignerWithOpts], the opts are +// forwarded; otherwise the plain [Signer.Sign] method is called and +// opts are dropped. +// +// This function exists as a transitional API. In the next major release +// of dsig (v2) it will be removed and its signature will become the +// canonical shape of [Sign]. Code that uses SignWithOpts today will need +// a mechanical rename to Sign (and nothing else) when v2 ships. +func SignWithOpts(key any, alg string, payload []byte, opts crypto.SignerOpts, rr io.Reader) ([]byte, error) { info, ok := GetAlgorithmInfo(alg) if !ok { - return nil, fmt.Errorf(`dsig.Sign: unsupported signature algorithm %q`, alg) + return nil, fmt.Errorf(`dsig.SignWithOpts: unsupported signature algorithm %q`, alg) } switch info.Family { @@ -29,10 +53,10 @@ func Sign(key any, alg string, payload []byte, rr io.Reader) ([]byte, error) { return dispatchECDSASign(key, info, payload, rr) case EdDSAFamily: return dispatchEdDSASign(key, info, payload, rr) - case Custom: - return dispatchCustomSign(key, info, payload, rr) + case Custom, MLDSAFamily: + return dispatchMetaSign(key, info, payload, opts, rr) default: - return nil, fmt.Errorf(`dsig.Sign: unsupported signature family %q`, info.Family) + return nil, fmt.Errorf(`dsig.SignWithOpts: unsupported signature family %q`, info.Family) } } @@ -100,7 +124,10 @@ func dispatchECDSASign(key any, info AlgorithmInfo, payload []byte, rr io.Reader return SignECDSA(privkey, payload, meta.Hash, rr) } -func dispatchCustomSign(key any, info AlgorithmInfo, payload []byte, rr io.Reader) ([]byte, error) { +func dispatchMetaSign(key any, info AlgorithmInfo, payload []byte, opts crypto.SignerOpts, rr io.Reader) ([]byte, error) { + if signer, ok := info.Meta.(SignerWithOpts); ok { + return signer.SignWithOpts(key, payload, opts, rr) + } signer, ok := info.Meta.(Signer) if !ok { return nil, fmt.Errorf(`dsig.Sign: algorithm has no signer registered`) @@ -121,6 +148,14 @@ func dispatchCustomSign(key any, info AlgorithmInfo, payload []byte, rr io.Reade // // rr is an io.Reader that provides randomness for signing. If rr is nil, // it defaults to rand.Reader. +// +// Deprecated in spirit: in the next major release of dsig (v2), the +// signature of SignDigest will gain a [crypto.SignerOpts] parameter to +// align with [Sign]. No SignDigestWithOpts shim exists in v1 because +// Custom-family algorithms (the only ones that would benefit from +// per-call opts) are rejected outright today; once a DigestSigner +// interface for the Custom family is added, the opts parameter will +// appear at the same time. func SignDigest(key any, alg string, digest []byte, rr io.Reader) ([]byte, error) { info, ok := GetAlgorithmInfo(alg) if !ok { @@ -140,6 +175,11 @@ func SignDigest(key any, alg string, digest []byte, rr io.Reader) ([]byte, error return nil, fmt.Errorf(`dsig.SignDigest: EdDSA does not support digest-based signing`) case Custom: return nil, fmt.Errorf(`dsig.SignDigest: custom algorithms do not support digest-based signing`) + case MLDSAFamily: + // ML-DSA's pre-hashed mode takes a mu representative. That is a + // different thing from a plain digest; pass mu to Sign with + // crypto.MLDSAMu. + return nil, fmt.Errorf(`dsig.SignDigest: ML-DSA does not support digest-based signing`) default: return nil, fmt.Errorf(`dsig.SignDigest: unsupported signature family %q`, info.Family) } @@ -207,4 +247,3 @@ func dispatchECDSASignDigest(key any, info AlgorithmInfo, digest []byte, rr io.R } return PackECDSASignature(r, s, privkey.Curve.Params().BitSize) } - diff --git a/vendor/github.com/lestrrat-go/dsig/verify.go b/vendor/github.com/lestrrat-go/dsig/verify.go index 05ffe8e94f..5999e505c7 100644 --- a/vendor/github.com/lestrrat-go/dsig/verify.go +++ b/vendor/github.com/lestrrat-go/dsig/verify.go @@ -9,10 +9,33 @@ import ( ) // Verify verifies a digital signature using the specified key and algorithm. +// +// Deprecated in spirit: in the next major release of dsig (v2), the +// signature of Verify will change to match [VerifyWithOpts], i.e. it +// will accept an additional [crypto.SignerOpts] parameter at the end. +// Callers that need to pass per-call options today should use +// [VerifyWithOpts]; callers that do not can keep using Verify and +// migrate when v2 ships by threading a nil opts argument through at +// the call site. func Verify(key any, alg string, payload, signature []byte) error { + return VerifyWithOpts(key, alg, payload, signature, nil) +} + +// VerifyWithOpts is like [Verify] but threads an optional +// [crypto.SignerOpts] through to the underlying verifier. For built-in +// families (HMAC, RSA, ECDSA, EdDSA) the opts argument is ignored. For +// Custom-family algorithms whose Meta implements [VerifierWithOpts], +// the opts are forwarded; otherwise the plain [Verifier.Verify] method +// is called and opts are dropped. +// +// This function exists as a transitional API. In the next major release +// of dsig (v2) it will be removed and its signature will become the +// canonical shape of [Verify]. Code that uses VerifyWithOpts today will +// need a mechanical rename to Verify (and nothing else) when v2 ships. +func VerifyWithOpts(key any, alg string, payload, signature []byte, opts crypto.SignerOpts) error { info, ok := GetAlgorithmInfo(alg) if !ok { - return fmt.Errorf(`dsig.Verify: unsupported signature algorithm %q`, alg) + return fmt.Errorf(`dsig.VerifyWithOpts: unsupported signature algorithm %q`, alg) } switch info.Family { @@ -24,10 +47,10 @@ func Verify(key any, alg string, payload, signature []byte) error { return dispatchECDSAVerify(key, info, payload, signature) case EdDSAFamily: return dispatchEdDSAVerify(key, info, payload, signature) - case Custom: - return dispatchCustomVerify(key, info, payload, signature) + case Custom, MLDSAFamily: + return dispatchMetaVerify(key, info, payload, signature, opts) default: - return fmt.Errorf(`dsig.Verify: unsupported signature family %q`, info.Family) + return fmt.Errorf(`dsig.VerifyWithOpts: unsupported signature family %q`, info.Family) } } @@ -41,6 +64,14 @@ func Verify(key any, alg string, payload, signature []byte) error { // parameter is not used because it is already incorporated into the MAC. // // EdDSA and Custom families are not supported and return an error. +// +// Deprecated in spirit: in the next major release of dsig (v2), the +// signature of VerifyDigest will gain a [crypto.SignerOpts] parameter +// to align with [Verify]. No VerifyDigestWithOpts shim exists in v1 +// because Custom-family algorithms (the only ones that would benefit +// from per-call opts) are rejected outright today; once a +// DigestVerifier interface for the Custom family is added, the opts +// parameter will appear at the same time. func VerifyDigest(key any, alg string, digest, signature []byte) error { info, ok := GetAlgorithmInfo(alg) if !ok { @@ -62,6 +93,9 @@ func VerifyDigest(key any, alg string, digest, signature []byte) error { // TODO: a DigestVerifier interface (optional, checked here) would let // custom algorithms opt in to digest-based verification. return fmt.Errorf(`dsig.VerifyDigest: custom algorithms do not support digest-based verification`) + case MLDSAFamily: + // mu is derived from the message, so there is no digest to supply here. + return fmt.Errorf(`dsig.VerifyDigest: ML-DSA does not support digest-based verification`) default: return fmt.Errorf(`dsig.VerifyDigest: unsupported signature family %q`, info.Family) } @@ -196,7 +230,10 @@ func dispatchEdDSAVerify(key any, _ AlgorithmInfo, payload, signature []byte) er return VerifyEdDSA(pubkey, payload, signature) } -func dispatchCustomVerify(key any, info AlgorithmInfo, payload, signature []byte) error { +func dispatchMetaVerify(key any, info AlgorithmInfo, payload, signature []byte, opts crypto.SignerOpts) error { + if verifier, ok := info.Meta.(VerifierWithOpts); ok { + return verifier.VerifyWithOpts(key, payload, signature, opts) + } verifier, ok := info.Meta.(Verifier) if !ok { return fmt.Errorf(`dsig.Verify: algorithm has no verifier registered`) diff --git a/vendor/github.com/lestrrat-go/httprc/v3/Changes b/vendor/github.com/lestrrat-go/httprc/v3/Changes index 001c8c5444..32357a060c 100644 --- a/vendor/github.com/lestrrat-go/httprc/v3/Changes +++ b/vendor/github.com/lestrrat-go/httprc/v3/Changes @@ -1,6 +1,13 @@ Changes ======= +v3.0.6 07 Jun 2026 + * Back off on HTTP fetch failure (connection refused, DNS failure, + timeout) by scheduling the next refresh at now+MinInterval, instead + of re-dispatching the resource in a tight ~1s loop (#119, #130) + * Document anchoring of RegexpWhitelist patterns + (e.g. `^https://example\.com/`) and add a runnable example (#125) + v3.0.5 30 Mar 2026 * Fix periodic check deadlock when number of ready resources exceeds outgoing channel buffer, which caused circular wait between controller diff --git a/vendor/github.com/lestrrat-go/httprc/v3/README.md b/vendor/github.com/lestrrat-go/httprc/v3/README.md index 68239669a2..4f353d29d2 100644 --- a/vendor/github.com/lestrrat-go/httprc/v3/README.md +++ b/vendor/github.com/lestrrat-go/httprc/v3/README.md @@ -65,6 +65,58 @@ If the values obtained from the headers fall within that range, the value from t used. If the value is larger than the maximum, the maximum is used. If the value is lower than the minimum, the minimum is used. +# Whitelisting URLs + +By default the client allows all URLs. If you store resources whose URLs come from +untrusted sources, you should restrict what can be fetched by passing a whitelist +via `httprc.WithWhitelist`. Several implementations are provided: `BlockAllWhitelist`, +`InsecureWhitelist` (allow all), `MapWhitelist` (exact string match), and +`RegexpWhitelist`. + +## A note on `RegexpWhitelist` patterns + +`RegexpWhitelist` matches each URL with `(*regexp.Regexp).MatchString`, which returns +true when the pattern matches **any substring** of the URL. Patterns are **not** +anchored for you, so a naive pattern can allow far more than you intend. + +Consider the difference between these two patterns: + +```go +// BAD: unanchored, dots unescaped +regexp.MustCompile(`http://example.com`) + +// GOOD: anchored at the start, dots escaped, host terminated with `/` +regexp.MustCompile(`^https://example\.com/`) +``` + +The unanchored `http://example.com` pattern will happily allow URLs such as: + +- `http://example.com.attacker.com/evil` — the real host is `attacker.com`; the + pattern only required `example.com` to appear *somewhere*, and without a trailing + `/` it does not stop at the end of the host. +- `http://attacker.com/?redirect=http://example.com` — the pattern appears inside + the query string, so the match succeeds even though the host is `attacker.com`. +- `httpsX//exampleYcom` — `.` is the regular-expression "any character" + metacharacter, so the dots match more than literal dots. + +To pin a pattern to a specific origin: + +1. **Anchor the start** with `^` so the match must begin at the start of the URL. +2. **Escape the dots** (`\.`) so they only match a literal `.`. +3. **Terminate the host** with `/` so `example.com` cannot be extended into + `example.com.attacker.com`. + +A couple of edge cases to keep in mind: + +- Requiring the trailing `/` means the bare origin `https://example.com` (no path) + will not match. Add a second pattern such as `^https://example\.com$` if you need + to allow it. +- If your URLs may include a port, allow for it explicitly, e.g. + `^https://example\.com(:\d+)?/`. + +See `ExampleRegexpWhitelist` in `whitelist_example_test.go` for a runnable +demonstration of the difference between anchored and unanchored patterns. + # SYNOPSIS diff --git a/vendor/github.com/lestrrat-go/httprc/v3/resource.go b/vendor/github.com/lestrrat-go/httprc/v3/resource.go index 0f0d140d27..1e957cbfa3 100644 --- a/vendor/github.com/lestrrat-go/httprc/v3/resource.go +++ b/vendor/github.com/lestrrat-go/httprc/v3/resource.go @@ -232,6 +232,10 @@ func (r *ResourceBase[T]) Sync(ctx context.Context) error { traceSink.Put(ctx, fmt.Sprintf("httprc.Resource.Sync: fetching %q", r.u)) res, err := httpcl.Do(req) if err != nil { + // Schedule retry after MinInterval so that connection failures + // don't cause a tight retry loop (the resource's Next stays at + // epoch if we don't update it here). + r.SetNext(time.Now().Add(r.MinInterval())) return fmt.Errorf(`httprc.Resource.Sync: failed to execute HTTP request: %w`, err) } defer res.Body.Close() diff --git a/vendor/github.com/lestrrat-go/httprc/v3/whitelist.go b/vendor/github.com/lestrrat-go/httprc/v3/whitelist.go index 74ef2a1be6..9a55d6e5c5 100644 --- a/vendor/github.com/lestrrat-go/httprc/v3/whitelist.go +++ b/vendor/github.com/lestrrat-go/httprc/v3/whitelist.go @@ -49,6 +49,29 @@ func (InsecureWhitelist) IsAllowed(_ string) bool { return true } // RegexpWhitelist is a jwk.Whitelist object comprised of a list of *regexp.Regexp // objects. All entries in the list are tried until one matches. If none of the // *regexp.Regexp objects match, then the URL is deemed unallowed. +// +// Matching is performed using (*regexp.Regexp).MatchString, which succeeds when +// the pattern matches ANY substring of the URL — it is NOT anchored automatically. +// This has important security implications: a pattern like `http://example.com` +// will match URLs you almost certainly did not intend to allow, such as +// `http://example.com.attacker.com/` (the host is actually attacker.com) or +// `http://attacker.com/?u=http://example.com` (the pattern appears in the query). +// +// To restrict to a specific origin, anchor the pattern at the start with `^`, +// escape the dots in the host (`.` is the "any character" metacharacter in a +// regular expression), and terminate the host with a `/` so that it cannot be +// extended into a subdomain: +// +// // GOOD: only matches the example.com origin and its paths +// regexp.MustCompile(`^https://example\.com/`) +// +// // BAD: also matches example.com.attacker.com, attacker.com/?x=http://example.com, httpsX//exampleYcom, ... +// regexp.MustCompile(`http://example.com`) +// +// Note that requiring a trailing `/` means the bare origin URL `https://example.com` +// (no path) will not match; register an additional pattern such as +// `^https://example\.com$` if you need to allow it. Likewise, account for an +// optional port (e.g. `^https://example\.com(:\d+)?/`) if your URLs may include one. type RegexpWhitelist struct { mu sync.RWMutex patterns []*regexp.Regexp diff --git a/vendor/github.com/lestrrat-go/jwx/v3/BUILD b/vendor/github.com/lestrrat-go/jwx/v3/BUILD index 2759408882..5da405b23f 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/BUILD +++ b/vendor/github.com/lestrrat-go/jwx/v3/BUILD @@ -1,9 +1,15 @@ -load("@rules_go//go:def.bzl", "go_library", "go_test") load("@gazelle//:def.bzl", "gazelle") +load("@rules_go//go:def.bzl", "go_library", "go_test") # gazelle:prefix github.com/lestrrat-go/jwx/v3 # gazelle:go_naming_convention import_alias +# Scratch directories that are not part of the module. Without these, +# gazelle walks bazel's own output tree under .gauntlet and rewrites every +# BUILD file to point at copies of the repo it finds in there. +# gazelle:exclude .gauntlet +# gazelle:exclude .tmp + gazelle(name = "gazelle") go_library( diff --git a/vendor/github.com/lestrrat-go/jwx/v3/Changes b/vendor/github.com/lestrrat-go/jwx/v3/Changes index c5eeebf6dd..34318280c5 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/Changes +++ b/vendor/github.com/lestrrat-go/jwx/v3/Changes @@ -4,6 +4,62 @@ Changes v3 has many incompatibilities with v2. To see the full list of differences between v2 and v3, please read the Changes-v3.md file (https://github.com/lestrrat-go/jwx/blob/develop/v3/Changes-v3.md) +v3.3.0 8 Sep 2026 + * [jwt][jws][jwe][jwk] Custom claim, header, and JWK field names are now + JSON-escaped on output. Previously a name was written between the quotes + as is, so a name containing `"` could close its own member and add + members the application never set. For example, calling `Set` with the + name `x":0,"admin` produced a signed token containing `"admin":true`. + Every name now yields exactly one member, and names that need no + escaping serialize exactly as before. + + If your application accepts custom names from callers, an exact-match + allowlist was never affected. A blocklist of reserved names, or an + allowlist by namespace prefix, could be bypassed by this defect. Both are + reasonable designs; the bug was in the serializer. Prefer an exact-match + allowlist, and if you accept a prefix, require the rest of the name to be + a plain identifier. + + Fixed in v4.5.0 and v3.3.0. v2, v1, and v0 contain the same code and are + unmaintained; see SECURITY.md. (GHSA-4cf7-xm37-g63h) + + * [jws] Added `jws.WithStrictECDSA(bool)`, a `jws.Sign` option that rejects + anything RFC 7518 forbids for an ECDSA signature. Today that is Section + 3.4's binding of ES256 to P-256, ES384 to P-384, and ES512 to P-521, so + signing a P-521 key under `jwa.ES256()` fails instead of producing a JWS + that other JOSE implementations reject. + + The default is unchanged: without the option, a mismatched curve and + algorithm still sign exactly as before. `jws.Verify` is unaffected either + way. `jwt.Sign` callers can reach the option through + `jwt.WithSignOption(jws.WithStrictECDSA(true))`. (#2323) + +v3.2.0 27 Jul 2026 + * [jwe] Correct the JSON `"aad"` member so it contains only + BASE64URL of the external Additional Authenticated Data, rather than the + combined value used as the content-encryption AAD. Add + `jwe.WithAuthenticateData` for encrypting JSON JWEs with external AAD; + the value is included in the shared AEAD input for all recipients, and + compact serialization rejects non-empty external AAD. (#2276, #2278) + + * [jwk] Added opt-in retention of unparseable JWK Set entries. Passing + `jwk.WithStrictKeySetParsing(false)` to `jwk.Parse` (or setting it + globally via `jwk.Configure`) keeps an entry whose key type is not + understood — for example a post-quantum key published alongside + classical keys — as a `jwk.UnsupportedKey` placeholder instead of + failing the whole set (RFC 7517 §5). The placeholder preserves the + entry's original JSON (marshaling round-trips losslessly) and the + parse error via `Reason()`; use `jwk.IsUnsupportedKey` to detect one. + The default is unchanged: v3 still fails the whole set on the first + unparseable entry, so existing callers see no difference. The same + option exists in v4 with the opposite default (v4 retains by + default, v3 stays strict by default); call sites that pass the + option explicitly keep the same meaning across the v3→v4 migration. + Placeholders + are rejected by `jws`/`jwe` key selection, `jwk.Export`, + `jwk.AssignKeyID`, and `jwk.PublicSetOf` (which accepts a new + `jwk.WithOmitUnsupportedKeys(true)` to drop them). (#2263) + v3.1.1 7 May 2026 * [jws] Coordinated RFC 7797 `b64=false` handling pass: `jws.Verify` rejects payloads with `b64=false` unless `b64` is also listed in diff --git a/vendor/github.com/lestrrat-go/jwx/v3/SECURITY.md b/vendor/github.com/lestrrat-go/jwx/v3/SECURITY.md index 601dced5cd..a8d9f83d29 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/SECURITY.md +++ b/vendor/github.com/lestrrat-go/jwx/v3/SECURITY.md @@ -2,13 +2,21 @@ ## Supported Versions -Most recent two major versions will receive security updates +Security fixes are published for the versions marked below. The +[State of support](https://github.com/lestrrat-go/jwx/discussions/1079) +discussion is the canonical, up-to-date statement; this table summarizes it. | Version | Supported | | -------- | ------------------ | -| v3.x.x | :white_check_mark: | -| v2.x.x | :white_check_mark: | -| < v2.0.0 | :x: | +| v4.x.x | :white_check_mark: Current release | +| v3.x.x | :white_check_mark: Previous release; receives regular fixes | +| v2.x.x | :x: Unmaintained. Do not use | +| v1.x.x | :x: Unmaintained. Do not use | +| < v1.0.0 | :x: Unmaintained. Do not use | + +Unmaintained versions receive no fixes of any kind, including for issues +already fixed in a supported version. Each advisory names the versions that +carry the fix; a version not named there stays affected. ## Reporting a Vulnerability diff --git a/vendor/github.com/lestrrat-go/jwx/v3/internal/json/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/internal/json/BUILD.bazel index 4e2dbe12b7..29b9544554 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/internal/json/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/internal/json/BUILD.bazel @@ -1,4 +1,4 @@ -load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "json", @@ -9,7 +9,10 @@ go_library( ], importpath = "github.com/lestrrat-go/jwx/v3/internal/json", visibility = ["//:__subpackages__"], - deps = ["//internal/base64"], + deps = [ + "//internal/base64", + "//internal/tokens", + ], ) alias( @@ -17,3 +20,12 @@ alias( actual = ":json", visibility = ["//:__subpackages__"], ) + +go_test( + name = "json_test", + srcs = ["json_test.go"], + deps = [ + ":json", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/internal/json/json.go b/vendor/github.com/lestrrat-go/jwx/v3/internal/json/json.go index 4dec2b806c..91c5cbd1bc 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/internal/json/json.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/internal/json/json.go @@ -7,6 +7,7 @@ import ( "sync/atomic" "github.com/lestrrat-go/jwx/v3/internal/base64" + "github.com/lestrrat-go/jwx/v3/internal/tokens" ) var useNumber atomic.Uint32 @@ -164,3 +165,31 @@ func (dc *decodeCtx) Registry() *Registry { func (dc *decodeCtx) StrictStrings() bool { return dc.strictStrings } + +// WriteQuotedKey writes key as a quoted JSON object member name followed by +// the separating colon and a space. +// +// Member names come from public methods such as Set and Builder.Claim, so +// they may contain any byte, including `"`. A name copied raw between the +// quotes could end its own member and start further ones, so the serialized +// object would no longer match the one the caller built +// (GHSA-4cf7-xm37-g63h). A name that needs no escaping is written directly, +// which keeps the common path free of allocations. Every other name goes +// through the JSON string encoder. +func WriteQuotedKey(buf *bytes.Buffer, key string) error { + if tokens.IsJSONSafeASCII(key) { + buf.WriteByte(tokens.DoubleQuote) + buf.WriteString(key) + buf.WriteString(`": `) + return nil + } + + encoded, err := Marshal(key) + if err != nil { + return fmt.Errorf(`failed to encode object member name: %w`, err) + } + buf.Write(encoded) + buf.WriteByte(tokens.Colon) + buf.WriteByte(' ') + return nil +} diff --git a/vendor/github.com/lestrrat-go/jwx/v3/internal/keyconv/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/internal/keyconv/BUILD.bazel index d46d2f3814..f6bac26132 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/internal/keyconv/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/internal/keyconv/BUILD.bazel @@ -8,7 +8,6 @@ go_library( deps = [ "//jwk", "@com_github_lestrrat_go_blackmagic//:blackmagic", - "@org_golang_x_crypto//ed25519", ], ) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/internal/pool/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/internal/pool/BUILD.bazel index c48330e278..04dbd6697a 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/internal/pool/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/internal/pool/BUILD.bazel @@ -21,11 +21,9 @@ alias( go_test( name = "pool_test", - srcs = [ - "byte_slice_test.go", - ], + srcs = ["byte_slice_test.go"], deps = [ ":pool", "@com_github_stretchr_testify//require", ], -) \ No newline at end of file +) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/internal/tokens/tokens.go b/vendor/github.com/lestrrat-go/jwx/v3/internal/tokens/tokens.go index 2af3b88de1..864a86c42f 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/internal/tokens/tokens.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/internal/tokens/tokens.go @@ -11,6 +11,20 @@ const ( Period = '.' ) +// IsJSONSafeASCII reports whether s can be concatenated into a +// hand-built JSON string literal without escaping. Any byte that +// would require a JSON escape (control bytes, `"`, `\`) or any +// non-ASCII byte disqualifies the value. +func IsJSONSafeASCII(s string) bool { + for i := range len(s) { + c := s[i] + if c < 0x20 || c >= 0x7f || c == '"' || c == '\\' { + return false + } + } + return true +} + // Cryptographic key sizes const ( KeySize16 = 16 diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwa/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jwa/BUILD.bazel index 6d0af7efb3..cc8bd5bd26 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwa/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwa/BUILD.bazel @@ -24,20 +24,20 @@ go_library( go_test( name = "jwa_test", srcs = [ + "builtin_registry_test.go", "compression_gen_test.go", "content_encryption_gen_test.go", + "cross_kind_test.go", "elliptic_gen_test.go", "jwa_test.go", "key_encryption_gen_test.go", "key_type_gen_test.go", "options_gen_test.go", + "registry_snapshot_test.go", "signature_gen_test.go", ], embed = [":jwa"], - deps = [ - "@com_github_stretchr_testify//require", - "@com_github_lestrrat_go_option_v2//:option", - ], + deps = ["@com_github_stretchr_testify//require"], ) alias( diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jwe/BUILD.bazel index 0719efd2dc..82ef013db7 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/BUILD.bazel @@ -23,40 +23,47 @@ go_library( deps = [ "//cert", "//internal/base64", - "//transform", "//internal/json", - "//internal/tokens", "//internal/keyconv", "//internal/pool", + "//internal/tokens", "//jwa", "//jwe/internal/aescbc", - "//jwe/internal/cipher", "//jwe/internal/content_crypt", "//jwe/internal/keygen", "//jwe/jwebb", "//jwk", + "//transform", "@com_github_lestrrat_go_blackmagic//:blackmagic", "@com_github_lestrrat_go_option_v2//:option", - "@org_golang_x_crypto//pbkdf2", ], ) go_test( name = "jwe_test", srcs = [ + "bench_encrypt_test.go", + "encrypt_aad_test.go", "filter_test.go", + "fuzz_test.go", "gh402_test.go", "headers_test.go", + "jwe_aad_internal_test.go", + "jwe_crit_test.go", "jwe_test.go", + "message_aad_test.go", "message_test.go", "options_gen_test.go", + "recipient_headers_test.go", "speed_test.go", + "unsupported_key_test.go", ], embed = [":jwe"], deps = [ "//cert", "//internal/json", "//internal/jwxtest", + "//internal/tokens", "//jwa", "//jwk", "@com_github_stretchr_testify//require", diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/headers_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/jwe/headers_gen.go index 5390c2be45..53ee8ca5dd 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/headers_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/headers_gen.go @@ -962,9 +962,9 @@ func (h *stdHeaders) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(tokens.Comma) } - buf.WriteByte('"') - buf.WriteString(pair.Name) - buf.WriteString(`": `) + if err := json.WriteQuotedKey(buf, pair.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, pair.Name, err) + } buf.Write(pair.Value.([]byte)) } buf.WriteByte(tokens.CloseCurlyBracket) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc/BUILD.bazel index 4ed4c53fa3..20d86e5252 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc/BUILD.bazel @@ -12,7 +12,7 @@ go_test( name = "aescbc_test", srcs = ["aescbc_test.go"], embed = [":aescbc"], - deps = ["@com_github_stretchr_testify//require"] + deps = ["@com_github_stretchr_testify//require"], ) alias( diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/cipher/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/cipher/BUILD.bazel index cf642c744d..3a67551111 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/cipher/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/cipher/BUILD.bazel @@ -9,10 +9,9 @@ go_library( importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/cipher", visibility = ["//:__subpackages__"], deps = [ - "//jwa", + "//internal/tokens", "//jwe/internal/aescbc", "//jwe/internal/keygen", - "//internal/tokens", ], ) @@ -21,7 +20,6 @@ go_test( srcs = ["cipher_test.go"], deps = [ ":cipher", - "//jwa", "//internal/tokens", "@com_github_stretchr_testify//require", ], diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf/BUILD.bazel index 59aeb2cd27..3665c71f57 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf/BUILD.bazel @@ -9,7 +9,10 @@ go_library( go_test( name = "concatkdf_test", - srcs = ["concatkdf_test.go"], + srcs = [ + "bench_test.go", + "concatkdf_test.go", + ], embed = [":concatkdf"], deps = [ "//jwa", diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/keygen/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/keygen/BUILD.bazel index bde8eb68f7..91f5b3973b 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/keygen/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/keygen/BUILD.bazel @@ -1,4 +1,4 @@ -load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "keygen", @@ -9,10 +9,8 @@ go_library( importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/keygen", visibility = ["//:__subpackages__"], deps = [ - "//internal/ecutil", - "//jwa", - "//jwe/internal/concatkdf", "//internal/tokens", + "//jwe/internal/concatkdf", "//jwk", ], ) @@ -22,3 +20,12 @@ alias( actual = ":keygen", visibility = ["//jwe:__subpackages__"], ) + +go_test( + name = "keygen_test", + srcs = ["keygen_test.go"], + deps = [ + ":keygen", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/jwe.go b/vendor/github.com/lestrrat-go/jwx/v3/jwe/jwe.go index 706efaaa27..6bf043ecb6 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/jwe.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/jwe.go @@ -877,6 +877,7 @@ type encryptContext struct { compression jwa.CompressionAlgorithm format int pbes2Count int + authenticatedData []byte builders []*recipientBuilder protected Headers legacyHeaderMerging bool @@ -897,6 +898,7 @@ func freeEncryptContext(ec *encryptContext) *encryptContext { ec.compression = jwa.NoCompress() ec.format = fmtCompact ec.pbes2Count = 0 + ec.authenticatedData = nil ec.builders = ec.builders[:0] ec.protected = nil return ec @@ -949,6 +951,12 @@ func (ec *encryptContext) ProcessOptions(options []EncryptOption) error { return err } ec.compression = comp + case identAuthenticateData{}: + var aad []byte + if err := option.Value(&aad); err != nil { + return err + } + ec.authenticatedData = aad case identMergeProtectedHeaders{}: var mp bool if err := option.Value(&mp); err != nil { @@ -994,6 +1002,10 @@ func (ec *encryptContext) ProcessOptions(options []EncryptOption) error { } } + if len(ec.authenticatedData) > 0 && ec.format == fmtCompact { + return fmt.Errorf(`cannot use compact serialization with external authenticated data (use WithJSON())`) + } + if useRawCEK { if len(ec.builders) != 1 { return fmt.Errorf(`multiple recipients for ECDH-ES/DIRECT mode are not supported`) @@ -1193,12 +1205,13 @@ func (ec *encryptContext) EncryptMessage(payload []byte, cek []byte) ([]byte, er } } - aad, err := protected.Encode() + protectedAAD, err := protected.Encode() if err != nil { return nil, fmt.Errorf(`failed to base64 encode protected headers: %w`, err) } - iv, ciphertext, tag, err := contentcrypt.Encrypt(cek, payload, aad) + contentAAD := concatAAD(protectedAAD, base64.Encode(ec.authenticatedData)) + iv, ciphertext, tag, err := contentcrypt.Encrypt(cek, payload, contentAAD) if err != nil { return nil, fmt.Errorf(`failed to encrypt payload: %w`, err) } @@ -1207,7 +1220,7 @@ func (ec *encryptContext) EncryptMessage(payload []byte, cek []byte) ([]byte, er // pre-encoded headers and raw fields, avoiding the full Message // construction and redundant header re-encoding that Compact() does. if ec.format == fmtCompact { - return compactSerialize(aad, recipients[0].EncryptedKey(), iv, ciphertext, tag), nil + return compactSerialize(protectedAAD, recipients[0].EncryptedKey(), iv, ciphertext, tag), nil } msg := msgPool.Get() @@ -1228,6 +1241,11 @@ func (ec *encryptContext) EncryptMessage(payload []byte, cek []byte) ([]byte, er if err := msg.Set(TagKey, tag); err != nil { return nil, fmt.Errorf(`failed to set %s: %w`, TagKey, err) } + if len(ec.authenticatedData) > 0 { + if err := msg.Set(AuthenticatedDataKey, ec.authenticatedData); err != nil { + return nil, fmt.Errorf(`failed to set %s: %w`, AuthenticatedDataKey, err) + } + } switch ec.format { case fmtJSON: diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/jwebb/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jwe/jwebb/BUILD.bazel index c410a05cdf..03df94b61c 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/jwebb/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/jwebb/BUILD.bazel @@ -4,6 +4,7 @@ go_library( name = "jwebb", srcs = [ "content_cipher.go", + "jwebb.go", "key_decrypt_asymmetric.go", "key_decrypt_symmetric.go", "key_encrypt_asymmetric.go", @@ -16,11 +17,11 @@ go_library( deps = [ "//internal/keyconv", "//internal/pool", + "//internal/tokens", "//jwe/internal/cipher", "//jwe/internal/concatkdf", "//jwe/internal/content_crypt", "//jwe/internal/keygen", - "//internal/tokens", "@org_golang_x_crypto//pbkdf2", ], ) @@ -32,12 +33,18 @@ go_test( "jwebb_test.go", "keywrap_test.go", ], - embed = [":jwebb"], deps = [ + ":jwebb", "//internal/jwxtest", + "//internal/tokens", "//jwa", "//jwe/internal/keygen", - "//internal/tokens", "@com_github_stretchr_testify//require", ], -) \ No newline at end of file +) + +alias( + name = "go_default_library", + actual = ":jwebb", + visibility = ["//visibility:public"], +) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/key_provider.go b/vendor/github.com/lestrrat-go/jwx/v3/jwe/key_provider.go index 81bb5b6ec7..62fbf1c849 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/key_provider.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/key_provider.go @@ -108,6 +108,11 @@ type keySetProvider struct { } func (kp *keySetProvider) selectKey(sink KeySink, key jwk.Key, r Recipient, msg *Message) error { + if uk, ok := key.(jwk.UnsupportedKey); ok { + kid, _ := uk.KeyID() + return fmt.Errorf(`key %q has unsupported key type %q and cannot be used for decryption; an extension module may be required to parse it: %w`, kid, uk.KeyType().String(), uk.Reason()) + } + if usage, ok := key.KeyUsage(); ok { if usage != "" && usage != jwk.ForEncryption.String() { kid, _ := key.KeyID() diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/message.go b/vendor/github.com/lestrrat-go/jwx/v3/jwe/message.go index 22c6e6660a..583815ddd3 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/message.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/message.go @@ -227,32 +227,23 @@ func (m *Message) MarshalJSON() ([]byte, error) { }) } - var encodedProtectedHeaders []byte if h := m.ProtectedHeaders(); h != nil { v, err := h.Encode() if err != nil { return nil, fmt.Errorf(`failed to encode protected headers: %w`, err) } - encodedProtectedHeaders = v - if len(encodedProtectedHeaders) <= 2 { // '{}' - encodedProtectedHeaders = nil - } else { + if len(v) > 2 { // '{}' fields = append(fields, jsonKV{ Key: ProtectedHeadersKey, - Value: fmt.Sprintf("%q", encodedProtectedHeaders), + Value: fmt.Sprintf("%q", v), }) } } if aad := m.AuthenticatedData(); len(aad) > 0 { - aad = base64.Encode(aad) - if encodedProtectedHeaders != nil { - aad = concatAAD(encodedProtectedHeaders, aad) - } - buf.Reset() - if err := enc.Encode(aad); err != nil { + if err := enc.Encode(base64.EncodeToString(aad)); err != nil { return nil, fmt.Errorf(`failed to encode %s field: %w`, AuthenticatedDataKey, err) } fields = append(fields, jsonKV{ @@ -377,7 +368,7 @@ func (m *Message) UnmarshalJSON(buf []byte) error { if proxy.Headers != nil || len(proxy.EncryptedKey) > 0 { recipient := NewRecipient() - // `"heders"` could be empty. If that's the case, just skip the + // `"headers"` could be empty. If that's the case, just skip the // following unmarshaling step if proxy.Headers != nil { hdrs := NewHeaders() diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.go b/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.go index ab356f5d81..969e5bc24e 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.go @@ -1,6 +1,8 @@ package jwe import ( + "bytes" + "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/option/v2" @@ -76,6 +78,16 @@ func WithProtectedHeaders(h Headers) EncryptOption { return &encryptOption{option.New(identProtectedHeaders{}, cloned)} } +// WithAuthenticateData specifies the external Additional Authenticated Data +// to use when encrypting a JSON JWE. +// +// The data is copied before it is stored in the option. External Additional +// Authenticated Data is not supported by compact serialization; pass +// WithJSON() to select JSON serialization. +func WithAuthenticateData(aad []byte) EncryptOption { + return &encryptOption{option.New(identAuthenticateData{}, bytes.Clone(aad))} +} + type withKey struct { alg jwa.KeyAlgorithm key any diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.yaml b/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.yaml index 428e73e2b0..6bbb682c53 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.yaml +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.yaml @@ -60,6 +60,13 @@ options: skip_option: true - ident: ProtectedHeaders skip_option: true + - ident: AuthenticateData + skip_option: true + interface: EncryptOption + argument_type: '[]byte' + comment: | + WithAuthenticateData specifies the external Additional Authenticated Data + to use when encrypting a JSON JWE. - ident: PerRecipientHeaders skip_option: true - ident: KeyProvider diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/options_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/jwe/options_gen.go index 54962c5d04..11f1275b8a 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/options_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/options_gen.go @@ -169,6 +169,7 @@ type withKeySetSuboption struct { func (*withKeySetSuboption) withKeySetSuboption() {} +type identAuthenticateData struct{} type identCBCBufferSize struct{} type identCEK struct{} type identCompress struct{} @@ -193,6 +194,10 @@ type identProtectedHeaders struct{} type identRequireKid struct{} type identSerialization struct{} +func (identAuthenticateData) String() string { + return "WithAuthenticateData" +} + func (identCBCBufferSize) String() string { return "WithCBCBufferSize" } diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jwk/BUILD.bazel index 1bcb93a319..647b9a0ed5 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "cache.go", "convert.go", + "doc.go", "ecdsa.go", "ecdsa_gen.go", "errors.go", @@ -25,6 +26,7 @@ go_library( "set.go", "symmetric.go", "symmetric_gen.go", + "unsupported.go", "usage.go", "whitelist.go", "x509.go", @@ -35,14 +37,14 @@ go_library( "//cert", "//internal/base64", "//internal/ecutil", - "//transform", "//internal/json", "//internal/pool", - "//internal/tokens", + "//internal/tokens", "//jwa", "//jwk/ecdsa", "//jwk/internal/registry", "//jwk/jwkbb", + "//transform", "@com_github_lestrrat_go_blackmagic//:blackmagic", "@com_github_lestrrat_go_httprc_v3//:httprc", "@com_github_lestrrat_go_option_v2//:option", @@ -52,13 +54,20 @@ go_library( go_test( name = "jwk_test", srcs = [ + "bench_set_test.go", + "ecdsa_test.go", "filter_test.go", + "fuzz_test.go", "headers_test.go", - "jwk_internal_test.go", "jwk_test.go", + "jwk_zero_on_error_test.go", + "okp_length_test.go", "options_gen_test.go", "refresh_test.go", + "rsa_thumbprint_test.go", + "rsa_validate_test.go", "set_test.go", + "unsupported_test.go", "x5c_test.go", ], data = glob(["testdata/**"]), @@ -72,7 +81,7 @@ go_test( "//internal/tokens", "//jwa", "//jwk/ecdsa", - "//jws", + "//jwk/jwkunsafe", "@com_github_lestrrat_go_blackmagic//:blackmagic", "@com_github_lestrrat_go_httprc_v3//:httprc", "@com_github_lestrrat_go_httprc_v3//tracesink", diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/convert.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/convert.go index 4cf7bb6450..779e6b8ca5 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/convert.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/convert.go @@ -409,6 +409,11 @@ func bytesToKey(src any) (Key, error) { // especially when the object implements the `jwk.Key` interface via // embedding. func Export(key Key, dst any) error { + if uk, ok := key.(UnsupportedKey); ok { + kid, _ := uk.KeyID() + return fmt.Errorf(`jwk.Export: cannot export an unsupported key (kty=%q, kid=%q) that could not be parsed; an extension module may be required: %w`, uk.KeyType().String(), kid, uk.Reason()) + } + // dst better be a pointer rv := reflect.ValueOf(dst) if rv.Kind() != reflect.Ptr { diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/doc.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/doc.go index b4f6e164e2..b7f9a07c68 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/doc.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/doc.go @@ -29,7 +29,7 @@ // jws.Sign([]byte(`...`), jws.WithKey(jwa.RS256, jwkKey)) // jwe.Encrypt([]byte(`...`), jwe.WithKey(jwa.RSA_OAEP, jwkKey)) // -// See examples/jwk_parse_example_test.go and other files in the exmaples/ directory for more. +// See examples/jwk_parse_example_test.go and other files in the examples/ directory for more. // // # Advanced Usage: Registering a custom key type and conversion routines // diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/ecdsa/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jwk/ecdsa/BUILD.bazel index bf058aa649..8490b202a8 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/ecdsa/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/ecdsa/BUILD.bazel @@ -1,4 +1,4 @@ -load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "ecdsa", @@ -13,3 +13,13 @@ alias( actual = ":ecdsa", visibility = ["//visibility:public"], ) + +go_test( + name = "ecdsa_test", + srcs = ["ecdsa_test.go"], + embed = [":ecdsa"], + deps = [ + "//jwa", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/ecdsa_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/ecdsa_gen.go index a717e24bb9..301d14dbc0 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/ecdsa_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/ecdsa_gen.go @@ -734,9 +734,9 @@ func (h *ecdsaPublicKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(tokens.Comma) } - buf.WriteByte('"') - buf.WriteString(pair.Name) - buf.WriteString(`": `) + if err := json.WriteQuotedKey(buf, pair.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, pair.Name, err) + } buf.Write(pair.Value.([]byte)) } buf.WriteByte(tokens.CloseCurlyBracket) @@ -1558,9 +1558,9 @@ func (h *ecdsaPrivateKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(tokens.Comma) } - buf.WriteByte('"') - buf.WriteString(pair.Name) - buf.WriteString(`": `) + if err := json.WriteQuotedKey(buf, pair.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, pair.Name, err) + } buf.Write(pair.Value.([]byte)) } buf.WriteByte(tokens.CloseCurlyBracket) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/interface.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/interface.go index db6b8e31d5..27e5ee0fab 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/interface.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/interface.go @@ -121,12 +121,13 @@ type Set interface { } type set struct { - keys []Key - mu sync.RWMutex - dc DecodeCtx - privateParams map[string]any - maxKeys int // scratch cap consumed by UnmarshalJSON; 0 means use global default - rejectDuplicateKID bool // scratch flag consumed by UnmarshalJSON; false falls back to global + keys []Key + mu sync.RWMutex + dc DecodeCtx + privateParams map[string]any + maxKeys int // scratch cap consumed by UnmarshalJSON; 0 means use global default + rejectDuplicateKID *bool // scratch override consumed by UnmarshalJSON; nil falls back to global + strictKeySetParsing *bool // scratch override consumed by UnmarshalJSON; nil falls back to global } type PublicKeyer interface { diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/jwk.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/jwk.go index ba2db6cb48..3014158bbf 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/jwk.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/jwk.go @@ -44,8 +44,17 @@ var maxKeys atomic.Int64 // Tunable via WithRejectDuplicateKID / Configure(WithRejectDuplicateKID(...)). var rejectDuplicateKID atomic.Bool +// strictKeySetParsing controls how Parse/UnmarshalJSON treat an entry in +// a JWKS "keys" array that cannot be parsed. Default is true (fail-fast): +// the first unparseable entry fails the whole set, preserving v3's +// historical behavior. When false, the entry is retained as an +// UnsupportedKey placeholder (unless WithIgnoreParseError drops it). +// Tunable via WithStrictKeySetParsing / Configure(WithStrictKeySetParsing(...)). +var strictKeySetParsing atomic.Bool + func init() { maxKeys.Store(1000) + strictKeySetParsing.Store(true) if err := RegisterProbeField(reflect.StructField{ Name: "Kty", @@ -118,12 +127,17 @@ func Import(raw any) (Key, error) { // to remove any fields, if necessary. func PublicSetOf(v Set, options ...PublicSetOption) (Set, error) { var allowSymmetric bool + var omitUnsupported bool for _, option := range options { switch option.Ident() { case identAllowSymmetric{}: if err := option.Value(&allowSymmetric); err != nil { return nil, fmt.Errorf(`failed to retrieve AllowSymmetric option value: %w`, err) } + case identOmitUnsupportedKeys{}: + if err := option.Value(&omitUnsupported); err != nil { + return nil, fmt.Errorf(`failed to retrieve OmitUnsupportedKeys option value: %w`, err) + } } } @@ -135,6 +149,13 @@ func PublicSetOf(v Set, options ...PublicSetOption) (Set, error) { if !ok { return nil, fmt.Errorf(`key not found`) } + if uk, ok := k.(UnsupportedKey); ok { + if omitUnsupported { + continue + } + kid, _ := uk.KeyID() + return nil, fmt.Errorf(`jwk.PublicSetOf: input set contains an unsupported key (kty=%q, kid=%q, index=%d) that could not be parsed; there is no way to prove it holds no private material, so it is not passed through. Pass jwk.WithOmitUnsupportedKeys(true) to drop such entries from the output: %w`, uk.KeyType().String(), kid, i, uk.Reason()) + } if k.KeyType() == jwa.OctetSeq() && !allowSymmetric { kid, _ := k.KeyID() return nil, fmt.Errorf(`jwk.PublicSetOf: input set contains a symmetric key (kid=%q, index=%d); symmetric keys have no public form and would leak secret material if published. Remove symmetric keys from the set before calling PublicSetOf, or pass jwk.WithAllowSymmetric(true) to opt into legacy pass-through behavior`, kid, i) @@ -363,6 +384,7 @@ func Parse(src []byte, options ...ParseOption) (Set, error) { var pemDecoder PEMDecoder maxK := int(maxKeys.Load()) rejectDupKid := rejectDuplicateKID.Load() + strict := strictKeySetParsing.Load() for _, option := range options { switch option.Ident() { case identPEM{}: @@ -394,6 +416,10 @@ func Parse(src []byte, options ...ParseOption) (Set, error) { if err := option.Value(&rejectDupKid); err != nil { return nil, parseerr(`failed to retrieve RejectDuplicateKID option value: %w`, err) } + case identStrictKeySetParsing{}: + if err := option.Value(&strict); err != nil { + return nil, parseerr(`failed to retrieve StrictKeySetParsing option value: %w`, err) + } case identTypedField{}: var pair typedFieldPair // temporary var needed for typed field if err := option.Value(&pair); err != nil { @@ -459,9 +485,20 @@ func Parse(src []byte, options ...ParseOption) (Set, error) { setter.setMaxKeys(maxK) defer setter.setMaxKeys(0) } - if setter, ok := s.(interface{ setRejectDuplicateKID(bool) }); ok && rejectDupKid { - setter.setRejectDuplicateKID(true) - defer setter.setRejectDuplicateKID(false) + // Propagate the resolved reject-duplicate-KID flag. A pointer + // distinguishes "not set by Parse" (nil → Set.UnmarshalJSON uses the + // global default) from an explicit per-call true/false, so a per-call + // false overrides a global true. + if setter, ok := s.(interface{ setRejectDuplicateKID(*bool) }); ok { + setter.setRejectDuplicateKID(&rejectDupKid) + defer setter.setRejectDuplicateKID(nil) + } + // Propagate the resolved strict flag. A pointer distinguishes "not + // set by Parse" (nil → Set.UnmarshalJSON uses the global default of + // true) from an explicit per-call true/false. + if setter, ok := s.(interface{ setStrictKeySetParsing(*bool) }); ok { + setter.setStrictKeySetParsing(&strict) + defer setter.setStrictKeySetParsing(nil) } // Dispatch JWK-vs-JWKS up front. Set.UnmarshalJSON requires JWKS @@ -541,6 +578,11 @@ func ParseString(s string, options ...ParseOption) (Set, error) { // recomputation (for example, when upgrading to a stronger thumbprint hash // via `jwk.WithThumbprintHash`). func AssignKeyID(key Key, options ...AssignKeyIDOption) error { + if uk, ok := key.(UnsupportedKey); ok { + kid, _ := uk.KeyID() + return fmt.Errorf(`jwk.AssignKeyID: cannot assign a key ID to an unsupported key (kty=%q, kid=%q) that could not be parsed; its thumbprint cannot be computed: %w`, uk.KeyType().String(), kid, uk.Reason()) + } + hash := crypto.SHA256 var force bool for _, option := range options { @@ -845,6 +887,12 @@ func Configure(options ...GlobalOption) { continue } rejectDuplicateKID.Store(v) + case identStrictKeySetParsing{}: + var v bool + if err := option.Value(&v); err != nil { + continue + } + strictKeySetParsing.Store(v) } } diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/jwkbb/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jwk/jwkbb/BUILD.bazel index baf286688a..7c3b89dddf 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/jwkbb/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/jwkbb/BUILD.bazel @@ -27,4 +27,4 @@ alias( name = "go_default_library", actual = ":jwkbb", visibility = ["//visibility:public"], -) \ No newline at end of file +) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/okp.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/okp.go index ddbda60efa..34bad5c153 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/okp.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/okp.go @@ -40,7 +40,7 @@ func (k *okpPrivateKey) KeyKind() KeyKind { return okpKeyKind(k.Crv) } // Because this is an elliptic curve based Diffie Hellman protocol, it is also referred to // as ECDH. // -// OKP keys are used to represent private/public pairs of thse elliptic curve +// OKP keys are used to represent private/public pairs of these elliptic curve // keys. But note that the name just means Octet Key Pair. func (k *okpPublicKey) Import(rawKeyIf any) error { diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/okp_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/okp_gen.go index 3cfac02757..c6ca49bf99 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/okp_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/okp_gen.go @@ -684,9 +684,9 @@ func (h *okpPublicKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(tokens.Comma) } - buf.WriteByte('"') - buf.WriteString(pair.Name) - buf.WriteString(`": `) + if err := json.WriteQuotedKey(buf, pair.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, pair.Name, err) + } buf.Write(pair.Value.([]byte)) } buf.WriteByte(tokens.CloseCurlyBracket) @@ -1454,9 +1454,9 @@ func (h *okpPrivateKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(tokens.Comma) } - buf.WriteByte('"') - buf.WriteString(pair.Name) - buf.WriteString(`": `) + if err := json.WriteQuotedKey(buf, pair.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, pair.Name, err) + } buf.Write(pair.Value.([]byte)) } buf.WriteByte(tokens.CloseCurlyBracket) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/options.yaml b/vendor/github.com/lestrrat-go/jwx/v3/jwk/options.yaml index 765f3ea8e9..91cb0ada89 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/options.yaml +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/options.yaml @@ -280,6 +280,39 @@ options: This does not affect `(*Set).AddKey` — programmatic additions remain permissive (AddKey dedupes only by pointer identity). + - ident: StrictKeySetParsing + interface: GlobalParseOption + argument_type: bool + comment: | + WithStrictKeySetParsing controls what happens when an entry in a + JWK Set's "keys" array cannot be parsed. + + In v3 the default is true (strict): the first unparseable entry + fails the entire set, exactly as older v3 releases did. Existing + callers therefore see no change in behavior. + + Pass `WithStrictKeySetParsing(false)` to opt into retention. In + that mode an unparseable entry is neither dropped nor fatal — it + is kept in the set as a `jwk.UnsupportedKey` placeholder that + preserves the entry's original JSON and the error that prevented + parsing (RFC 7517 §5). This lets a set that mixes understood and + not-yet-understood keys (for example, post-quantum keys published + by an identity provider) remain usable for the keys you do + understand. + + Note the cross-version difference: v4 defaults to false (retain), + while v3 defaults to true (fail-fast). The option means the same + thing in both — only the default differs — so call sites that pass + it explicitly are source-compatible across the v3→v4 migration. + + This option is distinct from `WithIgnoreParseError`, which silently + *drops* unparseable entries instead of retaining placeholders. + `WithIgnoreParseError(true)` takes precedence regardless of the + strict setting: the entry is dropped rather than failing the set + or being retained. + + Can be set globally via `jwk.Configure()` or per-call on + `jwk.Parse()` / `jwk.ParseReader()` / `jwk.ParseString()`. - ident: AllowSymmetric interface: PublicSetOption argument_type: bool @@ -296,3 +329,18 @@ options: Pass `WithAllowSymmetric(true)` only if you are certain the resulting set will not be published. When true, symmetric keys are passed through unchanged, matching the legacy behavior. + - ident: OmitUnsupportedKeys + interface: PublicSetOption + argument_type: bool + comment: | + WithOmitUnsupportedKeys controls how `jwk.PublicSetOf` treats + `jwk.UnsupportedKey` placeholders in the input set. + + By default this option is false: a placeholder in the input is an + error, because there is no way to prove that an unparseable entry + contains no private material, and passing it through would risk + republishing a private key. + + Pass `WithOmitUnsupportedKeys(true)` to drop placeholders from the + output set instead. Use this when you intend to publish the public + set and want unparseable entries silently excluded. diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/options_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/options_gen.go index e90a9ee8d4..0121f66bf2 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/options_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/options_gen.go @@ -213,9 +213,11 @@ type identMaxFetchBodySize struct{} type identMaxKeys struct{} type identMinRSAModulusBits struct{} type identMinRSAPublicExponent struct{} +type identOmitUnsupportedKeys struct{} type identPEM struct{} type identPEMDecoder struct{} type identRejectDuplicateKID struct{} +type identStrictKeySetParsing struct{} type identStrictKeyUsage struct{} type identThumbprintHash struct{} type identWaitReady struct{} @@ -265,6 +267,10 @@ func (identMinRSAPublicExponent) String() string { return "WithMinRSAPublicExponent" } +func (identOmitUnsupportedKeys) String() string { + return "WithOmitUnsupportedKeys" +} + func (identPEM) String() string { return "WithPEM" } @@ -277,6 +283,10 @@ func (identRejectDuplicateKID) String() string { return "WithRejectDuplicateKID" } +func (identStrictKeySetParsing) String() string { + return "WithStrictKeySetParsing" +} + func (identStrictKeyUsage) String() string { return "WithStrictKeyUsage" } @@ -448,6 +458,21 @@ func WithMinRSAPublicExponent(v int) GlobalOption { return &globalOption{option.New(identMinRSAPublicExponent{}, v)} } +// WithOmitUnsupportedKeys controls how `jwk.PublicSetOf` treats +// `jwk.UnsupportedKey` placeholders in the input set. +// +// By default this option is false: a placeholder in the input is an +// error, because there is no way to prove that an unparseable entry +// contains no private material, and passing it through would risk +// republishing a private key. +// +// Pass `WithOmitUnsupportedKeys(true)` to drop placeholders from the +// output set instead. Use this when you intend to publish the public +// set and want unparseable entries silently excluded. +func WithOmitUnsupportedKeys(v bool) PublicSetOption { + return &publicSetOption{option.New(identOmitUnsupportedKeys{}, v)} +} + // WithPEM specifies that the input to `Parse()` is a PEM encoded key. // // This option is planned to be deprecated in the future. The plan is to @@ -488,6 +513,39 @@ func WithRejectDuplicateKID(v bool) GlobalParseOption { return &globalParseOption{option.New(identRejectDuplicateKID{}, v)} } +// WithStrictKeySetParsing controls what happens when an entry in a +// JWK Set's "keys" array cannot be parsed. +// +// In v3 the default is true (strict): the first unparseable entry +// fails the entire set, exactly as older v3 releases did. Existing +// callers therefore see no change in behavior. +// +// Pass `WithStrictKeySetParsing(false)` to opt into retention. In +// that mode an unparseable entry is neither dropped nor fatal — it +// is kept in the set as a `jwk.UnsupportedKey` placeholder that +// preserves the entry's original JSON and the error that prevented +// parsing (RFC 7517 §5). This lets a set that mixes understood and +// not-yet-understood keys (for example, post-quantum keys published +// by an identity provider) remain usable for the keys you do +// understand. +// +// Note the cross-version difference: v4 defaults to false (retain), +// while v3 defaults to true (fail-fast). The option means the same +// thing in both — only the default differs — so call sites that pass +// it explicitly are source-compatible across the v3→v4 migration. +// +// This option is distinct from `WithIgnoreParseError`, which silently +// *drops* unparseable entries instead of retaining placeholders. +// `WithIgnoreParseError(true)` takes precedence regardless of the +// strict setting: the entry is dropped rather than failing the set +// or being retained. +// +// Can be set globally via `jwk.Configure()` or per-call on +// `jwk.Parse()` / `jwk.ParseReader()` / `jwk.ParseString()`. +func WithStrictKeySetParsing(v bool) GlobalParseOption { + return &globalParseOption{option.New(identStrictKeySetParsing{}, v)} +} + // WithStrictKeyUsage specifies if during JWK parsing, the "use" field // should be confined to the values that have been registered via // `jwk.RegisterKeyType()`. By default this option is true, and the diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/rsa_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/rsa_gen.go index 3ef59aec6f..7b44c4e6e7 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/rsa_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/rsa_gen.go @@ -692,9 +692,9 @@ func (h *rsaPublicKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(tokens.Comma) } - buf.WriteByte('"') - buf.WriteString(pair.Name) - buf.WriteString(`": `) + if err := json.WriteQuotedKey(buf, pair.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, pair.Name, err) + } buf.Write(pair.Value.([]byte)) } buf.WriteByte(tokens.CloseCurlyBracket) @@ -1707,9 +1707,9 @@ func (h *rsaPrivateKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(tokens.Comma) } - buf.WriteByte('"') - buf.WriteString(pair.Name) - buf.WriteString(`": `) + if err := json.WriteQuotedKey(buf, pair.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, pair.Name, err) + } buf.Write(pair.Value.([]byte)) } buf.WriteByte(tokens.CloseCurlyBracket) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/set.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/set.go index 6b8c7aa564..498a9b754d 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/set.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/set.go @@ -211,10 +211,14 @@ func (s *set) setMaxKeys(n int) { s.maxKeys = n } -func (s *set) setRejectDuplicateKID(v bool) { +func (s *set) setRejectDuplicateKID(v *bool) { s.rejectDuplicateKID = v } +func (s *set) setStrictKeySetParsing(v *bool) { + s.strictKeySetParsing = v +} + // UnmarshalJSON streams a JWKS document. The "keys" array is read // element-by-element with the configured cap enforced BEFORE the // (cap+1)-th element is decoded — an attacker-controlled input length @@ -241,7 +245,14 @@ func (s *set) UnmarshalJSON(data []byte) error { if maxK <= 0 { maxK = int(maxKeys.Load()) } - rejectDupKid := s.rejectDuplicateKID || rejectDuplicateKID.Load() + rejectDupKid := rejectDuplicateKID.Load() + if s.rejectDuplicateKID != nil { + rejectDupKid = *s.rejectDuplicateKID + } + strict := strictKeySetParsing.Load() + if s.strictKeySetParsing != nil { + strict = *s.strictKeySetParsing + } dec := json.NewDecoder(bytes.NewReader(data)) LOOP: @@ -285,11 +296,23 @@ LOOP: } key, err := ParseKey(raw, options...) if err != nil { - if !ignoreParseError { + // ignoreParseError is checked first so its + // long-standing "drop the entry" behavior is + // unchanged regardless of the strict flag. Then: + // strict (v3 default) fails the whole set; otherwise + // the entry is retained as an UnsupportedKey + // placeholder (RFC 7517 §5, opt-in via + // WithStrictKeySetParsing(false)). + if ignoreParseError { + i++ + continue + } + if strict { return fmt.Errorf(`failed to decode key #%d in "keys": %w`, i, err) } - i++ - continue + // dec.Decode may reuse its buffer, so + // newUnsupportedKey clones the raw bytes. + key = newUnsupportedKey(raw, err) } if seenKIDs != nil { if kid, ok := key.KeyID(); ok && kid != "" { diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/symmetric_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/symmetric_gen.go index 900ed6537b..389ac9acf8 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/symmetric_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/symmetric_gen.go @@ -638,9 +638,9 @@ func (h *symmetricKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(tokens.Comma) } - buf.WriteByte('"') - buf.WriteString(pair.Name) - buf.WriteString(`": `) + if err := json.WriteQuotedKey(buf, pair.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, pair.Name, err) + } buf.Write(pair.Value.([]byte)) } buf.WriteByte(tokens.CloseCurlyBracket) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/unsupported.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/unsupported.go new file mode 100644 index 0000000000..57cf035af2 --- /dev/null +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/unsupported.go @@ -0,0 +1,318 @@ +package jwk + +import ( + "bytes" + "crypto" + "errors" + "fmt" + + "github.com/lestrrat-go/blackmagic" + "github.com/lestrrat-go/jwx/v3/cert" + "github.com/lestrrat-go/jwx/v3/internal/json" + "github.com/lestrrat-go/jwx/v3/jwa" +) + +// UnsupportedKey is a placeholder for a JWK Set entry that could not be +// parsed into a usable key. Per RFC 7517 §5, an entry inside a "keys" +// array whose key type is not understood, that is missing required +// members, or whose values are out of the supported range may be retained +// as an UnsupportedKey instead of failing the whole set. +// +// In v3 retention is opt-in: pass `jwk.WithStrictKeySetParsing(false)` to +// `jwk.Parse` (or set it globally via `jwk.Configure`). By default v3 +// still fails the whole set on the first unparseable entry, so existing +// callers see no change. (In v4 retention is the default; the option +// carries the same meaning in both, only the default differs.) +// +// A placeholder preserves the entry's original JSON — marshaling it with +// json.Marshal (alone or as part of its set) reproduces the entry, so a +// set containing one round-trips losslessly — and it preserves the error +// that prevented parsing (via [UnsupportedKey.Reason]). +// +// An UnsupportedKey cannot be used for any cryptographic operation: +// [UnsupportedKey.Thumbprint], [UnsupportedKey.PublicKey] and +// [UnsupportedKey.Validate] all return an error wrapping Reason(), and +// the key is rejected by cryptographic consumers such as +// jws.Verify / jwe.Decrypt with a descriptive per-key error. +// +// Use [IsUnsupportedKey] to check whether a key is a placeholder. Use a +// type assertion when you also need the placeholder's details: +// +// if uk, ok := key.(jwk.UnsupportedKey); ok { +// // key type key.KeyType() is not supported by this build; +// // uk.Reason() explains why, an extension module may be required. +// } +type UnsupportedKey interface { + Key + + // Reason returns the error that prevented the entry from parsing. + Reason() error + + // isUnsupportedKey seals this interface: only the placeholder type + // produced by this package implements it. Without the seal, any + // third-party Key that happens to define a Reason() error method + // would satisfy UnsupportedKey and be rejected as a placeholder by + // jwk.Export, jwk.AssignKeyID, and jws/jwe key selection. + isUnsupportedKey() +} + +// IsUnsupportedKey reports whether key is a placeholder retained for a +// JWK Set entry that could not be parsed. Only placeholders produced by +// this package satisfy the check; a user-defined Key type can never be +// mistaken for one. It is the sanctioned way to +// skip placeholders when iterating a set; type-assert to +// [UnsupportedKey] when you also need Reason(). +func IsUnsupportedKey(key Key) bool { + _, ok := key.(UnsupportedKey) + return ok +} + +// unsupportedKey is the concrete implementation of [UnsupportedKey]. +// +// It is effectively immutable after construction: the mutators [Set] and +// [Remove] return errors without modifying any field, so no locking is +// required for concurrent reads. The best-effort common members are +// parsed once in [newUnsupportedKey]. +type unsupportedKey struct { + raw []byte + reason error + + rawKty string + ktyPresent bool + algorithm *jwa.KeyAlgorithm + keyID *string +} + +var _ UnsupportedKey = &unsupportedKey{} +var _ Key = &unsupportedKey{} + +// newUnsupportedKey builds a placeholder from the verbatim entry bytes +// and the error that prevented parsing. raw is cloned because the +// decoder buffer it came from may be reused. +func newUnsupportedKey(raw []byte, reason error) *unsupportedKey { + // reason is always non-nil in practice (a placeholder only exists + // because ParseKey failed), but guard anyway so a nil can never + // reach the %w verbs that wrap Reason(). + if reason == nil { + reason = errors.New(`unspecified parse error`) + } + k := &unsupportedKey{ + raw: bytes.Clone(raw), + reason: reason, + } + k.parseBestEffort() + return k +} + +// parseBestEffort re-parses the minimum set of members needed to make +// the placeholder discoverable and nameable: "kid" (LookupKeyID and the +// duplicate-kid check), "kty" (error messages, KeyType()), and "alg" +// (error messages). A member that fails to parse is simply left absent. +// Everything else stays unparsed — the raw JSON is the entry's +// authoritative representation. +func (k *unsupportedKey) parseBestEffort() { + var fields map[string]json.RawMessage + if err := json.Unmarshal(k.raw, &fields); err != nil { + return + } + + if raw, ok := fields[KeyTypeKey]; ok { + var s string + if err := json.Unmarshal(raw, &s); err == nil { + k.rawKty = s + k.ktyPresent = true + } + } + if raw, ok := fields[KeyIDKey]; ok { + var s string + if err := json.Unmarshal(raw, &s); err == nil { + k.keyID = &s + } + } + if raw, ok := fields[AlgorithmKey]; ok { + var s string + if err := json.Unmarshal(raw, &s); err == nil { + if alg, err := jwa.KeyAlgorithmFrom(s); err == nil { + k.algorithm = &alg + } + } + } +} + +func (k *unsupportedKey) Reason() error { + return k.reason +} + +// isUnsupportedKey implements the [UnsupportedKey] interface seal. +func (k *unsupportedKey) isUnsupportedKey() {} + +// unsupportederr wraps the placeholder's Reason() in an error explaining +// that the operation cannot be performed on an unsupported key. +func (k *unsupportedKey) unsupportederr(op string) error { + kid := "" + if k.keyID != nil { + kid = *k.keyID + } + return fmt.Errorf(`jwk: cannot %s an unsupported key (kty=%q, kid=%q): the entry could not be parsed: %w`, op, k.rawKty, kid, k.reason) +} + +func (k *unsupportedKey) KeyType() jwa.KeyType { + if !k.ktyPresent { + return jwa.EmptyKeyType() + } + return jwa.NewKeyType(k.rawKty) +} + +func (k *unsupportedKey) Algorithm() (jwa.KeyAlgorithm, bool) { + if k.algorithm != nil { + return *k.algorithm, true + } + return nil, false +} + +func (k *unsupportedKey) KeyID() (string, bool) { + if k.keyID != nil { + return *k.keyID, true + } + return "", false +} + +// The remaining standard members are not mirrored: nothing consumes them +// on a placeholder (key selection rejects it before ever checking usage), +// and the raw JSON already carries them for round-tripping. + +func (k *unsupportedKey) KeyOps() (KeyOperationList, bool) { + return nil, false +} + +func (k *unsupportedKey) KeyUsage() (string, bool) { + return "", false +} + +func (k *unsupportedKey) X509CertChain() (*cert.Chain, bool) { + return nil, false +} + +func (k *unsupportedKey) X509CertThumbprint() (string, bool) { + return "", false +} + +func (k *unsupportedKey) X509CertThumbprintS256() (string, bool) { + return "", false +} + +func (k *unsupportedKey) X509URL() (string, bool) { + return "", false +} + +func (k *unsupportedKey) Has(name string) bool { + switch name { + case KeyTypeKey: + return k.ktyPresent + case AlgorithmKey: + return k.algorithm != nil + case KeyIDKey: + return k.keyID != nil + default: + return false + } +} + +// Get retrieves the best-effort common members (kty, alg, kid) into dst. +// Any other field is reported as absent — the raw JSON remains its only +// representation. +func (k *unsupportedKey) Get(name string, dst any) error { + switch name { + case KeyTypeKey: + if !k.ktyPresent { + return fmt.Errorf(`field %q not found`, name) + } + return blackmagic.AssignIfCompatible(dst, k.KeyType()) + case AlgorithmKey: + if k.algorithm == nil { + return fmt.Errorf(`field %q not found`, name) + } + return blackmagic.AssignIfCompatible(dst, *k.algorithm) + case KeyIDKey: + if k.keyID == nil { + return fmt.Errorf(`field %q not found`, name) + } + return blackmagic.AssignIfCompatible(dst, *k.keyID) + default: + return fmt.Errorf(`field %q not found`, name) + } +} + +func (k *unsupportedKey) Keys() []string { + keys := make([]string, 0, 3) + if k.ktyPresent { + keys = append(keys, KeyTypeKey) + } + if k.algorithm != nil { + keys = append(keys, AlgorithmKey) + } + if k.keyID != nil { + keys = append(keys, KeyIDKey) + } + return keys +} + +// Set always returns an error: the verbatim raw JSON is the single +// source of truth for serialization, so mutation is not allowed (it +// would make the marshaled form diverge from the accessor view). +func (k *unsupportedKey) Set(string, any) error { + return k.unsupportederr("modify") +} + +// Remove always returns an error, for the same reason as [Set]. +func (k *unsupportedKey) Remove(string) error { + return k.unsupportederr("modify") +} + +// Validate reports the retained parse error: a placeholder is by +// definition not a valid key. The error is wrapped in a key validation +// error so it classifies like every other built-in Key.Validate failure +// (jwk.IsKeyValidationError is true), while Reason() stays reachable +// through the wrapping chain. +func (k *unsupportedKey) Validate() error { + return NewKeyValidationError(k.unsupportederr("validate")) +} + +// Thumbprint always returns an error: RFC 7638 thumbprints require the +// per-kty required members, which are not understood for a placeholder. +func (k *unsupportedKey) Thumbprint(crypto.Hash) ([]byte, error) { + return nil, k.unsupportederr("compute the thumbprint of") +} + +// PublicKey always returns an error: whether the entry contains private +// material is unknowable, so no public projection can be derived safely. +func (k *unsupportedKey) PublicKey() (Key, error) { + return nil, k.unsupportederr("derive the public key of") +} + +// Clone returns an independent copy of the placeholder. Placeholders are +// first-class set members, so they clone like any other key. +func (k *unsupportedKey) Clone() (Key, error) { + dst := &unsupportedKey{ + raw: bytes.Clone(k.raw), + reason: k.reason, + rawKty: k.rawKty, + ktyPresent: k.ktyPresent, + } + if k.algorithm != nil { + tmp := *k.algorithm + dst.algorithm = &tmp + } + if k.keyID != nil { + tmp := *k.keyID + dst.keyID = &tmp + } + return dst, nil +} + +// MarshalJSON emits the verbatim raw JSON of the original entry. This is +// the round-trip guarantee: a set containing a placeholder re-serializes +// the unknown entry unchanged. +func (k *unsupportedKey) MarshalJSON() ([]byte, error) { + return bytes.Clone(k.raw), nil +} diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jws/BUILD.bazel index 32dbdd1881..5f4e5a90d9 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/BUILD.bazel @@ -15,9 +15,9 @@ go_library( "message.go", "options.go", "options_gen.go", - "signer.go", "sign_context.go", "signature_builder.go", + "signer.go", "streaming_detached.go", "verifier.go", "verify_context.go", @@ -27,14 +27,14 @@ go_library( deps = [ "//cert", "//internal/base64", - "//internal/ecutil", "//internal/json", - "//internal/tokens", "//internal/keyconv", "//internal/pool", + "//internal/tokens", "//jwa", "//jwk", - "//jws/internal/keytype", + "//jws/internal/jwsbb", + "//jws/internal/keyalg", "//jws/jwsbb", "//jws/legacy", "//transform", @@ -47,25 +47,34 @@ go_library( go_test( name = "jws_test", srcs = [ - "es256k_test.go", + "bench_marshal_test.go", + "bench_serialize_test.go", "filter_test.go", + "format_detect_test.go", + "fuzz_test.go", + "headers_nil_test.go", "headers_test.go", + "jws_crit_test.go", + "jws_internal_test.go", "jws_test.go", + "key_provider_test.go", "message_test.go", "options_gen_test.go", "signer_test.go", "streaming_detached_test.go", + "unsupported_key_test.go", ], embed = [":jws"], deps = [ "//cert", "//internal/base64", - "//internal/ecutil", "//internal/json", "//internal/jwxtest", + "//internal/tokens", "//jwa", "//jwk", - "//jwt", + "//jws/legacy", + "@com_github_lestrrat_go_dsig//:dsig", "@com_github_lestrrat_go_httprc_v3//:httprc", "@com_github_stretchr_testify//require", ], diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/errors.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/errors.go index e4445bd547..91577a014b 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/errors.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/errors.go @@ -3,6 +3,8 @@ package jws import ( "errors" "fmt" + + "github.com/lestrrat-go/jwx/v3/jws/internal/keyalg" ) // errCritPresent is returned by VerifyCompactFast when the protected @@ -45,25 +47,17 @@ func ErrB64Present() error { return errB64Present } -// errUnclassifiableKey is the common sentinel for AlgorithmsForKey -// failures: the key shape cannot be matched to any registered key type -// for signing. Three different code paths land here — Import-failed, -// kty-not-registered, and shape-rejected (e.g. ecdh) — but they're all -// the same logical "we can't classify this key" outcome from the -// caller's perspective. Wrap-with-this lets callers branch on -// errors.Is(err, jws.ErrUnclassifiableKey()) instead of pattern-matching -// the three error-message shapes the function previously emitted. -var errUnclassifiableKey = errors.New("jws: key cannot be classified for signing") - -// ErrUnclassifiableKey returns the sentinel that jws.AlgorithmsForKey -// (and indirectly jws.Sign / jws.Verify when option-time validation -// fails) wraps when the supplied key cannot be matched to a registered -// key type. Branching on this sentinel is the right way to ask "is this -// a 'we can't tell what this key is' failure?" — the wrapping error -// also carries the concrete %T or %q diagnostic in its message, so the -// human-readable error stays specific. +// ErrUnclassifiableKey returns the sentinel that jws.Sign and jws.Verify +// wrap when option-time validation cannot match the supplied key to a +// registered key type. Branching on this sentinel is the right way to ask +// "is this a 'we can't tell what this key is' failure?" — the wrapping +// error also carries the concrete %T or %q diagnostic in its message, so +// the human-readable error stays specific. +// +// The sentinel itself lives in jws/internal/keyalg, which owns key +// classification. func ErrUnclassifiableKey() error { - return errUnclassifiableKey + return keyalg.ErrUnclassifiableKey } type signError struct { diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/headers_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/headers_gen.go index 0628e626d2..04cf66909f 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/headers_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/headers_gen.go @@ -812,9 +812,9 @@ func (h *stdHeaders) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(tokens.Comma) } - buf.WriteByte('"') - buf.WriteString(pair.Name) - buf.WriteString(`": `) + if err := json.WriteQuotedKey(buf, pair.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, pair.Name, err) + } buf.Write(pair.Value.([]byte)) } buf.WriteByte(tokens.CloseCurlyBracket) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/jwsbb/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/jwsbb/BUILD.bazel new file mode 100644 index 0000000000..b762d41a4b --- /dev/null +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/jwsbb/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "jwsbb", + srcs = ["ecdsacurve.go"], + importpath = "github.com/lestrrat-go/jwx/v3/jws/internal/jwsbb", + visibility = ["//jws:__subpackages__"], + deps = ["@com_github_lestrrat_go_dsig//:dsig"], +) + +alias( + name = "go_default_library", + actual = ":jwsbb", + visibility = ["//jws:__subpackages__"], +) + +go_test( + name = "jwsbb_test", + srcs = ["ecdsacurve_test.go"], + deps = [ + ":jwsbb", + "@com_github_lestrrat_go_dsig//:dsig", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/jwsbb/ecdsacurve.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/jwsbb/ecdsacurve.go new file mode 100644 index 0000000000..c8715e6c6d --- /dev/null +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/jwsbb/ecdsacurve.go @@ -0,0 +1,117 @@ +package jwsbb + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "fmt" + + "github.com/lestrrat-go/dsig" +) + +// This file enforces the RFC 7518 Section 3.4 binding between an ECDSA JWS +// algorithm and the curve its key must sit on (ES256/P-256, ES384/P-384, +// ES512/P-521). It is sign-side only, and jws reaches it only when the caller +// passes jws.WithStrictECDSA(true). +// +// The check is opt-in because the old permissive behavior is an interop +// defect, not a security hole: the signer controls both the key and the +// algorithm at the call site, and the JWS it produces is a genuine signature +// under its own key. Turning the check on by default would break working +// callers to fix a conformance problem they may not have. +// +// jws.Verify never reaches this file at all. It infers algorithms from a key +// when a JWKS entry carries no "alg" (see jws/internal/keyalg.Candidates and +// the deprecated jws.AlgorithmsForKey, whose godoc freezes that inference), +// and it must stay exactly as permissive as it is today. + +// RequireECDSACurve reports whether key sits on the curve RFC 7518 Section +// 3.4 binds joseAlg to. It returns nil -- never an error -- when the binding +// cannot be established: dsigAlg is an ECDSA-family algorithm outside the +// three JOSE built-ins (e.g. ES256K, whether from the jwx_es256k build tag +// or an extension module), or key carries no readable curve. Only positive +// evidence of a mismatch is an error. +func RequireECDSACurve(joseAlg, dsigAlg string, key any) error { + want, ok := curveForDsigAlgorithm(dsigAlg) + if !ok { + return nil + } + + pub := ecdsaPublicKeyOf(key) + if pub == nil || pub.Curve == nil { + return nil + } + + if pub.Curve == want { + return nil + } + gotParams := pub.Curve.Params() + if gotParams == nil { + return nil + } + wantParams := want.Params() + if wantParams != nil && gotParams.Name == wantParams.Name { + return nil + } + + return fmt.Errorf(`ECDSA curve mismatch: key is on %s, algorithm %q requires %s`, + curveName(pub.Curve), joseAlg, curveName(want)) +} + +// curveForDsigAlgorithm maps a dsig ECDSA algorithm name to the curve RFC +// 7518 Section 3.4 requires for it. Only the three JOSE built-ins are +// known; anything else (custom-curve extensions such as ES256K) misses +// deliberately, so the caller passes the key through unchecked. +func curveForDsigAlgorithm(dsigAlg string) (elliptic.Curve, bool) { + switch dsigAlg { + case dsig.ECDSAWithP256AndSHA256: + return elliptic.P256(), true + case dsig.ECDSAWithP384AndSHA384: + return elliptic.P384(), true + case dsig.ECDSAWithP521AndSHA512: + return elliptic.P521(), true + default: + return nil, false + } +} + +// ecdsaPublicKeyOf extracts an *ecdsa.PublicKey from key, or nil when key is +// not (or does not expose) an ECDSA key. Callers pass an already-converted +// key (jwk.Key unwrapping happens before this is called), so only the raw Go +// crypto forms and an opaque crypto.Signer are handled here. +func ecdsaPublicKeyOf(key any) *ecdsa.PublicKey { + switch k := key.(type) { + case *ecdsa.PrivateKey: + if k == nil { + return nil + } + return &k.PublicKey + case ecdsa.PrivateKey: + return &k.PublicKey + case *ecdsa.PublicKey: + return k + case ecdsa.PublicKey: + return &k + case crypto.Signer: + pub, ok := k.Public().(*ecdsa.PublicKey) + if !ok { + return nil + } + return pub + default: + return nil + } +} + +// curveName returns crv.Params().Name, guarding a nil Params() the same way +// the comparison in RequireECDSACurve does. +func curveName(crv elliptic.Curve) string { + if crv == nil { + return "" + } + params := crv.Params() + if params == nil { + return "" + } + return params.Name +} diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/keyalg/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/keyalg/BUILD.bazel new file mode 100644 index 0000000000..97313cd6ca --- /dev/null +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/keyalg/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "keyalg", + srcs = ["keyalg.go"], + importpath = "github.com/lestrrat-go/jwx/v3/jws/internal/keyalg", + visibility = ["//jws:__subpackages__"], + deps = [ + "//jwa", + "//jwk", + ], +) + +alias( + name = "go_default_library", + actual = ":keyalg", + visibility = ["//jws:__subpackages__"], +) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/keyalg/keyalg.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/keyalg/keyalg.go new file mode 100644 index 0000000000..09458230bc --- /dev/null +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/keyalg/keyalg.go @@ -0,0 +1,259 @@ +// Package keyalg works out which signature algorithms a key can be used +// with, and owns the registration tables it reads to decide. +// +// The answer is a guess, on purpose. jws.Verify uses it to pick +// algorithms to try when a JWKS key has no "alg" field, and option +// handling uses it to catch a key that clearly does not go with the +// algorithm asked for. It is not a check for whether a key and an +// algorithm are a valid pair, and the list can be wider than any one RFC +// allows for a given key. +// +// This package is internal to jwx. The jws package still has +// AlgorithmsForKey, a one-line wrapper over [Candidates], but that is +// deprecated and was never meant for callers outside jwx. Everything in +// the tree calls this package instead. +package keyalg + +import ( + "crypto" + "crypto/ecdh" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "errors" + "fmt" + "slices" + "sync" + + "github.com/lestrrat-go/jwx/v3/jwa" + "github.com/lestrrat-go/jwx/v3/jwk" +) + +// ErrUnclassifiableKey is the common sentinel for [Candidates] failures: +// the key shape cannot be matched to any registered key type for signing. +// Three different code paths land here — Import-failed, kty-not-registered, +// and shape-rejected (e.g. ecdh) — but they're all the same logical "we +// can't classify this key" outcome from the caller's perspective. +// Wrap-with-this lets callers branch on errors.Is instead of +// pattern-matching the three error-message shapes. +// +// The jws package re-exports this through jws.ErrUnclassifiableKey(). +var ErrUnclassifiableKey = errors.New("jws: key cannot be classified for signing") + +// curver is implemented by jwk.Key types that carry curve information. +type curver interface { + Crv() (jwa.EllipticCurveAlgorithm, bool) +} + +var mu sync.RWMutex +var keyTypeToAlgorithms = make(map[jwa.KeyType][]jwa.SignatureAlgorithm) +var algorithmToKeyTypes = make(map[jwa.SignatureAlgorithm][]jwa.KeyType) +var curveToAlgorithms = make(map[jwa.EllipticCurveAlgorithm][]jwa.SignatureAlgorithm) + +func init() { + RegisterForKeyType(jwa.OKP(), jwa.EdDSA()) + RegisterForCurve(jwa.Ed25519(), jwa.EdDSAEd25519()) + for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()} { + RegisterForKeyType(jwa.OctetSeq(), alg) + } + for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()} { + RegisterForKeyType(jwa.RSA(), alg) + } + for _, alg := range []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()} { + RegisterForKeyType(jwa.EC(), alg) + } +} + +// RegisterForKeyType records alg as usable with keys of type kty. +// +// This backs jws.RegisterAlgorithmForKeyType, which extension modules +// call from init() to add their own algorithms. +func RegisterForKeyType(kty jwa.KeyType, alg jwa.SignatureAlgorithm) { + mu.Lock() + defer mu.Unlock() + keyTypeToAlgorithms[kty] = append(keyTypeToAlgorithms[kty], alg) + if !slices.Contains(algorithmToKeyTypes[alg], kty) { + algorithmToKeyTypes[alg] = append(algorithmToKeyTypes[alg], kty) + } +} + +// RegisterForCurve scopes alg to the given elliptic curve. When +// [Candidates] can determine a key's curve, an algorithm registered under +// some curve is offered only for keys on that curve, instead of for every +// key of its key type. +// +// This backs jws.RegisterAlgorithmForCurve. It is append-only and +// deduplicates entries, so builtin registrations cannot be overwritten by +// external modules. +func RegisterForCurve(crv jwa.EllipticCurveAlgorithm, alg jwa.SignatureAlgorithm) { + mu.Lock() + defer mu.Unlock() + if slices.Contains(curveToAlgorithms[crv], alg) { + return + } + curveToAlgorithms[crv] = append(curveToAlgorithms[crv], alg) +} + +// KeyTypesFor returns the key types registered for alg. The reverse index +// is maintained at registration time so this is an O(1) lookup. It returns +// nil if no key type is registered for alg, which signals callers to skip +// any prefilter. +func KeyTypesFor(alg jwa.SignatureAlgorithm) []jwa.KeyType { + mu.RLock() + defer mu.RUnlock() + // Copy so the caller can safely iterate without holding the lock; + // RegisterForKeyType may append concurrently after we return. + // Typical length is 1. + return slices.Clone(algorithmToKeyTypes[alg]) +} + +// Candidates returns the signature algorithms that key could be used +// with. It only takes into consideration keys/algorithms for verification +// purposes, as this is the only usage where one may need to dynamically +// figure out which method to use. +// +// When the key's curve is known, algorithms registered for that curve via +// [RegisterForCurve] are combined with key-type-level algorithms to +// produce a more precise result. The curve is known for a [jwk.Key] that +// has a Crv() method, for raw ed25519 keys, and for any raw key that +// reaches the [jwk.Import] fallback below. +// +// ECDSA is the exception. A raw [ecdsa.PublicKey] or [ecdsa.PrivateKey] is +// classified by key type alone and its Curve field is never read. No +// builtin registration binds P-256, P-384, or P-521 to an algorithm +// either, so every EC key reports the full ES* list no matter which curve +// it sits on. RFC 7518 Section 3.4 is stricter than that; jws.Sign +// enforces it only when the caller passes jws.WithStrictECDSA(true). +// +// Accepted key shapes (resolved in order): +// +// 1. [jwk.Key] — kty is read directly; if the implementation also exposes +// Crv(), the curve refines the result. +// 2. Stdlib crypto types: [rsa.PublicKey] / [rsa.PrivateKey] (and pointer +// forms), [ecdsa.PublicKey] / [ecdsa.PrivateKey] (and pointer forms), +// [ed25519.PublicKey], [ed25519.PrivateKey], and [byte] slices for +// symmetric keys. +// 3. [crypto/ecdh.PublicKey] / [crypto/ecdh.PrivateKey] (and pointer +// forms) — explicitly rejected; ECDH keys are key-agreement only. +// Returns an error wrapping [ErrUnclassifiableKey]. +// 4. [crypto.Signer] (e.g. KMS-backed adapters) — resolved once via +// .Public(); the public key is then re-classified through tiers 1–2 +// or the [jwk.Import] fallback below. To prevent infinite recursion, +// a Signer whose .Public() is itself a Signer is left for the +// downstream dispatcher to handle. +// 5. [jwk.Import] fallback — anything else is offered to the import +// registry, allowing extension modules to register their own raw key +// types. +// +// All "we cannot classify this key" failures wrap [ErrUnclassifiableKey], +// so callers can branch with errors.Is rather than pattern-matching error +// strings. The wrapping error keeps the concrete %T or %q diagnostic in +// its message for human readers. +func Candidates(key any) ([]jwa.SignatureAlgorithm, error) { + var kty jwa.KeyType + var crv jwa.EllipticCurveAlgorithm + var hasCrv bool + + switch key := key.(type) { + case jwk.Key: + kty = key.KeyType() + if ck, ok := key.(curver); ok { + crv, hasCrv = ck.Crv() + } + case rsa.PublicKey, *rsa.PublicKey, rsa.PrivateKey, *rsa.PrivateKey: + kty = jwa.RSA() + case ecdsa.PublicKey, *ecdsa.PublicKey, ecdsa.PrivateKey, *ecdsa.PrivateKey: + kty = jwa.EC() + case ed25519.PublicKey, ed25519.PrivateKey: + kty = jwa.OKP() + crv = jwa.Ed25519() + hasCrv = true + case *ecdh.PublicKey, ecdh.PublicKey, *ecdh.PrivateKey, ecdh.PrivateKey: + // ecdh keys are for key agreement (X25519/X448), not signing. + // Reject at the API boundary instead of returning a misleading + // algorithm list that would fail deeper in the signing stack. + return nil, fmt.Errorf(`%w: key type %T cannot be used for signing (ecdh keys are key-agreement only)`, ErrUnclassifiableKey, key) + case []byte: + kty = jwa.OctetSeq() + default: + // For crypto.Signer from external packages (e.g. KMS-backed signers), + // extract the underlying public key type via .Public(). + // Standard library types (*rsa.PrivateKey, etc.) are already handled + // by the concrete cases above. + var signerPubErr error + if signer, ok := key.(crypto.Signer); ok { + pub := signer.Public() + // Guard: only recurse if the public key is not itself a crypto.Signer, + // to prevent infinite recursion from pathological implementations. + if _, isSigner := pub.(crypto.Signer); !isSigner { + algs, err := Candidates(pub) + if err == nil { + return algs, nil + } + // Save the inner classification error so a + // downstream Import-fallback failure can surface + // both diagnostics. A successful Import discards + // signerPubErr — only the eventual failure path + // joins them. + signerPubErr = err + } + } + imported, err := jwk.Import(key) + if err != nil { + outer := fmt.Errorf(`%w: unknown key type %T`, ErrUnclassifiableKey, key) + if signerPubErr != nil { + return nil, errors.Join(outer, signerPubErr) + } + return nil, outer + } + kty = imported.KeyType() + if ck, ok := imported.(curver); ok { + crv, hasCrv = ck.Crv() + } + } + + mu.RLock() + defer mu.RUnlock() + + ktyAlgs, ok := keyTypeToAlgorithms[kty] + if !ok { + return nil, fmt.Errorf(`%w: unregistered key type %q`, ErrUnclassifiableKey, kty) + } + + // If we know the curve and there are curve-specific registrations, + // return only key-type-level algorithms (those not registered under + // any curve) plus curve-specific algorithms for this curve. + if hasCrv { + crvAlgs := curveToAlgorithms[crv] + return filterForCurve(ktyAlgs, crvAlgs), nil + } + + return ktyAlgs, nil +} + +// filterForCurve returns the subset of ktyAlgs that are not registered +// under any curve (i.e., generic for the key type) plus the curve-specific +// algorithms from crvAlgs. +func filterForCurve(ktyAlgs, crvAlgs []jwa.SignatureAlgorithm) []jwa.SignatureAlgorithm { + var result []jwa.SignatureAlgorithm + + // Add key-type-level algorithms that are not claimed by any curve + for _, alg := range ktyAlgs { + if !isRegisteredUnderAnyCurve(alg) { + result = append(result, alg) + } + } + + // Add curve-specific algorithms + result = append(result, crvAlgs...) + return result +} + +func isRegisteredUnderAnyCurve(alg jwa.SignatureAlgorithm) bool { + for _, algs := range curveToAlgorithms { + if slices.Contains(algs, alg) { + return true + } + } + return false +} diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/keytype/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/keytype/BUILD.bazel index eb8bd94acb..f43eec37ac 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/keytype/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/internal/keytype/BUILD.bazel @@ -9,3 +9,9 @@ go_library( "//jwk", ], ) + +alias( + name = "go_default_library", + actual = ":keytype", + visibility = ["//jws:__subpackages__"], +) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/jws.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/jws.go index 99bf78581a..90fc2d4c47 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/jws.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/jws.go @@ -27,11 +27,6 @@ package jws import ( "crypto" - "crypto/ecdh" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" - "errors" "fmt" "io" "slices" @@ -46,6 +41,7 @@ import ( "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" + "github.com/lestrrat-go/jwx/v3/jws/internal/keyalg" "github.com/lestrrat-go/jwx/v3/jws/jwsbb" ) @@ -535,57 +531,29 @@ func RegisterCustomField(name string, object any) { registry.Register(name, object) } -// curver is implemented by jwk.Key types that carry curve information. -type curver interface { - Crv() (jwa.EllipticCurveAlgorithm, bool) -} - -// Helpers for signature verification -var muAlgorithmMaps sync.RWMutex -var keyTypeToAlgorithms = make(map[jwa.KeyType][]jwa.SignatureAlgorithm) -var algorithmToKeyTypes = make(map[jwa.SignatureAlgorithm][]jwa.KeyType) -var curveToAlgorithms = make(map[jwa.EllipticCurveAlgorithm][]jwa.SignatureAlgorithm) - -func init() { - RegisterAlgorithmForKeyType(jwa.OKP(), jwa.EdDSA()) - RegisterAlgorithmForCurve(jwa.Ed25519(), jwa.EdDSAEd25519()) - for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()} { - RegisterAlgorithmForKeyType(jwa.OctetSeq(), alg) - } - for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()} { - RegisterAlgorithmForKeyType(jwa.RSA(), alg) - } - for _, alg := range []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()} { - RegisterAlgorithmForKeyType(jwa.EC(), alg) - } -} - // RegisterAlgorithmForKeyType registers an additional algorithm as valid for -// the given key type. This is used internally by init() and can also be called -// from external modules that provide support for additional algorithms (e.g. Ed448). +// the given key type. This is used internally to register the builtin +// algorithms, and can also be called from external modules that provide +// support for additional algorithms (e.g. Ed448). +// +// Registering an algorithm here makes [Sign] and [Verify] accept it for keys +// of that type, and makes it a candidate when a JWKS key carrying no "alg" +// member is verified under jws.WithInferAlgorithmFromKey(true). func RegisterAlgorithmForKeyType(kty jwa.KeyType, alg jwa.SignatureAlgorithm) { - muAlgorithmMaps.Lock() - defer muAlgorithmMaps.Unlock() - keyTypeToAlgorithms[kty] = append(keyTypeToAlgorithms[kty], alg) - if !slices.Contains(algorithmToKeyTypes[alg], kty) { - algorithmToKeyTypes[alg] = append(algorithmToKeyTypes[alg], kty) - } + keyalg.RegisterForKeyType(kty, alg) } -// RegisterAlgorithmForCurve registers an algorithm as valid for the given -// elliptic curve. When [AlgorithmsForKey] can determine the curve of a key, -// it returns the union of key-type-level algorithms and curve-specific -// algorithms instead of all algorithms for the key type. +// RegisterAlgorithmForCurve scopes an algorithm to the given elliptic curve. +// When the curve of a key can be determined, an algorithm registered under +// some curve is offered only for keys on that curve, instead of for every key +// of its key type. Pair this with [RegisterAlgorithmForKeyType] so that, for +// example, an OKP algorithm meant for one curve does not become a candidate +// for every OKP key. // // This function is append-only and deduplicates entries, so builtin // registrations cannot be overwritten by external modules. func RegisterAlgorithmForCurve(crv jwa.EllipticCurveAlgorithm, alg jwa.SignatureAlgorithm) { - muAlgorithmMaps.Lock() - defer muAlgorithmMaps.Unlock() - if slices.Contains(curveToAlgorithms[crv], alg) { - return - } - curveToAlgorithms[crv] = append(curveToAlgorithms[crv], alg) + keyalg.RegisterForCurve(crv, alg) } // AlgorithmsForKey returns the possible signature algorithms that can @@ -593,10 +561,18 @@ func RegisterAlgorithmForCurve(crv jwa.EllipticCurveAlgorithm, alg jwa.Signature // for verification purposes, as this is the only usage where one may need // dynamically figure out which method to use. // -// When the key's curve can be determined (via [jwk.Key] Crv() method or -// inferred from the raw Go type), curve-specific algorithms registered via +// When the key's curve is known, algorithms registered for that curve via // [RegisterAlgorithmForCurve] are combined with key-type-level algorithms -// to produce a more precise result. +// to produce a more precise result. The curve is known for a [jwk.Key] +// that has a Crv() method, for raw ed25519 keys, and for any raw key that +// reaches the [jwk.Import] fallback below. +// +// ECDSA is the exception. A raw [ecdsa.PublicKey] or [ecdsa.PrivateKey] is +// classified by key type alone and its Curve field is never read. No +// builtin registration binds P-256, P-384, or P-521 to an algorithm +// either, so every EC key reports the full ES* list no matter which curve +// it sits on. RFC 7518 Section 3.4 is stricter than that; see +// [WithStrictECDSA] for enforcing it when signing. // // Accepted key shapes (resolved in order): // @@ -622,130 +598,66 @@ func RegisterAlgorithmForCurve(crv jwa.EllipticCurveAlgorithm, alg jwa.Signature // so callers can branch with errors.Is rather than pattern-matching error // strings. The wrapping error keeps the concrete %T or %q diagnostic in // its message for human readers. +// +// Deprecated: Do not use. This is an internal helper that jwx uses to +// guess which algorithms to try when a JWKS key has no "alg" field. It is +// exported only because it always has been, and was never meant for +// callers outside jwx. It does not tell you whether a key and an +// algorithm go together, so do not use it as that kind of check. The list +// it hands back can be wider than RFC 7518 allows for the key you passed. +// +// It keeps working for the rest of the v3 series, and is deprecated in v4 +// as well. It will not be fixed in the meantime, and the way it picks +// algorithms will not change. The list itself can still grow. An +// extension module that calls [RegisterAlgorithmForKeyType] or +// [RegisterAlgorithmForCurve] adds to what this reports, the same way it +// adds to what [Sign] and [Verify] accept. +// +// To find out whether a key works with an algorithm, pass both to [Sign] +// or [Verify] and check the error. func AlgorithmsForKey(key any) ([]jwa.SignatureAlgorithm, error) { - var kty jwa.KeyType - var crv jwa.EllipticCurveAlgorithm - var hasCrv bool - - switch key := key.(type) { - case jwk.Key: - kty = key.KeyType() - if ck, ok := key.(curver); ok { - crv, hasCrv = ck.Crv() - } - case rsa.PublicKey, *rsa.PublicKey, rsa.PrivateKey, *rsa.PrivateKey: - kty = jwa.RSA() - case ecdsa.PublicKey, *ecdsa.PublicKey, ecdsa.PrivateKey, *ecdsa.PrivateKey: - kty = jwa.EC() - case ed25519.PublicKey, ed25519.PrivateKey: - kty = jwa.OKP() - crv = jwa.Ed25519() - hasCrv = true - case *ecdh.PublicKey, ecdh.PublicKey, *ecdh.PrivateKey, ecdh.PrivateKey: - // ecdh keys are for key agreement (X25519/X448), not signing. - // Reject at the API boundary instead of returning a misleading - // algorithm list that would fail deeper in the signing stack. - return nil, fmt.Errorf(`%w: key type %T cannot be used for signing (ecdh keys are key-agreement only)`, errUnclassifiableKey, key) - case []byte: - kty = jwa.OctetSeq() - default: - // For crypto.Signer from external packages (e.g. KMS-backed signers), - // extract the underlying public key type via .Public(). - // Standard library types (*rsa.PrivateKey, etc.) are already handled - // by the concrete cases above. - var signerPubErr error - if signer, ok := key.(crypto.Signer); ok { - pub := signer.Public() - // Guard: only recurse if the public key is not itself a crypto.Signer, - // to prevent infinite recursion from pathological implementations. - if _, isSigner := pub.(crypto.Signer); !isSigner { - algs, err := AlgorithmsForKey(pub) - if err == nil { - return algs, nil - } - // Save the inner classification error so a - // downstream Import-fallback failure can surface - // both diagnostics. A successful Import discards - // signerPubErr — only the eventual failure path - // joins them. - signerPubErr = err - } - } - imported, err := jwk.Import(key) - if err != nil { - outer := fmt.Errorf(`%w: unknown key type %T`, errUnclassifiableKey, key) - if signerPubErr != nil { - return nil, errors.Join(outer, signerPubErr) - } - return nil, outer - } - kty = imported.KeyType() - if ck, ok := imported.(curver); ok { - crv, hasCrv = ck.Crv() - } - } - - muAlgorithmMaps.RLock() - defer muAlgorithmMaps.RUnlock() - - ktyAlgs, ok := keyTypeToAlgorithms[kty] - if !ok { - return nil, fmt.Errorf(`%w: unregistered key type %q`, errUnclassifiableKey, kty) - } - - // If we know the curve and there are curve-specific registrations, - // return only key-type-level algorithms (those not registered under - // any curve) plus curve-specific algorithms for this curve. - if hasCrv { - crvAlgs := curveToAlgorithms[crv] - return filterAlgorithmsForCurve(ktyAlgs, crvAlgs), nil - } - - return ktyAlgs, nil + // The godoc says the way this picks algorithms will not change, so + // calling keyalg only works while keyalg picks them the same way this + // function did before it was deprecated. It does today. If Candidates + // ever changes (narrowing EC keys to the one algorithm their curve + // allows is the likely first case), copy the old code back in here + // instead of letting the change through. An extension registering a + // new algorithm is not that kind of change, because the tables have + // always been an input. + return keyalg.Candidates(key) } -// filterAlgorithmsForCurve returns the subset of ktyAlgs that are not -// registered under any curve (i.e., generic for the key type) plus the -// curve-specific algorithms from crvAlgs. -func filterAlgorithmsForCurve(ktyAlgs, crvAlgs []jwa.SignatureAlgorithm) []jwa.SignatureAlgorithm { - var result []jwa.SignatureAlgorithm - - // Add key-type-level algorithms that are not claimed by any curve - for _, alg := range ktyAlgs { - if !isRegisteredUnderAnyCurve(alg) { - result = append(result, alg) - } - } - - // Add curve-specific algorithms - result = append(result, crvAlgs...) - return result -} - -func isRegisteredUnderAnyCurve(alg jwa.SignatureAlgorithm) bool { - for _, algs := range curveToAlgorithms { - if slices.Contains(algs, alg) { - return true - } - } - return false +// unsupportedKeyError builds the rejection error for a jwk.UnsupportedKey +// placeholder that reached a cryptographic entry point. op names the +// operation the placeholder cannot perform (e.g. "signature verification"). +// The error names the placeholder's kid and kty, and wraps the retained +// parse error from Reason(). +func unsupportedKeyError(uk jwk.UnsupportedKey, op string) error { + kid, _ := uk.KeyID() + return fmt.Errorf(`key with kid %q has unsupported key type %q and cannot be used for %s; an extension module may be required to parse it: %w`, kid, uk.KeyType().String(), op, uk.Reason()) } // validateAlgorithmForKey checks that alg is compatible with key. +// A jwk.UnsupportedKey placeholder is rejected up front — before any of +// the carve-outs below — because it carries no usable key material for +// any algorithm, custom or built-in. // Three classification failures are intentionally allowed through: // (a) a nil key, used by keyless algorithms (see GH910); // (b) any key handed to an algorithm with a user-registered custom // Signer2/Verifier2 — custom implementations may accept arbitrary key -// types that AlgorithmsForKey cannot classify; and +// types that keyalg.Candidates cannot classify; and // (c) an opaque crypto.Signer whose .Public() is itself a crypto.Signer, -// the one case AlgorithmsForKey refuses to recurse into. +// the one case keyalg.Candidates refuses to recurse into. // Every other classification failure is surfaced so callers get a crisp // option-boundary rejection instead of a deep-stack error. func validateAlgorithmForKey(alg jwa.SignatureAlgorithm, key any) error { + if uk, ok := key.(jwk.UnsupportedKey); ok { + return fmt.Errorf(`jws.WithKey: %w`, unsupportedKeyError(uk, `signing or signature verification`)) + } if key == nil { return nil } - algs, err := AlgorithmsForKey(key) + algs, err := keyalg.Candidates(key) if err != nil { if hasCustomSigVerifier(alg) { return nil diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/jwsbb/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jws/jwsbb/BUILD.bazel index 54e64265a0..ca0b963032 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/jwsbb/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/jwsbb/BUILD.bazel @@ -20,9 +20,7 @@ go_library( "//internal/base64", "//internal/ecutil", "//internal/keyconv", - "//internal/pool", "//internal/tokens", - "//jws/internal/keytype", "@com_github_lestrrat_go_dsig//:dsig", "@com_github_valyala_fastjson//:fastjson", ], @@ -30,10 +28,20 @@ go_library( go_test( name = "jwsbb_test", - srcs = ["jwsbb_test.go"], - embed = [":jwsbb"], + srcs = [ + "header_test.go", + "jwsbb_test.go", + ], deps = [ + ":jwsbb", "//internal/base64", + "//internal/pool", "@com_github_stretchr_testify//require", ], ) + +alias( + name = "go_default_library", + actual = ":jwsbb", + visibility = ["//visibility:public"], +) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/jwsbb/sign.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/jwsbb/sign.go index 8c0c185c54..bcc3bbbd31 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/jwsbb/sign.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/jwsbb/sign.go @@ -76,6 +76,11 @@ func dispatchRSASign(key any, dsigAlg string, payload []byte, rr io.Reader) ([]b return dsig.Sign(privkey, dsigAlg, payload, rr) } +// dispatchECDSASign does not enforce the RFC 7518 Section 3.4 binding +// between an ES* algorithm and its curve. That check lives one layer up, in +// jws, behind jws.WithStrictECDSA, because it is opt-in: signing a P-521 key +// under ES256 is non-conformant but has always been allowed here, and jwsbb +// is the raw building-block layer where the caller owns that decision. func dispatchECDSASign(key any, dsigAlg string, payload []byte, rr io.Reader) ([]byte, error) { // Try crypto.Signer first (dsig can handle it directly) if signer, ok := key.(crypto.Signer); ok { diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/key_provider.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/key_provider.go index 49afd0e19f..a5475341a0 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/key_provider.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/key_provider.go @@ -10,6 +10,7 @@ import ( "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" + "github.com/lestrrat-go/jwx/v3/jws/internal/keyalg" ) // KeyProvider is responsible for providing key(s) to sign or verify a payload. @@ -116,6 +117,10 @@ type keySetProvider struct { // It returns true if at least one pair was added, false if the key was // filtered out (e.g. wrong usage, no matching algorithm). func (kp *keySetProvider) selectKey(sink KeySink, key jwk.Key, sig *Signature, _ *Message) (bool, error) { + if uk, ok := key.(jwk.UnsupportedKey); ok { + return false, unsupportedKeyError(uk, `signature verification`) + } + if usage, ok := key.KeyUsage(); ok { // it's okay if use: "". we'll assume it's "sig" if usage != "" && usage != jwk.ForSignature.String() { @@ -138,7 +143,7 @@ func (kp *keySetProvider) selectKey(sink KeySink, key jwk.Key, sig *Signature, _ return false, nil } - algs, err := AlgorithmsForKey(key) + algs, err := keyalg.Candidates(key) if err != nil { return false, fmt.Errorf(`failed to get a list of signature methods for key type %s: %w`, key.KeyType(), err) } @@ -233,22 +238,23 @@ func (kp *keySetProvider) fetchKeysByKid(sink KeySink, sig *Signature, msg *Mess // fetchAllKeys iterates all keys in the set and adds suitable ones to the sink. // // When the protected header advertises an `alg`, keys whose type cannot -// produce that algorithm are skipped before reaching selectKey. This -// bounds verification fan-out to N_keys_of_matching_type instead of -// N_keys when `WithRequireKid(false)` is used against a heterogeneous -// JWKS. The skip is semantics-preserving: validateAlgorithmForKey in -// verify_context would reject the incompatible (alg, key) pair before -// running any verifier anyway. +// produce that algorithm are skipped before reaching selectKey +// (unsupported-key placeholders excepted — see the comment at the check +// below). This bounds verification fan-out to N_keys_of_matching_type +// instead of N_keys when `WithRequireKid(false)` is used against a +// heterogeneous JWKS. The skip is semantics-preserving: +// validateAlgorithmForKey in verify_context would reject the +// incompatible (alg, key) pair before running any verifier anyway. // // The allowed-KeyType set is looked up once per FetchKeys call via the -// precomputed algorithmToKeyTypes inverse map, so the per-key check is +// precomputed inverse map in keyalg, so the per-key check is // a cheap KeyType equality over a tiny slice (typically 1 element). // When allowedKtys is nil (no header alg, or alg has no registered // key type), the filter is skipped. func (kp *keySetProvider) fetchAllKeys(sink KeySink, sig *Signature, msg *Message) error { var allowedKtys []jwa.KeyType if hdrAlg, ok := sig.ProtectedHeaders().Algorithm(); ok { - allowedKtys = keyTypesForAlgorithm(hdrAlg) + allowedKtys = keyalg.KeyTypesFor(hdrAlg) } found := false var errs []error @@ -257,7 +263,13 @@ func (kp *keySetProvider) fetchAllKeys(sink KeySink, sig *Signature, msg *Messag if !ok { return fmt.Errorf(`failed to get key at index %d`, i) } - if allowedKtys != nil && !slices.Contains(allowedKtys, key.KeyType()) { + // Unsupported-key placeholders are exempt from the prefilter: + // their raw kty is never a registered KeyType, so the filter + // would silently skip them and the caller would only see a + // generic "no keys worked" error. Letting them reach selectKey + // records the per-key rejection (kid, kty, retained parse + // reason) in errs instead. + if allowedKtys != nil && !slices.Contains(allowedKtys, key.KeyType()) && !jwk.IsUnsupportedKey(key) { continue } added, err := kp.selectKey(sink, key, sig, msg) @@ -269,26 +281,16 @@ func (kp *keySetProvider) fetchAllKeys(sink KeySink, sig *Signature, msg *Messag found = true } } + // Only when no candidate reached the sink do the collected per-key + // errors become the outcome: a key that was skipped without error + // (e.g. no "alg" member and inference disabled) must not mask the + // named rejections of the keys that did fail. if !found && len(errs) > 0 { return fmt.Errorf(`no key in the key set was usable: %w`, errors.Join(errs...)) } return nil } -// keyTypesForAlgorithm returns the registered key types that can -// produce the given signature algorithm. The inverse map is maintained -// at registration time so this is an O(1) lookup. Returns nil if no -// key type is registered for alg, which signals callers to skip the -// prefilter. -func keyTypesForAlgorithm(alg jwa.SignatureAlgorithm) []jwa.KeyType { - muAlgorithmMaps.RLock() - defer muAlgorithmMaps.RUnlock() - // Copy so the caller can safely iterate without holding the - // lock; RegisterAlgorithmForKeyType may append concurrently - // after we return. Typical length is 1. - return slices.Clone(algorithmToKeyTypes[alg]) -} - type jkuProvider struct { fetcher jwk.Fetcher options []jwk.FetchOption @@ -329,13 +331,17 @@ func (kp jkuProvider) FetchKeys(ctx context.Context, sink KeySink, sig *Signatur return fmt.Errorf(`jku: key with "kid" %q not found in JWKS fetched from %q`, kid, u) } + if uk, ok := key.(jwk.UnsupportedKey); ok { + return fmt.Errorf(`jku: key with "kid" %q from %q has unsupported key type %q and cannot be used for signature verification; an extension module may be required to parse it: %w`, kid, u, uk.KeyType().String(), uk.Reason()) + } + if usage, ok := key.KeyUsage(); ok { if usage != "" && usage != jwk.ForSignature.String() { return fmt.Errorf(`key with kid %q is marked use=%q, not usable for signature verification (expected %q)`, kid, usage, jwk.ForSignature.String()) } } - algs, err := AlgorithmsForKey(key) + algs, err := keyalg.Candidates(key) if err != nil { return fmt.Errorf(`failed to get a list of signature methods for key type %s: %w`, key.KeyType(), err) } diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/legacy/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jws/legacy/BUILD.bazel index 8e77cece46..41ee14aa45 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/legacy/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/legacy/BUILD.bazel @@ -14,8 +14,13 @@ go_library( deps = [ "//internal/ecutil", "//internal/keyconv", - "//internal/pool", "//jwa", "//jws/internal/keytype", ], ) + +alias( + name = "go_default_library", + actual = ":legacy", + visibility = ["//visibility:public"], +) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/message.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/message.go index 02e4590620..25814afd78 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/message.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/message.go @@ -9,6 +9,7 @@ import ( "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" + "github.com/lestrrat-go/jwx/v3/jwk" ) func NewSignature() *Signature { @@ -113,6 +114,15 @@ func (s *Signature) Sign(payload []byte, signer Signer, key any) ([]byte, []byte } func (s *Signature) sign2(payload []byte, signer interface{ Algorithm() jwa.SignatureAlgorithm }, key any) ([]byte, []byte, error) { + // A jwk.UnsupportedKey placeholder carries no usable key material and + // must never reach the Signer. This path builds a signatureBuilder + // directly and bypasses validateAlgorithmForKey (only the + // jws.Sign/WithKey path runs that guard), so reject the placeholder + // here — the single entry to signatureBuilder for this path. + if uk, ok := key.(jwk.UnsupportedKey); ok { + return nil, nil, unsupportedKeyError(uk, `signing`) + } + // Create a signatureBuilder to use the shared signing logic sb := signatureBuilderPool.Get() defer signatureBuilderPool.Put(sb) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/options.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/options.go index cc98abc5be..a126dd405c 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/options.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/options.go @@ -104,6 +104,14 @@ func (w *withKey) Protected(v Headers) Headers { // The algorithm specified in the `alg` parameter MUST be able to support // the type of key you provided, otherwise an error is returned. // +// RFC 7518 Section 3.4 binds each of ES256/ES384/ES512 to one elliptic curve +// (ES256/P-256, ES384/P-384, ES512/P-521), but `jws.Sign()` does not enforce +// that by default: a key on any other curve still signs, and the JWS it +// produces is one strict JOSE implementations reject. Pass +// `jws.WithStrictECDSA(true)` to `jws.Sign()` to reject the mismatch instead. +// `jws.Verify()` is unaffected either way and keeps inferring algorithms from +// a key's curve exactly as before. +// // Any of the following is accepted for the `key` parameter: // * A "raw" key (e.g. rsa.PrivateKey, ecdsa.PrivateKey, etc) // * A crypto.Signer diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/options.yaml b/vendor/github.com/lestrrat-go/jwx/v3/jws/options.yaml index fb8c88db62..326d14a745 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/options.yaml +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/options.yaml @@ -227,6 +227,28 @@ options: the key on-demand each time. By default, the key is not validated. + - ident: StrictECDSA + interface: SignOption + argument_type: bool + comment: | + WithStrictECDSA makes `jws.Sign()` reject anything RFC 7518 forbids for + an ECDSA signature. Today that is exactly one rule: Section 3.4 binds + ES256 to P-256, ES384 to P-384, and ES512 to P-521, so signing with a + key on any other curve fails instead of producing a JWS that strict + JOSE implementations reject. + + Future releases may enforce further RFC 7518 ECDSA rules under this + same option, so enabling it means "be strict about ECDSA", not "check + the curve and nothing else". + + Extension algorithms on their own curves, such as ES256K, are not + affected. Only the three curves the RFC names are checked. + + This option is sign-side only. `jws.Verify()` is unaffected and keeps + inferring algorithms from a key's curve exactly as before, so a JWS + produced without this option still verifies. + + By default, the curve is not checked. - ident: InferAlgorithmFromKey interface: WithKeySetSuboption argument_type: bool diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/options_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/options_gen.go index 10dd96e489..ade61d36c5 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/options_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/options_gen.go @@ -223,6 +223,7 @@ type identProtectedHeaders struct{} type identPublicHeaders struct{} type identRequireKid struct{} type identSerialization struct{} +type identStrictECDSA struct{} type identUseDefault struct{} type identValidateKey struct{} @@ -306,6 +307,10 @@ func (identSerialization) String() string { return "WithSerialization" } +func (identStrictECDSA) String() string { + return "WithStrictECDSA" +} + func (identUseDefault) String() string { return "WithUseDefault" } @@ -605,6 +610,28 @@ func WithCompact() SignVerifyParseOption { return &signVerifyParseOption{option.New(identSerialization{}, fmtCompact)} } +// WithStrictECDSA makes `jws.Sign()` reject anything RFC 7518 forbids for +// an ECDSA signature. Today that is exactly one rule: Section 3.4 binds +// ES256 to P-256, ES384 to P-384, and ES512 to P-521, so signing with a +// key on any other curve fails instead of producing a JWS that strict +// JOSE implementations reject. +// +// Future releases may enforce further RFC 7518 ECDSA rules under this +// same option, so enabling it means "be strict about ECDSA", not "check +// the curve and nothing else". +// +// Extension algorithms on their own curves, such as ES256K, are not +// affected. Only the three curves the RFC names are checked. +// +// This option is sign-side only. `jws.Verify()` is unaffected and keeps +// inferring algorithms from a key's curve exactly as before, so a JWS +// produced without this option still verifies. +// +// By default, the curve is not checked. +func WithStrictECDSA(v bool) SignOption { + return &signOption{option.New(identStrictECDSA{}, v)} +} + // WithUseDefault specifies that if and only if a jwk.Key contains // exactly one jwk.Key, that key should be used. func WithUseDefault(v bool) WithKeySetSuboption { diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/sign_context.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/sign_context.go index a6cbd045e9..f564454f18 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/sign_context.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/sign_context.go @@ -14,6 +14,7 @@ type signContext struct { format int detached bool validateKey bool + strictECDSA bool payload []byte payloadReader io.Reader encoder Base64Encoder @@ -39,6 +40,7 @@ func freeSignContext(ctx *signContext) *signContext { ctx.sigbuilders = ctx.sigbuilders[:0] ctx.detached = false ctx.validateKey = false + ctx.strictECDSA = false ctx.encoder = base64.DefaultEncoder() ctx.none = nil ctx.payload = nil @@ -133,6 +135,10 @@ func (sc *signContext) ProcessOptions(options []SignOption) error { if err := option.Value(&sc.validateKey); err != nil { return makeSignError(prefixJwsSign, `failed to retrieve validate-key option value: %w`, err) } + case identStrictECDSA{}: + if err := option.Value(&sc.strictECDSA); err != nil { + return makeSignError(prefixJwsSign, `failed to retrieve strict-ECDSA option value: %w`, err) + } case identBase64Encoder{}: if err := option.Value(&sc.encoder); err != nil { return makeSignError(prefixJwsSign, `failed to retrieve base64-encoder option value: %w`, err) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/signature_builder.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/signature_builder.go index 2963b6e48d..76ed62521b 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/signature_builder.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/signature_builder.go @@ -2,14 +2,19 @@ package jws import ( "bytes" + "crypto/ecdsa" "fmt" "slices" + "github.com/lestrrat-go/dsig" + "github.com/lestrrat-go/jwx/v3/internal/json" + "github.com/lestrrat-go/jwx/v3/internal/keyconv" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" + jwsbbi "github.com/lestrrat-go/jwx/v3/jws/internal/jwsbb" "github.com/lestrrat-go/jwx/v3/jws/jwsbb" ) @@ -52,7 +57,60 @@ func freeSignatureBuilder(sb *signatureBuilder) *signatureBuilder { return sb } +// requireECDSACurve enforces the RFC 7518 Section 3.4 binding between an ES* +// algorithm and the curve its key must sit on. It is only reached when the +// caller asked for it with jws.WithStrictECDSA(true). +// +// Anything that is not an ECDSA signature passes straight through, as does an +// ECDSA-family algorithm outside the three JOSE built-ins (an extension on its +// own curve, such as ES256K) and a key whose curve cannot be read. Deciding +// those cases is not this check's job; only positive evidence of a mismatch is +// an error. +func requireECDSACurve(alg jwa.SignatureAlgorithm, key any) error { + dsigAlg, ok := jwsbb.GetDsigAlgorithm(alg.String()) + if !ok { + return nil + } + + info, ok := dsig.GetAlgorithmInfo(dsigAlg) + if !ok || info.Family != dsig.ECDSA { + return nil + } + + rawKey, ok := unwrapECDSASignKey(key) + if !ok { + return nil + } + + return jwsbbi.RequireECDSACurve(alg.String(), dsigAlg, rawKey) +} + +// unwrapECDSASignKey returns the key jwsbbi.RequireECDSACurve should inspect. +// That function reads the curve off a raw key or a crypto.Signer, so a +// jwk.Key has to be unwrapped first. +// +// The bool is false when key is a jwk.Key holding something other than an +// ECDSA private key, which leaves the curve unreadable. The caller skips the +// check in that case and lets the signer reject the key on its own terms. +func unwrapECDSASignKey(key any) (any, bool) { + if _, ok := key.(jwk.Key); !ok { + return key, true + } + + var privkey *ecdsa.PrivateKey + if err := keyconv.ECDSAPrivateKey(&privkey, key); err != nil { + return nil, false + } + return privkey, true +} + func (sb *signatureBuilder) Build(sc *signContext, payload []byte) (*Signature, error) { + if sc.strictECDSA { + if err := requireECDSACurve(sb.alg, sb.key); err != nil { + return nil, makeSignError(prefixJwsSign, `%w`, err) + } + } + // Clone caller-provided headers before mutating so that re-using the // same Headers instance across multiple Sign calls does not cause // cross-contamination of alg/kid. diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/streaming_detached.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/streaming_detached.go index 871bb6439d..5a23038350 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/streaming_detached.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/streaming_detached.go @@ -16,6 +16,7 @@ import ( "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" + jwsbbi "github.com/lestrrat-go/jwx/v3/jws/internal/jwsbb" "github.com/lestrrat-go/jwx/v3/jws/jwsbb" ) @@ -85,6 +86,14 @@ func (sc *signContext) signStreaming() ([]byte, error) { return nil, makeSignError(prefixJwsSign, `failed to convert key for signature %d: %w`, idx, err) } + // The non-streaming path runs the same check from + // signatureBuilder.Build, which this path does not go through. + if sc.strictECDSA && dsigInfo.Family == dsig.ECDSA { + if err := jwsbbi.RequireECDSACurve(alg.String(), dsigInfo.Name, rawKey); err != nil { + return nil, makeSignError(prefixJwsSign, `signature %d: %w`, idx, err) + } + } + protected, err := cloneOrNewHeaders(sb.protected) if err != nil { return nil, makeSignError(prefixJwsSign, `failed to clone protected headers for signature %d: %w`, idx, err) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/verify_context.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/verify_context.go index f9c2421f34..f4aed3b368 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/verify_context.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/verify_context.go @@ -13,6 +13,7 @@ import ( "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/jwa" + "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws/jwsbb" ) @@ -287,6 +288,15 @@ func (vc *verifyContext) VerifyMessage(buf []byte) ([]byte, error) { } func (vc *verifyContext) tryKey(verifyBuf []byte, alg jwa.SignatureAlgorithm, key any, msg *Message, sig *Signature) error { + // Reject placeholders before any verifier — including a custom + // Verifier2 — can see them. A custom KeyProvider can sink an + // (alg, key) pair directly, bypassing keySetProvider.selectKey and + // validateAlgorithmForKey, so this is the last chokepoint before + // key material is used. + if uk, ok := key.(jwk.UnsupportedKey); ok { + return unsupportedKeyError(uk, `signature verification`) + } + if vc.validateKey { if err := validateKeyBeforeUse(key); err != nil { return fmt.Errorf(`failed to validate key before verification: %w`, err) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwt/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jwt/BUILD.bazel index 86197d348a..d49f6c1b48 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwt/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwt/BUILD.bazel @@ -4,9 +4,10 @@ go_library( name = "jwt", srcs = [ "builder_gen.go", + "doc.go", "errors.go", - "filter.go", "fastpath.go", + "filter.go", "http.go", "interface.go", "io.go", @@ -24,17 +25,17 @@ go_library( deps = [ "//:jwx", "//internal/base64", - "//transform", "//internal/json", - "//internal/tokens", "//internal/pool", + "//internal/tokens", "//jwa", "//jwe", "//jwk", "//jws", "//jws/jwsbb", - "//jwt/internal/types", "//jwt/internal/errors", + "//jwt/internal/types", + "//transform", "@com_github_lestrrat_go_blackmagic//:blackmagic", "@com_github_lestrrat_go_option_v2//:option", ], @@ -43,6 +44,10 @@ go_library( go_test( name = "jwt_test", srcs = [ + "fastpath_test.go", + "filter_test.go", + "fuzz_test.go", + "jwt_crit_test.go", "jwt_test.go", "options_gen_test.go", "token_options_test.go", @@ -54,13 +59,14 @@ go_test( deps = [ "//internal/json", "//internal/jwxtest", + "//internal/tokens", "//jwa", "//jwe", "//jwk", "//jwk/ecdsa", "//jws", "//jwt/internal/types", - "@com_github_lestrrat_go_httprc_v3//:httprc", + "@com_github_lestrrat_go_httprc_v3//:httprc", "@com_github_stretchr_testify//require", ], ) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwt/internal/errors/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/jwt/internal/errors/BUILD.bazel index a053e8c0aa..cb5c4099c5 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwt/internal/errors/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwt/internal/errors/BUILD.bazel @@ -2,9 +2,7 @@ load("@rules_go//go:def.bzl", "go_library") go_library( name = "errors", - srcs = [ - "errors.go", - ], + srcs = ["errors.go"], importpath = "github.com/lestrrat-go/jwx/v3/jwt/internal/errors", visibility = ["//jwt:__subpackages__"], ) @@ -13,4 +11,4 @@ alias( name = "go_default_library", actual = ":errors", visibility = ["//jwt:__subpackages__"], -) \ No newline at end of file +) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwt/jwt.go b/vendor/github.com/lestrrat-go/jwx/v3/jwt/jwt.go index 6229c763cd..7bbd2686bf 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwt/jwt.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwt/jwt.go @@ -517,7 +517,7 @@ OUTER: // the token. // // For well-known algorithms with no special considerations (e.g. detached -// payloads, extra protected heders, etc), this function will automatically +// payloads, extra protected headers, etc), this function will automatically // take the fast path and bypass the jws.Sign() machinery, which improves // performance significantly. // diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwt/token_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/jwt/token_gen.go index 7a057742a9..343e399473 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwt/token_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwt/token_gen.go @@ -628,9 +628,9 @@ func (t *stdToken) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(tokens.Comma) } - buf.WriteByte('"') - buf.WriteString(pair.Name) - buf.WriteString(`": `) + if err := json.WriteQuotedKey(buf, pair.Name); err != nil { + return nil, fmt.Errorf(`failed to encode claim name %q: %w`, pair.Name, err) + } buf.Write(pair.Value.([]byte)) } buf.WriteByte(tokens.CloseCurlyBracket) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/transform/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v3/transform/BUILD.bazel index 3333c6607c..9abb3aa5df 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/transform/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/transform/BUILD.bazel @@ -15,9 +15,7 @@ go_library( go_test( name = "transform_test", - srcs = [ - "map_test.go", - ], + srcs = ["map_test.go"], deps = [ ":transform", "//jwt", @@ -29,4 +27,4 @@ alias( name = "go_default_library", actual = ":transform", visibility = ["//visibility:public"], -) \ No newline at end of file +) diff --git a/vendor/github.com/olekukonko/tablewriter/README.md b/vendor/github.com/olekukonko/tablewriter/README.md index d8334424c4..8ee705f088 100644 --- a/vendor/github.com/olekukonko/tablewriter/README.md +++ b/vendor/github.com/olekukonko/tablewriter/README.md @@ -28,7 +28,7 @@ go get github.com/olekukonko/tablewriter@v0.0.5 #### Latest Version The latest stable version ```bash -go get github.com/olekukonko/tablewriter@v1.1.3 +go get github.com/olekukonko/tablewriter@v1.1.5 ``` **Warning:** Version `v1.0.0` contains missing functionality and should not be used. @@ -62,7 +62,7 @@ func main() { data := [][]string{ {"Package", "Version", "Status"}, {"tablewriter", "v0.0.5", "legacy"}, - {"tablewriter", "v1.1.3", "latest"}, + {"tablewriter", "v1.1.5", "latest"}, } table := tablewriter.NewWriter(os.Stdout) @@ -77,7 +77,7 @@ func main() { │ PACKAGE │ VERSION │ STATUS │ ├─────────────┼─────────┼────────┤ │ tablewriter │ v0.0.5 │ legacy │ -│ tablewriter │ v1.1.3 │ latest │ +│ tablewriter │ v1.1.5 │ latest │ └─────────────┴─────────┴────────┘ ``` @@ -426,6 +426,30 @@ func main() { ![Colorized Table with Long Values](_readme/color_1.png "Title") +##### 24-bit (RGB / true color) tints + +Besides the named `color.Fg*`/`color.Bg*` attributes, tints accept 24-bit +colors. `renderer.RGB` and `renderer.BgRGB` take red, green and blue channels +(0-255, out-of-range values are clamped), while `renderer.Hex`/`renderer.BgHex` +parse a `#RRGGBB` or `#RGB` string. All of them return a `renderer.Colors`, so +they slot in wherever named attributes do and can be combined with `append`: + +```go +orange, _ := renderer.Hex("#ff8800") + +colorCfg := renderer.ColorizedConfig{ + // Bold orange headers on a dark-grey background. + Header: renderer.Tint{ + FG: append(orange, color.Bold), + BG: renderer.BgRGB(30, 30, 30), + }, + // Teal rows. + Column: renderer.Tint{FG: renderer.RGB(0, 200, 180)}, +} +``` + +Terminals without true-color support may approximate or ignore these colors. + #### 5. Streaming Table with Truncation Stream a table incrementally with truncation and a footer, simulating a real-time data feed (inspired by `TestOceanStreamTruncation` and `TestOceanStreamSlowOutput`). diff --git a/vendor/github.com/olekukonko/tablewriter/comb.hcl b/vendor/github.com/olekukonko/tablewriter/comb.hcl index 6d5025af2d..73d6b5e0c4 100644 --- a/vendor/github.com/olekukonko/tablewriter/comb.hcl +++ b/vendor/github.com/olekukonko/tablewriter/comb.hcl @@ -1,10 +1,15 @@ recursive = true -output_file = "all.txt" +output_file = "tw.txt" extensions = [".go"] -exclude_dirs = [ - "_examples", "_readme", "_lab","_tmp","pkg","lab","cmd","test.txt","tmp", - "_readme","pkg","renderer" -] -exclude_files = ["README.md","README_LEGACY.md","MIGRATION.md","test.hcl","csv.go"] +exclude_dirs { + items = [ + "_examples", "_readme", "_lab", "_tmp", "pkg", "lab", "cmd", "test.txt", "tmp", + "_readme", "pkg", "renderer" + ] +} +exclude_files { + items = ["README.md","README_LEGACY.md","MIGRATION.md","test.hcl","csv.go"] +} use_gitignore = true -detailed = true \ No newline at end of file +detailed = true +go_mode = "all" \ No newline at end of file diff --git a/vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go b/vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go index f6fa17e42d..e46a2732cc 100644 --- a/vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go +++ b/vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go @@ -193,7 +193,8 @@ func WrapWords(words []string, spc, lim, pen int) [][]string { if i < n-1 { remainderLen += spc + lengths[i] } - if remainderLen <= lim { + // the last word has no following break to choose, so it must end the chain + if remainderLen <= lim || i == n-1 { cost[i] = 0 nbrk[i] = n continue diff --git a/vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go b/vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go index 14b334b095..79d2b50f08 100644 --- a/vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go +++ b/vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go @@ -333,7 +333,11 @@ func Width(str string) int { if IsTab(rune(str[0])) { return TabWidth() } - return 1 + // Only printable ASCII has a guaranteed width of 1; control + // characters (width 0) fall through to the accurate path below. + if str[0] >= 0x20 && str[0] != 0x7f { + return 1 + } } mu.Lock() diff --git a/vendor/github.com/olekukonko/tablewriter/renderer/markdown.go b/vendor/github.com/olekukonko/tablewriter/renderer/markdown.go index c8ff55a9d0..99554c2cf3 100644 --- a/vendor/github.com/olekukonko/tablewriter/renderer/markdown.go +++ b/vendor/github.com/olekukonko/tablewriter/renderer/markdown.go @@ -12,17 +12,20 @@ import ( // Markdown renders tables in Markdown format with customizable settings. type Markdown struct { - config tw.Rendition // Rendering configuration - logger *ll.Logger // Debug trace messages - alignment tw.Alignment // alias of []tw.Align - w io.Writer + config tw.Rendition // Rendering configuration + logger *ll.Logger // Debug trace messages + headerAlignment tw.Alignment // Cached header alignments + bodyAlignment tw.Alignment // Cached body alignments + bodyResolved bool // Whether body alignment has been resolved from actual row data + w io.Writer + // Deferred separator rendering + pendingSeparator bool + pendingSepCtx tw.Formatting } -// NewMarkdown initializes a Markdown renderer with defaults tailored for Markdown (e.g., pipes, header separator). -// Only the first config is used if multiple are provided. +// NewMarkdown initializes a Markdown renderer with defaults tailored for Markdown. func NewMarkdown(configs ...tw.Rendition) *Markdown { cfg := defaultBlueprint() - // Configure Markdown-specific defaults cfg.Symbols = tw.NewSymbols(tw.StyleMarkdown) cfg.Borders = tw.Border{Left: tw.On, Right: tw.On, Top: tw.Off, Bottom: tw.Off} cfg.Settings.Separators.BetweenColumns = tw.On @@ -31,9 +34,7 @@ func NewMarkdown(configs ...tw.Rendition) *Markdown { cfg.Settings.Lines.ShowTop = tw.Off cfg.Settings.Lines.ShowBottom = tw.Off cfg.Settings.Lines.ShowFooterLine = tw.Off - // cfg.Settings.TrimWhitespace = tw.On - // Apply user overrides if len(configs) > 0 { cfg = mergeMarkdownConfig(cfg, configs[0]) } @@ -55,7 +56,6 @@ func mergeMarkdownConfig(defaults, overrides tw.Rendition) tw.Rendition { // Enforce Markdown requirements defaults.Settings.Lines.ShowHeaderLine = tw.On defaults.Settings.Separators.BetweenColumns = tw.On - // defaults.Settings.TrimWhitespace = tw.On return defaults } @@ -68,39 +68,55 @@ func (m *Markdown) Config() tw.Rendition { return m.config } -// Header renders the Markdown table header and its separator line. +func (m *Markdown) Rendition(config tw.Rendition) { + m.config = mergeRendition(m.config, config) + m.Reset() +} + +// Header renders the Markdown table header. Separator is deferred until body alignment is known. func (m *Markdown) Header(headers [][]string, ctx tw.Formatting) { - m.resolveAlignment(ctx) + m.resolveHeaderAlignment(ctx) if len(headers) == 0 || len(headers[0]) == 0 { m.logger.Debug("Header: No headers to render") return } - m.logger.Debugf("Rendering header with %d lines, widths=%v, current=%v, next=%v", len(headers), ctx.Row.Widths, ctx.Row.Current, ctx.Row.Next) + m.logger.Debugf("Rendering header with %d lines, widths=%v, current=%v, next=%v", + len(headers), ctx.Row.Widths, ctx.Row.Current, ctx.Row.Next) - // Render header content + // Render header content immediately m.renderMarkdownLine(headers[0], ctx, false) - // Render separator if enabled + // Defer separator rendering until we know the body alignment if m.config.Settings.Lines.ShowHeaderLine.Enabled() { - sepCtx := ctx - sepCtx.Row.Widths = ctx.Row.Widths - sepCtx.Row.Current = ctx.Row.Current - sepCtx.Row.Previous = ctx.Row.Current - sepCtx.IsSubRow = true - m.renderMarkdownLine(nil, sepCtx, true) + m.pendingSeparator = true + m.pendingSepCtx = ctx + m.logger.Debug("Header: Deferred separator rendering until body alignment is known") } } -// Row renders a Markdown table data row. +// Row renders a Markdown table data row. First row triggers deferred separator if pending. func (m *Markdown) Row(row []string, ctx tw.Formatting) { - m.resolveAlignment(ctx) - m.logger.Debugf("Rendering row with data=%v, widths=%v, previous=%v, current=%v, next=%v", row, ctx.Row.Widths, ctx.Row.Previous, ctx.Row.Current, ctx.Row.Next) + m.resolveBodyAlignment(ctx) + + // Render deferred separator if pending and body alignment is now known + if m.pendingSeparator && m.bodyResolved { + m.renderDeferredSeparator() + } + + m.logger.Debugf("Rendering row with data=%v, widths=%v, previous=%v, current=%v, next=%v", + row, ctx.Row.Widths, ctx.Row.Previous, ctx.Row.Current, ctx.Row.Next) m.renderMarkdownLine(row, ctx, false) } // Footer renders the Markdown table footer. func (m *Markdown) Footer(footers [][]string, ctx tw.Formatting) { - m.resolveAlignment(ctx) + m.resolveBodyAlignment(ctx) + + // Render deferred separator if still pending (no rows to trigger it) + if m.pendingSeparator && m.bodyResolved { + m.renderDeferredSeparator() + } + if len(footers) == 0 || len(footers[0]) == 0 { m.logger.Debug("Footer: No footers to render") return @@ -110,14 +126,32 @@ func (m *Markdown) Footer(footers [][]string, ctx tw.Formatting) { m.renderMarkdownLine(footers[0], ctx, false) } -// Line is a no-op for Markdown, as only the header separator is rendered (handled by Header). +// renderDeferredSeparator renders the separator line now that body alignment is known. +func (m *Markdown) renderDeferredSeparator() { + m.logger.Debug("Rendering deferred separator with known body alignment: %s", m.bodyAlignment) + + sepCtx := m.pendingSepCtx + sepCtx.Row.Widths = m.pendingSepCtx.Row.Widths + sepCtx.Row.Previous = m.pendingSepCtx.Row.Current + sepCtx.IsSubRow = true + sepCtx.Row.Current = m.pendingSepCtx.Row.Current // Use header context for separator rendering + + m.renderMarkdownLine(nil, sepCtx, true) + m.pendingSeparator = false +} + func (m *Markdown) Line(ctx tw.Formatting) { - m.logger.Debugf("Line: Generic Line call received (pos: %s, loc: %s). Markdown ignores these.", ctx.Row.Position, ctx.Row.Location) + m.logger.Debugf("Line: Generic Line call received (pos: %s, loc: %s). Markdown ignores these.", + ctx.Row.Position, ctx.Row.Location) } // Reset clears the renderer's internal state, including debug traces. func (m *Markdown) Reset() { - m.logger.Info("Reset: Cleared debug trace") + m.headerAlignment = nil + m.bodyAlignment = nil + m.bodyResolved = false + m.pendingSeparator = false + m.logger.Info("Reset: Cleared alignment caches") } func (m *Markdown) Start(w io.Writer) error { @@ -127,40 +161,89 @@ func (m *Markdown) Start(w io.Writer) error { } func (m *Markdown) Close() error { - m.logger.Warn("Markdown.Close() called (no-op).") + if !m.pendingSeparator { + return nil + } + + // If we have a deferred separator but body alignment was never resolved, + // fall back to header alignment so the separator still renders. + if !m.bodyResolved && len(m.headerAlignment) > 0 { + m.bodyResolved = true + m.bodyAlignment = make(tw.Alignment, len(m.headerAlignment)) + copy(m.bodyAlignment, m.headerAlignment) + } + + if m.bodyResolved { + m.renderDeferredSeparator() + } + return nil } -func (m *Markdown) resolveAlignment(ctx tw.Formatting) tw.Alignment { - if len(m.alignment) != 0 { - return m.alignment +func (m *Markdown) resolveHeaderAlignment(ctx tw.Formatting) tw.Alignment { + if len(m.headerAlignment) != 0 { + return m.headerAlignment } - - // get total columns total := len(ctx.Row.Current) - - // build default alignment for i := 0; i < total; i++ { - m.alignment = append(m.alignment, tw.AlignNone) // Default to AlignNone + m.headerAlignment = append(m.headerAlignment, tw.AlignNone) + } + for i := 0; i < total; i++ { + m.headerAlignment[i] = ctx.Row.Current[i].Align + } + m.logger.Debugf(" → Header Align Resolved %s", m.headerAlignment) + return m.headerAlignment +} + +func (m *Markdown) resolveBodyAlignment(ctx tw.Formatting) tw.Alignment { + // Only resolve from actual row data once + if m.bodyResolved { + return m.bodyAlignment } - // add per column alignment if it exists - for i := 0; i < total; i++ { - m.alignment[i] = ctx.Row.Current[i].Align + total := len(ctx.Row.Current) + if total == 0 { + return m.bodyAlignment } - m.logger.Debugf(" → Align Resolved %s", m.alignment) - return m.alignment + // Initialize if needed + if len(m.bodyAlignment) == 0 { + for i := 0; i < total; i++ { + m.bodyAlignment = append(m.bodyAlignment, tw.AlignNone) + } + } + + // Only update from row context if position is Row or Footer (not Header/separator) + if ctx.Row.Position == tw.Row || ctx.Row.Position == tw.Footer { + for i := 0; i < total && i < len(m.bodyAlignment); i++ { + m.bodyAlignment[i] = ctx.Row.Current[i].Align + } + m.bodyResolved = true + m.logger.Debugf(" → Body Align Resolved from %s: %s", ctx.Row.Position, m.bodyAlignment) + } + + return m.bodyAlignment +} + +// resolveAlignmentRule applies the Deliberate Rules: +// Rule 1: No explicit alignment → Center (backward compatible) +// Rule 2 & 3: Body has explicit alignment → Body wins +// Rule 4: Only header has explicit alignment → Header wins +func (m *Markdown) resolveAlignmentRule(headerAlign, bodyAlign tw.Align) tw.Align { + headerExplicit := headerAlign != tw.AlignNone && headerAlign != tw.Empty && headerAlign != tw.Skip + bodyExplicit := bodyAlign != tw.AlignNone && bodyAlign != tw.Empty && bodyAlign != tw.Skip + + if bodyExplicit { + return bodyAlign + } else if headerExplicit { + return headerAlign + } + return tw.AlignCenter } // formatCell formats a Markdown cell's content with padding and alignment, ensuring at least 3 characters wide. func (m *Markdown) formatCell(content string, width int, align tw.Align, padding tw.Padding) string { - // if m.config.Settings.TrimWhitespace.Enabled() { - // content = strings.TrimSpace(content) - //} contentVisualWidth := twwidth.Width(content) - - // Use specified padding characters or default to spaces padLeftChar := padding.Left if padLeftChar == tw.Empty { padLeftChar = tw.Space @@ -169,14 +252,11 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding if padRightChar == tw.Empty { padRightChar = tw.Space } - - // Calculate padding widths padLeftCharWidth := twwidth.Width(padLeftChar) padRightCharWidth := twwidth.Width(padRightChar) minWidth := tw.Max(3, contentVisualWidth+padLeftCharWidth+padRightCharWidth) targetWidth := tw.Max(width, minWidth) - // Calculate padding totalPaddingNeeded := max(targetWidth-contentVisualWidth, 0) var leftPadStr, rightPadStr string @@ -206,10 +286,7 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding rightPadStr = strings.Repeat(padRightChar, rightPadCount) } - // Build result result := leftPadStr + content + rightPadStr - - // Adjust width if needed finalWidth := twwidth.Width(result) if finalWidth != targetWidth { m.logger.Debugf("Markdown formatCell MISMATCH: content='%s', target_w=%d, paddingL='%s', paddingR='%s', align=%s -> result='%s', result_w=%d", @@ -238,7 +315,6 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding return result } -// formatSeparator generates a Markdown separator (e.g., `---`, `:--`, `:-:`) with alignment indicators. func (m *Markdown) formatSeparator(width int, align tw.Align) string { targetWidth := tw.Max(3, width) var sb strings.Builder @@ -254,10 +330,10 @@ func (m *Markdown) formatSeparator(width int, align tw.Align) string { sb.WriteRune(':') sb.WriteString(strings.Repeat("-", targetWidth-2)) sb.WriteRune(':') - case tw.AlignNone: - sb.WriteString(strings.Repeat("-", targetWidth)) default: - sb.WriteString(strings.Repeat("-", targetWidth)) // Fallback + sb.WriteRune(':') + sb.WriteString(strings.Repeat("-", targetWidth-2)) + sb.WriteRune(':') } result := sb.String() @@ -317,26 +393,22 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader for colIndex < numCols { cellCtx, ok := ctx.Row.Current[colIndex] - align := m.alignment[colIndex] - defaultPadding := tw.Padding{Left: tw.Space, Right: tw.Space} if !ok { cellCtx = tw.CellContext{ - Data: tw.Empty, Align: align, Padding: defaultPadding, + Data: tw.Empty, Align: tw.AlignNone, Padding: defaultPadding, Width: ctx.Row.Widths.Get(colIndex), Merge: tw.MergeState{}, } } else if !cellCtx.Padding.Paddable() { cellCtx.Padding = defaultPadding } - // Add separator isContinuation := ok && cellCtx.Merge.Horizontal.Present && !cellCtx.Merge.Horizontal.Start if colIndex > 0 && !isContinuation { output.WriteString(separator) m.logger.Debugf("renderMarkdownLine: Added separator '%s' before col %d", separator, colIndex) } - // Calculate width and span span := 1 visualWidth := 0 isHMergeStart := ok && cellCtx.Merge.Horizontal.Present && cellCtx.Merge.Horizontal.Start @@ -360,51 +432,62 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader visualWidth = 0 } - // Render segment if isContinuation { m.logger.Debugf("renderMarkdownLine: Skipping col %d (HMerge continuation)", colIndex) - } else { - var formattedSegment string - if isHeaderSep { - // Use header's alignment from ctx.Row.Previous - headerAlign := align - if headerCellCtx, headerOK := ctx.Row.Previous[colIndex]; headerOK { - headerAlign = headerCellCtx.Align - // Preserve tw.AlignNone for separator - if headerAlign != tw.AlignNone && (headerAlign == tw.Empty || headerAlign == tw.Skip) { - headerAlign = tw.AlignCenter - } - } - formattedSegment = m.formatSeparator(visualWidth, headerAlign) - } else { - content := tw.Empty - if colIndex < len(line) { - content = line[colIndex] - } - // For rows, use the header's alignment if specified - rowAlign := align - if headerCellCtx, headerOK := ctx.Row.Previous[colIndex]; headerOK && !isHeaderSep { - if headerCellCtx.Align != tw.AlignNone && headerCellCtx.Align != tw.Empty { - rowAlign = headerCellCtx.Align - } - } - if rowAlign == tw.AlignNone || rowAlign == tw.Empty { - switch ctx.Row.Position { - case tw.Header: - rowAlign = tw.AlignCenter - case tw.Footer: - rowAlign = tw.AlignRight - default: - rowAlign = tw.AlignLeft - } - m.logger.Debugf("renderMarkdownLine: Col %d using default align '%s'", colIndex, rowAlign) - } - formattedSegment = m.formatCell(content, visualWidth, rowAlign, cellCtx.Padding) - } - output.WriteString(formattedSegment) - m.logger.Debugf("renderMarkdownLine: Wrote col %d (span %d, width %d): '%s'", colIndex, span, visualWidth, formattedSegment) + colIndex += span + continue } + var formattedSegment string + if isHeaderSep { + // Separator: use body alignment (if resolved) else header alignment + headerAlign := tw.AlignNone + if colIndex < len(m.headerAlignment) { + headerAlign = m.headerAlignment[colIndex] + } + bodyAlign := tw.AlignNone + if m.bodyResolved && colIndex < len(m.bodyAlignment) { + bodyAlign = m.bodyAlignment[colIndex] + } + sepAlign := m.resolveAlignmentRule(headerAlign, bodyAlign) + formattedSegment = m.formatSeparator(visualWidth, sepAlign) + m.logger.Debugf("renderMarkdownLine: Separator col %d - headerAlign=%s, bodyAlign=%s, final=%s", + colIndex, headerAlign, bodyAlign, sepAlign) + } else { + content := "" + if colIndex < len(line) { + content = line[colIndex] + } + if ctx.Row.Position == tw.Header { + // Header content uses its own alignment + headerAlign := tw.AlignNone + if colIndex < len(m.headerAlignment) { + headerAlign = m.headerAlignment[colIndex] + } + if headerAlign == tw.AlignNone || headerAlign == tw.Empty || headerAlign == tw.Skip { + headerAlign = tw.AlignCenter + } + formattedSegment = m.formatCell(content, visualWidth, headerAlign, cellCtx.Padding) + m.logger.Debugf("renderMarkdownLine: Header col %d - align=%s", colIndex, headerAlign) + } else { + // Body/footer: apply rules + headerAlign := tw.AlignNone + if colIndex < len(m.headerAlignment) { + headerAlign = m.headerAlignment[colIndex] + } + bodyAlign := tw.AlignNone + if m.bodyResolved && colIndex < len(m.bodyAlignment) { + bodyAlign = m.bodyAlignment[colIndex] + } + rowAlign := m.resolveAlignmentRule(headerAlign, bodyAlign) + formattedSegment = m.formatCell(content, visualWidth, rowAlign, cellCtx.Padding) + m.logger.Debugf("renderMarkdownLine: Row col %d - headerAlign=%s, bodyAlign=%s, final=%s", + colIndex, headerAlign, bodyAlign, rowAlign) + } + } + output.WriteString(formattedSegment) + m.logger.Debugf("renderMarkdownLine: Wrote col %d (span %d, width %d): '%s'", + colIndex, span, visualWidth, formattedSegment) colIndex += span } @@ -413,3 +496,5 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader m.w.Write([]byte(output.String())) m.logger.Debugf("renderMarkdownLine: Final line: %s", strings.TrimSuffix(output.String(), tw.NewLine)) } + +var _ tw.Renditioning = (*Markdown)(nil) diff --git a/vendor/github.com/olekukonko/tablewriter/renderer/rgb.go b/vendor/github.com/olekukonko/tablewriter/renderer/rgb.go new file mode 100644 index 0000000000..e7e26563b2 --- /dev/null +++ b/vendor/github.com/olekukonko/tablewriter/renderer/rgb.go @@ -0,0 +1,98 @@ +package renderer + +import ( + "fmt" + "strconv" + "strings" + + "github.com/fatih/color" +) + +// SGR parameters used to build 24-bit ("true color") sequences. +// +// fatih/color keeps the equivalent prefixes unexported, so tablewriter defines +// them here. This lets RGB colors be emitted through the already vendored +// fatih/color version, without requiring a newer release of that dependency. +const ( + fgTrueColor color.Attribute = 38 // select foreground color + bgTrueColor color.Attribute = 48 // select background color + trueColorMode color.Attribute = 2 // "2" selects the 24-bit RGB sub-mode +) + +// clampRGBComponent constrains a single color channel to the valid 0-255 range +// so out-of-range values produce a usable color instead of a malformed escape +// sequence. +func clampRGBComponent(v int) color.Attribute { + switch { + case v < 0: + return 0 + case v > 255: + return 255 + default: + return color.Attribute(v) + } +} + +// RGB returns foreground Colors for a 24-bit ("true color") value. Each of r, g +// and b is a channel in the range 0-255; values outside that range are clamped. +// +// The result is a plain Colors slice, so it can be used anywhere Colors are +// accepted and combined with regular attributes via append, for example: +// +// Tint{FG: append(renderer.RGB(255, 128, 0), color.Bold)} +// +// Terminals that do not support 24-bit color may ignore or approximate the +// sequence. +func RGB(r, g, b int) Colors { + return Colors{fgTrueColor, trueColorMode, clampRGBComponent(r), clampRGBComponent(g), clampRGBComponent(b)} +} + +// BgRGB returns background Colors for a 24-bit ("true color") value. It behaves +// like RGB but sets the background rather than the foreground. +func BgRGB(r, g, b int) Colors { + return Colors{bgTrueColor, trueColorMode, clampRGBComponent(r), clampRGBComponent(g), clampRGBComponent(b)} +} + +// Hex parses a hexadecimal color string and returns foreground Colors for the +// equivalent 24-bit color. Both the shorthand "#RGB" and full "#RRGGBB" forms +// are accepted, with or without the leading '#'. An error is returned for +// strings that are not a valid hex color. +func Hex(s string) (Colors, error) { + r, g, b, err := parseHexColor(s) + if err != nil { + return nil, err + } + return RGB(r, g, b), nil +} + +// BgHex behaves like Hex but returns background Colors. +func BgHex(s string) (Colors, error) { + r, g, b, err := parseHexColor(s) + if err != nil { + return nil, err + } + return BgRGB(r, g, b), nil +} + +// parseHexColor decodes a "#RGB" or "#RRGGBB" color (the leading '#' is +// optional) into its red, green and blue components. +func parseHexColor(s string) (r, g, b int, err error) { + h := strings.TrimPrefix(strings.TrimSpace(s), "#") + + switch len(h) { + case 3: + // Expand shorthand: "abc" -> "aabbcc". + h = string([]byte{h[0], h[0], h[1], h[1], h[2], h[2]}) + case 6: + // Already full length. + default: + return 0, 0, 0, fmt.Errorf("tablewriter: invalid hex color %q: want \"#RGB\" or \"#RRGGBB\"", s) + } + + v, err := strconv.ParseUint(h, 16, 32) + if err != nil { + return 0, 0, 0, fmt.Errorf("tablewriter: invalid hex color %q: %w", s, err) + } + + return int(v >> 16 & 0xFF), int(v >> 8 & 0xFF), int(v & 0xFF), nil +} diff --git a/vendor/github.com/olekukonko/tablewriter/renderer/svg.go b/vendor/github.com/olekukonko/tablewriter/renderer/svg.go index c7d7f1c180..a48cc20456 100644 --- a/vendor/github.com/olekukonko/tablewriter/renderer/svg.go +++ b/vendor/github.com/olekukonko/tablewriter/renderer/svg.go @@ -8,6 +8,7 @@ import ( "github.com/olekukonko/ll" + "github.com/olekukonko/tablewriter/pkg/twwidth" "github.com/olekukonko/tablewriter/tw" ) @@ -384,10 +385,13 @@ func (s *SVG) Debug() []string { // estimateTextWidth estimates text width in SVG units. // Parameter text is the input string to measure. -// Returns the estimated width based on font size and char factor. +// Returns the estimated width based on the text's display width, font size, +// and char factor. Display width is used (instead of the rune count) so that +// wide runes such as CJK characters, which occupy two cells, are sized as two +// columns rather than one. func (s *SVG) estimateTextWidth(text string) float64 { - runeCount := float64(len([]rune(text))) - return runeCount * s.config.FontSize * s.config.ApproxCharWidthFactor + displayWidth := float64(twwidth.Width(text)) + return displayWidth * s.config.FontSize * s.config.ApproxCharWidthFactor } // Footer buffers footer lines for SVG rendering. diff --git a/vendor/github.com/olekukonko/tablewriter/stream.go b/vendor/github.com/olekukonko/tablewriter/stream.go index a0f2a48973..249ee65b4f 100644 --- a/vendor/github.com/olekukonko/tablewriter/stream.go +++ b/vendor/github.com/olekukonko/tablewriter/stream.go @@ -673,12 +673,24 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i }) if len(colsToAdjust) > 0 { for i := 0; i < int(math.Abs(float64(remainingSpace))); i++ { - colIdx := colsToAdjust[i%len(colsToAdjust)] - currentColWidth := t.streamWidths.Get(colIdx) if remainingSpace > 0 { + colIdx := colsToAdjust[i%len(colsToAdjust)] + currentColWidth := t.streamWidths.Get(colIdx) t.streamWidths.Set(colIdx, currentColWidth+1) - } else if remainingSpace < 0 && currentColWidth > 1 { // Don't reduce below 1 - t.streamWidths.Set(colIdx, currentColWidth-1) + } else { + // Find next column that can be reduced (skip columns already at minimum width) + reduced := false + for j := 0; j < len(colsToAdjust); j++ { + colIdx := colsToAdjust[(i+j)%len(colsToAdjust)] + if t.streamWidths.Get(colIdx) > 1 { + t.streamWidths.Set(colIdx, t.streamWidths.Get(colIdx)-1) + reduced = true + break + } + } + if !reduced { + break // All columns at minimum width, no further reduction possible + } } } } diff --git a/vendor/github.com/olekukonko/tablewriter/struct.go b/vendor/github.com/olekukonko/tablewriter/struct.go new file mode 100644 index 0000000000..7fef20a70c --- /dev/null +++ b/vendor/github.com/olekukonko/tablewriter/struct.go @@ -0,0 +1,342 @@ +package tablewriter + +import ( + "reflect" + "strconv" + "strings" + + "github.com/olekukonko/tablewriter/tw" +) + +// Recognized keys for the `tw` struct tag. Centralizing them here means the +// parser switch, and anything that documents or tests the tag vocabulary, +// all refer back to one definition instead of duplicating string literals. +// +// Note: Go struct tags are raw string literals, so a tag written on a field +// (e.g. `tw:"align=left"`) can never reference these constants directly — +// that part of the duplication is a language limitation, not a design +// choice. What these constants buy us is refactor-safety on the parsing +// side: renaming a key is a one-line change instead of a grep-and-pray. +const ( + twTagSkip = "-" // valid as the entire tag, or as a bare key/part + + twKeyName = "name" + twKeyAlign = "align" + twKeyHeaderAlign = "header_align" + twKeyMaxWidth = "max_width" + twKeyPadLeft = "pad_left" + twKeyPadRight = "pad_right" + twKeyTrimSpace = "trim_space" + twKeyTrimTab = "trim_tab" + twKeyAutoFormat = "auto_format" + twKeyWrap = "wrap" +) + +// extractHeadersFromStruct is a thin wrapper around the unified extraction +// function below. It only cares about the header names. +func (t *Table) extractHeadersFromStruct(sample interface{}) []string { + headers, _ := t.extractFieldsAndValuesFromStruct(sample) + return headers +} + +// extractFieldsAndValuesFromStruct is the single source of truth for struct +// reflection. It initiates recursive extraction starting at column offset 0. +func (t *Table) extractFieldsAndValuesFromStruct(sample interface{}) ([]string, []string) { + return t.extractFieldsAndValuesFromStructWithOffset(sample, 0) +} + +// extractFieldsAndValuesFromStructWithOffset recursively processes a struct, +// handling pointers and embedded structs, and natively parses the readable +// 'tw' struct tag. colOffset is the absolute column index of this struct's +// first field, used to keep embedded structs' per-column config (alignment, +// width, padding) addressed correctly within the parent's column layout. +func (t *Table) extractFieldsAndValuesFromStructWithOffset(sample interface{}, colOffset int) ([]string, []string) { + v := reflect.ValueOf(sample) + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return nil, nil + } + v = v.Elem() + } + + if v.Kind() != reflect.Struct { + return nil, nil + } + + typ := v.Type() + headers := make([]string, 0, typ.NumField()) + values := make([]string, 0, typ.NumField()) + + // Apply structural table configuration (alignment, width, padding, ...) + // only on the first row, to avoid redundant work on every appended row + // in Bulk(). Field tags are identical across rows of the same struct + // type, so re-applying them on every row would be wasted reflection and + // map writes, not a behavior change. + isFirstPass := len(t.rows) == 0 + + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + fieldValue := v.Field(i) + + // Skip unexported fields + if field.PkgPath != "" { + continue + } + + // Handle embedded structs recursively, tracking absolute column offset + if field.Anonymous { + h, val := t.extractFieldsAndValuesFromStructWithOffset(fieldValue.Interface(), colOffset+len(headers)) + if h != nil { + headers = append(headers, h...) + values = append(values, val...) + } + continue + } + + var legacyTagName string + skipField := false + twTag := field.Tag.Get("tw") + + // Check legacy priority tags (e.g., json, db) for fallback headers + for _, tagKey := range t.config.Behavior.Structs.Tags { + tagValue := field.Tag.Get(tagKey) + if tagValue != "" { + if tagValue == "-" { + skipField = true + break + } + legacyTagName = tagValue + break + } + } + + // Determine base header name (Fallback: legacy tag -> field name). + // Title-case it now, before any 'tw' name= override is applied below. + // This matters for multi-word Go field names like "KeepMe": titling + // it here turns it into "KEEPME" (one word, already uppercase), so + // the render-time AutoFormat pass (which splits camelCase on case + // transitions to insert spaces) has no lowercase-to-uppercase + // transition left to find and won't turn it into "KEEP ME". An + // explicit tw:"name=..." value is taken verbatim instead, after + // this point, and intentionally skips this step to preserve casing. + headerName := field.Name + if legacyTagName != "" { + headerName = strings.Split(legacyTagName, ",")[0] + } + headerName = tw.Title(headerName) + + // Absolute column index this field will occupy in the rendered table + colIdx := colOffset + len(headers) + + // Parse the 'tw' struct tag schema + if twTag != "" { + if strings.TrimSpace(twTag) == twTagSkip { + continue + } + + parts := strings.Split(twTag, ",") + + // Pass 1: resolve skip first, before any part is allowed to + // mutate table config. A tag like "align=left,-" must not leave + // behind an alignment write for colIdx; if this field is going + // to be dropped, the part that comes before "-" in the tag + // string shouldn't matter, and a stale config write would leak + // onto whichever field ends up occupying this column index. + for _, p := range parts { + kv := strings.SplitN(p, "=", 2) + if strings.TrimSpace(kv[0]) == twTagSkip { + skipField = true + break + } + } + + // Pass 2: only now apply side effects, and only if the field + // survived pass 1. + if !skipField { + for _, p := range parts { + kv := strings.SplitN(p, "=", 2) + key := strings.TrimSpace(kv[0]) + + if len(kv) != 2 { + continue + } + + val := kv[1] // Do not TrimSpace val yet, to preserve padding spaces + valTrimmed := strings.TrimSpace(val) + + switch key { + case twKeyName: + headerName = val // Use exact string, preserve casing + + // Per-Column Configurations + case twKeyAlign: + if isFirstPass { + align := t.parseTwAlign(valTrimmed) + t.setTwColumnAlignment(tw.Row, colIdx, align) + t.setTwColumnAlignment(tw.Footer, colIdx, align) + } + case twKeyHeaderAlign: + if isFirstPass { + t.setTwColumnAlignment(tw.Header, colIdx, t.parseTwAlign(valTrimmed)) + } + case twKeyMaxWidth: + if isFirstPass { + if mw, err := strconv.Atoi(valTrimmed); err == nil && mw > 0 { + t.setTwColumnMaxWidth(colIdx, mw) + } + } + case twKeyPadLeft: + if isFirstPass { + t.setTwColumnPadding(colIdx, val, true) + } + case twKeyPadRight: + if isFirstPass { + t.setTwColumnPadding(colIdx, val, false) + } + + // Table-wide configurations. These cannot be scoped to a + // single column: TrimSpace/TrimTab are a single flag on + // t.config.Behavior used for every cell in the table + // (header, row and footer alike), and Header.Formatting + // .AutoFormat is one flag per section, not a per-column + // array like Alignment.PerColumn is. Setting one of + // these on a field changes that behavior for every + // other column too, including ones processed earlier. + // Whichever field sets a given key last "wins" for the + // whole table. If genuinely independent per-column + // control is needed, these settings don't belong in a + // per-field tag — that would require making + // CellFormatting per-column-aware first. + case twKeyTrimSpace: + if isFirstPass { + t.config.Behavior.TrimSpace = t.parseTwState(valTrimmed) + } + case twKeyTrimTab: + if isFirstPass { + t.config.Behavior.TrimTab = t.parseTwState(valTrimmed) + } + case twKeyAutoFormat: + if isFirstPass { + t.config.Header.Formatting.AutoFormat = t.parseTwState(valTrimmed) + } + case twKeyWrap: + if isFirstPass { + switch valTrimmed { + case "none": + t.config.Row.Formatting.AutoWrap = tw.WrapNone + case "normal": + t.config.Row.Formatting.AutoWrap = tw.WrapNormal + case "truncate": + t.config.Row.Formatting.AutoWrap = tw.WrapTruncate + case "break": + t.config.Row.Formatting.AutoWrap = tw.WrapBreak + } + } + } + } + } + } + + if skipField { + continue + } + + headers = append(headers, headerName) + + // Extract value + value := "" + if !strings.Contains(legacyTagName, ",omitempty") || !fieldValue.IsZero() { + value = t.convertToString(fieldValue.Interface()) + } + values = append(values, value) + } + + return headers, values +} + +// Configuration Helper Methods + +func (t *Table) parseTwAlign(val string) tw.Align { + switch strings.ToLower(val) { + case "center": + return tw.AlignCenter + case "left": + return tw.AlignLeft + case "right": + return tw.AlignRight + default: + return tw.AlignDefault + } +} + +func (t *Table) parseTwState(val string) tw.State { + if strings.ToLower(val) == "true" { + return tw.On + } + return tw.Off +} + +func (t *Table) setTwColumnAlignment(pos tw.Position, colIdx int, align tw.Align) { + var alignConfig *[]tw.Align + switch pos { + case tw.Header: + alignConfig = &t.config.Header.Alignment.PerColumn + case tw.Row: + alignConfig = &t.config.Row.Alignment.PerColumn + case tw.Footer: + alignConfig = &t.config.Footer.Alignment.PerColumn + } + + if alignConfig != nil { + if *alignConfig == nil { + *alignConfig = make([]tw.Align, colIdx+1) + } + for len(*alignConfig) <= colIdx { + *alignConfig = append(*alignConfig, tw.Skip) + } + (*alignConfig)[colIdx] = align + } +} + +// setTwColumnMaxWidth records a per-column max width for the header, row, +// and footer sections. This must operate on pointers to the actual config +// fields, not copies: tw.CellWidth.PerColumn is a map, and on a table that +// has never had a per-column width set before, that map is nil. Ranging +// over a []tw.CellWidth of *values* would initialize the map only on a +// local copy of the struct, which is discarded at the end of the loop body +// — the real t.config.*.ColMaxWidths.PerColumn would stay nil and the tag +// would silently do nothing. See setTwColumnPadding below for the same +// pattern done correctly, which this mirrors. +func (t *Table) setTwColumnMaxWidth(colIdx, mw int) { + configs := []*tw.CellWidth{ + &t.config.Header.ColMaxWidths, + &t.config.Row.ColMaxWidths, + &t.config.Footer.ColMaxWidths, + } + for _, cfg := range configs { + if cfg.PerColumn == nil { + cfg.PerColumn = tw.NewMapper[int, int]() + } + cfg.PerColumn.Set(colIdx, mw) + } +} + +func (t *Table) setTwColumnPadding(colIdx int, padStr string, isLeft bool) { + configs := []*tw.CellPadding{&t.config.Header.Padding, &t.config.Row.Padding, &t.config.Footer.Padding} + + for _, p := range configs { + if p.PerColumn == nil { + p.PerColumn = make([]tw.Padding, colIdx+1) + } + for len(p.PerColumn) <= colIdx { + p.PerColumn = append(p.PerColumn, tw.Padding{}) + } + + if isLeft { + p.PerColumn[colIdx].Left = padStr + } else { + p.PerColumn[colIdx].Right = padStr + } + p.PerColumn[colIdx].Overwrite = true + } +} diff --git a/vendor/github.com/olekukonko/tablewriter/tablewriter.go b/vendor/github.com/olekukonko/tablewriter/tablewriter.go index 9fd7404565..10e5067adc 100644 --- a/vendor/github.com/olekukonko/tablewriter/tablewriter.go +++ b/vendor/github.com/olekukonko/tablewriter/tablewriter.go @@ -981,7 +981,7 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string padLeftWidth := twwidth.Width(colPad.Left) padRightWidth := twwidth.Width(colPad.Right) - effectiveContentMaxWidth := t.calculateContentMaxWidth(i, config, padLeftWidth, padRightWidth, isStreaming) + effectiveContentMaxWidth := t.calculateContentMaxWidth(i, config, padLeftWidth, padRightWidth, isStreaming, effectiveNumCols) if config.Formatting.AutoFormat.Enabled() { cellContent = tw.Title(strings.Join(tw.SplitCamelCase(cellContent), tw.Space)) diff --git a/vendor/github.com/olekukonko/tablewriter/tw/mapper.go b/vendor/github.com/olekukonko/tablewriter/tw/mapper.go index 1eee9eb7be..48c8fcd27c 100644 --- a/vendor/github.com/olekukonko/tablewriter/tw/mapper.go +++ b/vendor/github.com/olekukonko/tablewriter/tw/mapper.go @@ -20,6 +20,35 @@ func NewMapper[K comparable, V any]() Mapper[K, V] { return make(Mapper[K, V]) } +// NewMapperWithKeys creates a Mapper with the given keys and zero values +func NewMapperWithKeys[K comparable, V any](keys ...K) Mapper[K, V] { + m := make(Mapper[K, V], len(keys)) + for _, k := range keys { + m[k] = *new(V) + } + return m +} + +// NewMapperFromPairs creates a Mapper from alternating key-value pairs +func NewMapperFromPairs[K comparable, V any](pairs ...any) Mapper[K, V] { + m := make(Mapper[K, V]) + for i := 0; i < len(pairs); i += 2 { + if i+1 >= len(pairs) { + break + } + k, ok := pairs[i].(K) + if !ok { + continue + } + v, ok := pairs[i+1].(V) + if !ok { + continue + } + m[k] = v + } + return m +} + // Get returns the value associated with the key. // If the key doesn't exist or the map is nil, it returns the zero value for the value type. func (m Mapper[K, V]) Get(key K) V { diff --git a/vendor/github.com/olekukonko/tablewriter/zoo.go b/vendor/github.com/olekukonko/tablewriter/zoo.go index c24a53d1d5..4cdcdc6e65 100644 --- a/vendor/github.com/olekukonko/tablewriter/zoo.go +++ b/vendor/github.com/olekukonko/tablewriter/zoo.go @@ -795,15 +795,21 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error { // Sort columns for deterministic reduction sortedCols := workingWidths.SortedKeys() for i := 0; i < overDistributed; i++ { + reduced := false // Reduce from highest-indexed column for j := len(sortedCols) - 1; j >= 0; j-- { col := sortedCols[j] if workingWidths.Get(col) > 1 && naturalColumnWidths.Get(col) < workingWidths.Get(col) { workingWidths.Set(col, workingWidths.Get(col)-1) ctx.logger.Debugf("Reduced col %d by 1 to %d", col, workingWidths.Get(col)) + reduced = true break } } + if !reduced { + // No eligible column found, no further reduction possible + break + } } } } @@ -970,7 +976,7 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error { // calculateContentMaxWidth computes the maximum content width for a column, accounting for padding and mode-specific constraints. // Returns the effective content width (after subtracting padding) for the given column index. -func (t *Table) calculateContentMaxWidth(colIdx int, config tw.CellConfig, padLeftWidth, padRightWidth int, isStreaming bool) int { +func (t *Table) calculateContentMaxWidth(colIdx int, config tw.CellConfig, padLeftWidth, padRightWidth int, isStreaming bool, numCols int) int { var effectiveContentMaxWidth int if isStreaming { @@ -1001,11 +1007,26 @@ func (t *Table) calculateContentMaxWidth(colIdx int, config tw.CellConfig, padLe colIdx, constraintTotalCellWidth) } - // Check new Widths.Global + // Check new Widths.Global. It is a table-wide limit, so split it + // across columns (same idea as MaxWidth). Applying the full Global + // value per column wraps too wide, then later shrink+truncate + // drops characters (see #328). if !hasConstraint && t.config.Widths.Global > 0 { - constraintTotalCellWidth = t.config.Widths.Global + n := numCols + if n < 1 { + n = 1 + } + sepW := 0 + if n > 1 && t.renderer != nil && t.renderer.Config().Settings.Separators.BetweenColumns.Enabled() { + sepW = twwidth.Width(t.renderer.Config().Symbols.Column()) * (n - 1) + } + available := t.config.Widths.Global - sepW + if available < n { + available = n + } + constraintTotalCellWidth = available / n hasConstraint = true - t.logger.Debugf("calculateContentMaxWidth: Using Widths.Global = %d", constraintTotalCellWidth) + t.logger.Debugf("calculateContentMaxWidth: Using Widths.Global = %d as per-column %d (%d cols)", t.config.Widths.Global, constraintTotalCellWidth, n) } } @@ -1645,92 +1666,3 @@ func (t *Table) updateWidths(row []string, widths tw.Mapper[int, int], padding t } } } - -// extractHeadersFromStruct is now a thin wrapper around the new unified function. -// It only cares about the header names. -func (t *Table) extractHeadersFromStruct(sample interface{}) []string { - headers, _ := t.extractFieldsAndValuesFromStruct(sample) - return headers -} - -// extractFieldsAndValuesFromStruct is the new single source of truth for struct reflection. -// It recursively processes a struct, handling pointers and embedded structs, -// and returns two slices: one for header names and one for string-converted values. -func (t *Table) extractFieldsAndValuesFromStruct(sample interface{}) ([]string, []string) { - v := reflect.ValueOf(sample) - if v.Kind() == reflect.Ptr { - if v.IsNil() { - return nil, nil - } - v = v.Elem() - } - - if v.Kind() != reflect.Struct { - return nil, nil - } - - typ := v.Type() - headers := make([]string, 0, typ.NumField()) - values := make([]string, 0, typ.NumField()) - - for i := 0; i < typ.NumField(); i++ { - field := typ.Field(i) - fieldValue := v.Field(i) - - // Skip unexported fields - if field.PkgPath != "" { - continue - } - - // Handle embedded structs recursively - if field.Anonymous { - h, val := t.extractFieldsAndValuesFromStruct(fieldValue.Interface()) - if h != nil { - headers = append(headers, h...) - values = append(values, val...) - } - continue - } - - var tagName string - skipField := false - - // Loop through the priority list of configured tags (e.g., ["json", "db"]) - for _, tagKey := range t.config.Behavior.Structs.Tags { - tagValue := field.Tag.Get(tagKey) - - // If a tag is found... - if tagValue != "" { - // If the tag is "-", this field should be skipped entirely. - if tagValue == "-" { - skipField = true - break // Stop processing tags for this field. - } - // Otherwise, we've found our highest-priority tag. Store it and stop. - tagName = tagValue - break // Stop processing tags for this field. - } - } - - // If the field was marked for skipping, continue to the next field. - if skipField { - continue - } - - // Determine header name from the tag or fallback to the field name - headerName := field.Name - if tagName != "" { - headerName = strings.Split(tagName, ",")[0] - } - headers = append(headers, tw.Title(headerName)) - - // Determine value, respecting omitempty from the found tag - value := "" - if !strings.Contains(tagName, ",omitempty") || !fieldValue.IsZero() { - value = t.convertToString(fieldValue.Interface()) - } - values = append(values, value) - } - - return headers, values -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/annotations.go b/vendor/github.com/open-policy-agent/opa/ast/annotations.go deleted file mode 100644 index 3bc5fb36a5..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/annotations.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2022 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -type ( - // Annotations represents metadata attached to other AST nodes such as rules. - Annotations = v1.Annotations - - // SchemaAnnotation contains a schema declaration for the document identified by the path. - SchemaAnnotation = v1.SchemaAnnotation - - AuthorAnnotation = v1.AuthorAnnotation - - RelatedResourceAnnotation = v1.RelatedResourceAnnotation - - AnnotationSet = v1.AnnotationSet - - AnnotationsRef = v1.AnnotationsRef - - AnnotationsRefSet = v1.AnnotationsRefSet - - FlatAnnotationsRefSet = v1.FlatAnnotationsRefSet -) - -func NewAnnotationsRef(a *Annotations) *AnnotationsRef { - return v1.NewAnnotationsRef(a) -} - -func BuildAnnotationSet(modules []*Module) (*AnnotationSet, Errors) { - return v1.BuildAnnotationSet(modules) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/ast.go b/vendor/github.com/open-policy-agent/opa/ast/ast.go new file mode 100644 index 0000000000..523f36799d --- /dev/null +++ b/vendor/github.com/open-policy-agent/opa/ast/ast.go @@ -0,0 +1,2051 @@ +// Copyright 2026 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. +// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. +// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. +package ast + +import ( + "encoding/json" + "errors" + "fmt" + "io" + + astJSON "github.com/open-policy-agent/opa/ast/json" + v1 "github.com/open-policy-agent/opa/v1/ast" +) + +type ( + // Annotations represents metadata attached to other AST nodes such as rules. + Annotations = v1.Annotations + + // SchemaAnnotation contains a schema declaration for the document identified by the path. + SchemaAnnotation = v1.SchemaAnnotation + + AuthorAnnotation = v1.AuthorAnnotation + + RelatedResourceAnnotation = v1.RelatedResourceAnnotation + + AnnotationSet = v1.AnnotationSet + + AnnotationsRef = v1.AnnotationsRef + + AnnotationsRefSet = v1.AnnotationsRefSet + + FlatAnnotationsRefSet = v1.FlatAnnotationsRefSet +) + +func NewAnnotationsRef(a *Annotations) *AnnotationsRef { + return v1.NewAnnotationsRef(a) +} + +func BuildAnnotationSet(modules []*Module) (*AnnotationSet, Errors) { + return v1.BuildAnnotationSet(modules) +} + +// Builtins is the registry of built-in functions supported by OPA. +// Call RegisterBuiltin to add a new built-in. +var Builtins = v1.Builtins + +// RegisterBuiltin adds a new built-in function to the registry. +func RegisterBuiltin(b *Builtin) { + v1.RegisterBuiltin(b) +} + +// DefaultBuiltins is the registry of built-in functions supported in OPA +// by default. When adding a new built-in function to OPA, update this +// list. +var DefaultBuiltins = v1.DefaultBuiltins + +// BuiltinMap provides a convenient mapping of built-in names to +// built-in definitions. +var BuiltinMap = v1.BuiltinMap + +// Deprecated: Builtins can now be directly annotated with the +// Nondeterministic property, and when set to true, will be ignored +// for partial evaluation. +var IgnoreDuringPartialEval = v1.IgnoreDuringPartialEval + +/** + * Unification + */ + +// Equality represents the "=" operator. +var Equality = v1.Equality + +/** + * Assignment + */ + +// Assign represents the assignment (":=") operator. +var Assign = v1.Assign + +// Member represents the `in` (infix) operator. +var Member = v1.Member + +// MemberWithKey represents the `in` (infix) operator when used +// with two terms on the lhs, i.e., `k, v in obj`. +var MemberWithKey = v1.MemberWithKey + +var GreaterThan = v1.GreaterThan + +var GreaterThanEq = v1.GreaterThanEq + +// LessThan represents the "<" comparison operator. +var LessThan = v1.LessThan + +var LessThanEq = v1.LessThanEq + +var NotEqual = v1.NotEqual + +// Equal represents the "==" comparison operator. +var Equal = v1.Equal + +var Plus = v1.Plus + +var Minus = v1.Minus + +var Multiply = v1.Multiply + +var Divide = v1.Divide + +var Round = v1.Round + +var Ceil = v1.Ceil + +var Floor = v1.Floor + +var Abs = v1.Abs + +var Rem = v1.Rem + +/** + * Bitwise + */ + +var BitsOr = v1.BitsOr + +var BitsAnd = v1.BitsAnd + +var BitsNegate = v1.BitsNegate + +var BitsXOr = v1.BitsXOr + +var BitsShiftLeft = v1.BitsShiftLeft + +var BitsShiftRight = v1.BitsShiftRight + +/** + * Sets + */ + +var And = v1.And + +// Or performs a union operation on sets. +var Or = v1.Or + +var Intersection = v1.Intersection + +var Union = v1.Union + +/** + * Aggregates + */ + +var Count = v1.Count + +var Sum = v1.Sum + +var Product = v1.Product + +var Max = v1.Max + +var Min = v1.Min + +/** + * Sorting + */ + +var Sort = v1.Sort + +/** + * Arrays + */ + +var ArrayConcat = v1.ArrayConcat + +var ArraySlice = v1.ArraySlice + +var ArrayReverse = v1.ArrayReverse + +/** + * Conversions + */ + +var ToNumber = v1.ToNumber + +/** + * Regular Expressions + */ + +var RegexMatch = v1.RegexMatch + +var RegexIsValid = v1.RegexIsValid + +var RegexFindAllStringSubmatch = v1.RegexFindAllStringSubmatch + +var RegexTemplateMatch = v1.RegexTemplateMatch + +var RegexSplit = v1.RegexSplit + +// RegexFind takes two strings and a number, the pattern, the value and number of match values to +// return, -1 means all match values. +var RegexFind = v1.RegexFind + +// GlobsMatch takes two strings regexp-style strings and evaluates to true if their +// intersection matches a non-empty set of non-empty strings. +// Examples: +// - "a.a." and ".b.b" -> true. +// - "[a-z]*" and [0-9]+" -> not true. +var GlobsMatch = v1.GlobsMatch + +/** + * Strings + */ + +var AnyPrefixMatch = v1.AnyPrefixMatch + +var AnySuffixMatch = v1.AnySuffixMatch + +var Concat = v1.Concat + +var FormatInt = v1.FormatInt + +var IndexOf = v1.IndexOf + +var IndexOfN = v1.IndexOfN + +var Substring = v1.Substring + +var Contains = v1.Contains + +var StringCount = v1.StringCount + +var StartsWith = v1.StartsWith + +var EndsWith = v1.EndsWith + +var Lower = v1.Lower + +var Upper = v1.Upper + +var Split = v1.Split + +var Replace = v1.Replace + +var ReplaceN = v1.ReplaceN + +var RegexReplace = v1.RegexReplace + +var Trim = v1.Trim + +var TrimLeft = v1.TrimLeft + +var TrimPrefix = v1.TrimPrefix + +var TrimRight = v1.TrimRight + +var TrimSuffix = v1.TrimSuffix + +var TrimSpace = v1.TrimSpace + +var Sprintf = v1.Sprintf + +var StringReverse = v1.StringReverse + +var RenderTemplate = v1.RenderTemplate + +/** + * Numbers + */ + +// RandIntn returns a random number 0 - n +// Marked non-deterministic because it relies on RNG internally. +var RandIntn = v1.RandIntn + +var NumbersRange = v1.NumbersRange + +var NumbersRangeStep = v1.NumbersRangeStep + +/** + * Units + */ + +var UnitsParse = v1.UnitsParse + +var UnitsParseBytes = v1.UnitsParseBytes + +// +/** + * Type + */ + +// UUIDRFC4122 returns a version 4 UUID string. +// Marked non-deterministic because it relies on RNG internally. +var UUIDRFC4122 = v1.UUIDRFC4122 + +var UUIDParse = v1.UUIDParse + +/** + * JSON + */ + +var JSONFilter = v1.JSONFilter + +var JSONRemove = v1.JSONRemove + +var JSONPatch = v1.JSONPatch + +var ObjectSubset = v1.ObjectSubset + +var ObjectUnion = v1.ObjectUnion + +var ObjectUnionN = v1.ObjectUnionN + +var ObjectRemove = v1.ObjectRemove + +var ObjectFilter = v1.ObjectFilter + +var ObjectGet = v1.ObjectGet + +var ObjectKeys = v1.ObjectKeys + +/* + * Encoding + */ + +var JSONMarshal = v1.JSONMarshal + +var JSONMarshalWithOptions = v1.JSONMarshalWithOptions + +var JSONUnmarshal = v1.JSONUnmarshal + +var JSONIsValid = v1.JSONIsValid + +var Base64Encode = v1.Base64Encode + +var Base64Decode = v1.Base64Decode + +var Base64IsValid = v1.Base64IsValid + +var Base64UrlEncode = v1.Base64UrlEncode + +var Base64UrlEncodeNoPad = v1.Base64UrlEncodeNoPad + +var Base64UrlDecode = v1.Base64UrlDecode + +var URLQueryDecode = v1.URLQueryDecode + +var URLQueryEncode = v1.URLQueryEncode + +var URLQueryEncodeObject = v1.URLQueryEncodeObject + +var URLQueryDecodeObject = v1.URLQueryDecodeObject + +var YAMLMarshal = v1.YAMLMarshal + +var YAMLUnmarshal = v1.YAMLUnmarshal + +// YAMLIsValid verifies the input string is a valid YAML document. +var YAMLIsValid = v1.YAMLIsValid + +var HexEncode = v1.HexEncode + +var HexDecode = v1.HexDecode + +/** + * Tokens + */ + +var JWTDecode = v1.JWTDecode + +var JWTVerifyRS256 = v1.JWTVerifyRS256 + +var JWTVerifyRS384 = v1.JWTVerifyRS384 + +var JWTVerifyRS512 = v1.JWTVerifyRS512 + +var JWTVerifyPS256 = v1.JWTVerifyPS256 + +var JWTVerifyPS384 = v1.JWTVerifyPS384 + +var JWTVerifyPS512 = v1.JWTVerifyPS512 + +var JWTVerifyES256 = v1.JWTVerifyES256 + +var JWTVerifyES384 = v1.JWTVerifyES384 + +var JWTVerifyES512 = v1.JWTVerifyES512 + +var JWTVerifyHS256 = v1.JWTVerifyHS256 + +var JWTVerifyHS384 = v1.JWTVerifyHS384 + +var JWTVerifyHS512 = v1.JWTVerifyHS512 + +// Marked non-deterministic because it relies on time internally. +var JWTDecodeVerify = v1.JWTDecodeVerify + +// Marked non-deterministic because it relies on RNG internally. +var JWTEncodeSignRaw = v1.JWTEncodeSignRaw + +// Marked non-deterministic because it relies on RNG internally. +var JWTEncodeSign = v1.JWTEncodeSign + +/** + * Time + */ + +// Marked non-deterministic because it relies on time directly. +var NowNanos = v1.NowNanos + +var ParseNanos = v1.ParseNanos + +var ParseRFC3339Nanos = v1.ParseRFC3339Nanos + +var ParseDurationNanos = v1.ParseDurationNanos + +var Format = v1.Format + +var Date = v1.Date + +var Clock = v1.Clock + +var Weekday = v1.Weekday + +var AddDate = v1.AddDate + +var Diff = v1.Diff + +/** + * Crypto. + */ + +var CryptoX509ParseCertificates = v1.CryptoX509ParseCertificates + +var CryptoX509ParseAndVerifyCertificates = v1.CryptoX509ParseAndVerifyCertificates + +var CryptoX509ParseAndVerifyCertificatesWithOptions = v1.CryptoX509ParseAndVerifyCertificatesWithOptions + +var CryptoX509ParseCertificateRequest = v1.CryptoX509ParseCertificateRequest + +var CryptoX509ParseKeyPair = v1.CryptoX509ParseKeyPair +var CryptoX509ParseRSAPrivateKey = v1.CryptoX509ParseRSAPrivateKey + +var CryptoParsePrivateKeys = v1.CryptoParsePrivateKeys + +var CryptoMd5 = v1.CryptoMd5 + +var CryptoSha1 = v1.CryptoSha1 + +var CryptoSha256 = v1.CryptoSha256 + +var CryptoHmacMd5 = v1.CryptoHmacMd5 + +var CryptoHmacSha1 = v1.CryptoHmacSha1 + +var CryptoHmacSha256 = v1.CryptoHmacSha256 + +var CryptoHmacSha512 = v1.CryptoHmacSha512 + +var CryptoHmacEqual = v1.CryptoHmacEqual + +/** + * Graphs. + */ + +var WalkBuiltin = v1.WalkBuiltin + +var ReachableBuiltin = v1.ReachableBuiltin + +var ReachablePathsBuiltin = v1.ReachablePathsBuiltin + +/** + * Type + */ + +var IsNumber = v1.IsNumber + +var IsString = v1.IsString + +var IsBoolean = v1.IsBoolean + +var IsArray = v1.IsArray + +var IsSet = v1.IsSet + +var IsObject = v1.IsObject + +var IsNull = v1.IsNull + +/** + * Type Name + */ + +// TypeNameBuiltin returns the type of the input. +var TypeNameBuiltin = v1.TypeNameBuiltin + +/** + * HTTP Request + */ + +// Marked non-deterministic because HTTP request results can be non-deterministic. +var HTTPSend = v1.HTTPSend + +/** + * GraphQL + */ + +// GraphQLParse returns a pair of AST objects from parsing/validation. +var GraphQLParse = v1.GraphQLParse + +// GraphQLParseAndVerify returns a boolean and a pair of AST object from parsing/validation. +var GraphQLParseAndVerify = v1.GraphQLParseAndVerify + +// GraphQLParseQuery parses the input GraphQL query and returns a JSON +// representation of its AST. +var GraphQLParseQuery = v1.GraphQLParseQuery + +// GraphQLParseSchema parses the input GraphQL schema and returns a JSON +// representation of its AST. +var GraphQLParseSchema = v1.GraphQLParseSchema + +// GraphQLIsValid returns true if a GraphQL query is valid with a given +// schema, and returns false for all other inputs. +var GraphQLIsValid = v1.GraphQLIsValid + +// GraphQLSchemaIsValid returns true if the input is valid GraphQL schema, +// and returns false for all other inputs. +var GraphQLSchemaIsValid = v1.GraphQLSchemaIsValid + +/** + * JSON Schema + */ + +// JSONSchemaVerify returns empty string if the input is valid JSON schema +// and returns error string for all other inputs. +var JSONSchemaVerify = v1.JSONSchemaVerify + +// JSONMatchSchema returns empty array if the document matches the JSON schema, +// and returns non-empty array with error objects otherwise. +var JSONMatchSchema = v1.JSONMatchSchema + +/** + * Cloud Provider Helper Functions + */ + +var ProvidersAWSSignReqObj = v1.ProvidersAWSSignReqObj + +/** + * Rego + */ + +var RegoParseModule = v1.RegoParseModule + +var RegoMetadataChain = v1.RegoMetadataChain + +// RegoMetadataRule returns the metadata for the active rule +var RegoMetadataRule = v1.RegoMetadataRule + +/** + * OPA + */ + +// Marked non-deterministic because of unpredictable config/environment-dependent results. +var OPARuntime = v1.OPARuntime + +/** + * Trace + */ + +var Trace = v1.Trace + +/** + * Glob + */ + +var GlobMatch = v1.GlobMatch + +var GlobQuoteMeta = v1.GlobQuoteMeta + +/** + * Networking + */ + +var NetCIDRIntersects = v1.NetCIDRIntersects + +var NetCIDRExpand = v1.NetCIDRExpand + +var NetCIDRContains = v1.NetCIDRContains + +var NetCIDRContainsMatches = v1.NetCIDRContainsMatches + +var NetCIDRMerge = v1.NetCIDRMerge + +var NetCIDRIsValid = v1.NetCIDRIsValid + +// Marked non-deterministic because DNS resolution results can be non-deterministic. +var NetLookupIPAddr = v1.NetLookupIPAddr + +/** + * Semantic Versions + */ + +var SemVerIsValid = v1.SemVerIsValid + +var SemVerCompare = v1.SemVerCompare + +/** + * Printing + */ + +// Print is a special built-in function that writes zero or more operands +// to a message buffer. The caller controls how the buffer is displayed. The +// operands may be of any type. Furthermore, unlike other built-in functions, +// undefined operands DO NOT cause the print() function to fail during +// evaluation. +var Print = v1.Print + +// InternalPrint represents the internal implementation of the print() function. +// The compiler rewrites print() calls to refer to the internal implementation. +var InternalPrint = v1.InternalPrint + +/** + * Deprecated built-ins. + */ + +// SetDiff has been replaced by the minus built-in. +var SetDiff = v1.SetDiff + +// NetCIDROverlap has been replaced by the `net.cidr_contains` built-in. +var NetCIDROverlap = v1.NetCIDROverlap + +// CastArray checks the underlying type of the input. If it is array or set, an array +// containing the values is returned. If it is not an array, an error is thrown. +var CastArray = v1.CastArray + +// CastSet checks the underlying type of the input. +// If it is a set, the set is returned. +// If it is an array, the array is returned in set form (all duplicates removed) +// If neither, an error is thrown +var CastSet = v1.CastSet + +// CastString returns input if it is a string; if not returns error. +// For formatting variables, see sprintf +var CastString = v1.CastString + +// CastBoolean returns input if it is a boolean; if not returns error. +var CastBoolean = v1.CastBoolean + +// CastNull returns null if input is null; if not returns error. +var CastNull = v1.CastNull + +// CastObject returns the given object if it is null; throws an error otherwise +var CastObject = v1.CastObject + +// RegexMatchDeprecated declares `re_match` which has been deprecated. Use `regex.match` instead. +var RegexMatchDeprecated = v1.RegexMatchDeprecated + +// All takes a list and returns true if all of the items +// are true. A collection of length 0 returns true. +var All = v1.All + +// Any takes a collection and returns true if any of the items +// is true. A collection of length 0 returns false. +var Any = v1.Any + +// Builtin represents a built-in function supported by OPA. Every built-in +// function is uniquely identified by a name. +type Builtin = v1.Builtin + +// VersonIndex contains an index from built-in function name, language feature, +// and future rego keyword to version number. During the build, this is used to +// create an index of the minimum version required for the built-in/feature/kw. +type VersionIndex = v1.VersionIndex + +// In the compiler, we used this to check that we're OK working with ref heads. +// If this isn't present, we'll fail. This is to ensure that older versions of +// OPA can work with policies that we're compiling -- if they don't know ref +// heads, they wouldn't be able to parse them. +const FeatureRefHeadStringPrefixes = v1.FeatureRefHeadStringPrefixes +const FeatureRefHeads = v1.FeatureRefHeads +const FeatureRegoV1 = v1.FeatureRegoV1 +const FeatureRegoV1Import = v1.FeatureRegoV1Import + +// Capabilities defines a structure containing data that describes the capabilities +// or features supported by a particular version of OPA. +type Capabilities = v1.Capabilities + +// WasmABIVersion captures the Wasm ABI version. Its `Minor` version is indicating +// backwards-compatible changes. +type WasmABIVersion = v1.WasmABIVersion + +// CapabilitiesForThisVersion returns the capabilities of this version of OPA. +func CapabilitiesForThisVersion() *Capabilities { + return v1.CapabilitiesForThisVersion(v1.CapabilitiesRegoVersion(DefaultRegoVersion)) +} + +// LoadCapabilitiesJSON loads a JSON serialized capabilities structure from the reader r. +func LoadCapabilitiesJSON(r io.Reader) (*Capabilities, error) { + return v1.LoadCapabilitiesJSON(r) +} + +// LoadCapabilitiesVersion loads a JSON serialized capabilities structure from the specific version. +func LoadCapabilitiesVersion(version string) (*Capabilities, error) { + return v1.LoadCapabilitiesVersion(version) +} + +// LoadCapabilitiesFile loads a JSON serialized capabilities structure from a file. +func LoadCapabilitiesFile(file string) (*Capabilities, error) { + return v1.LoadCapabilitiesFile(file) +} + +// LoadCapabilitiesVersions loads all capabilities versions +func LoadCapabilitiesVersions() ([]string, error) { + return v1.LoadCapabilitiesVersions() +} + +// UnificationErrDetail describes a type mismatch error when two values are +// unified (e.g., x = [1,2,y]). +type UnificationErrDetail = v1.UnificationErrDetail + +// RefErrUnsupportedDetail describes an undefined reference error where the +// referenced value does not support dereferencing (e.g., scalars). +type RefErrUnsupportedDetail = v1.RefErrUnsupportedDetail + +// RefErrInvalidDetail describes an undefined reference error where the referenced +// value does not support the reference operand (e.g., missing object key, +// invalid key type, etc.) +type RefErrInvalidDetail = v1.RefErrInvalidDetail + +// Compare returns an integer indicating whether two AST values are less than, +// equal to, or greater than each other. +// +// If a is less than b, the return value is negative. If a is greater than b, +// the return value is positive. If a is equal to b, the return value is zero. +// +// Different types are never equal to each other. For comparison purposes, types +// are sorted as follows: +// +// nil < Null < Boolean < Number < String < Var < Ref < Array < Object < Set < +// ArrayComprehension < ObjectComprehension < SetComprehension < Expr < SomeDecl +// < With < Body < Rule < Import < Package < Module. +// +// Arrays and Refs are equal if and only if both a and b have the same length +// and all corresponding elements are equal. If one element is not equal, the +// return value is the same as for the first differing element. If all elements +// are equal but a and b have different lengths, the shorter is considered less +// than the other. +// +// Objects are considered equal if and only if both a and b have the same sorted +// (key, value) pairs and are of the same length. Other comparisons are +// consistent but not defined. +// +// Sets are considered equal if and only if the symmetric difference of a and b +// is empty. +// Other comparisons are consistent but not defined. +func Compare(a, b any) int { + return v1.Compare(a, b) +} + +// CompileErrorLimitDefault is the default number errors a compiler will allow before +// exiting. +const CompileErrorLimitDefault = 10 + +// Compiler contains the state of a compilation process. +type Compiler = v1.Compiler + +// CompilerStage defines the interface for stages in the compiler. +type CompilerStage = v1.CompilerStage + +// CompilerEvalMode allows toggling certain stages that are only +// needed for certain modes, Concretely, only "topdown" mode will +// have the compiler build comprehension and rule indices. +type CompilerEvalMode = v1.CompilerEvalMode + +const ( + // EvalModeTopdown (default) instructs the compiler to build rule + // and comprehension indices used by topdown evaluation. + EvalModeTopdown = v1.EvalModeTopdown + + // EvalModeIR makes the compiler skip the stages for comprehension + // and rule indices. + EvalModeIR = v1.EvalModeIR +) + +// CompilerStageDefinition defines a compiler stage +type CompilerStageDefinition = v1.CompilerStageDefinition + +// RulesOptions defines the options for retrieving rules by Ref from the +// compiler. +type RulesOptions = v1.RulesOptions + +// QueryContext contains contextual information for running an ad-hoc query. +// +// Ad-hoc queries can be run in the context of a package and imports may be +// included to provide concise access to data. +type QueryContext = v1.QueryContext + +// NewQueryContext returns a new QueryContext object. +func NewQueryContext() *QueryContext { + return v1.NewQueryContext() +} + +// QueryCompiler defines the interface for compiling ad-hoc queries. +type QueryCompiler = v1.QueryCompiler + +// QueryCompilerStage defines the interface for stages in the query compiler. +type QueryCompilerStage = v1.QueryCompilerStage + +// QueryCompilerStageDefinition defines a QueryCompiler stage +type QueryCompilerStageDefinition = v1.QueryCompilerStageDefinition + +// NewCompiler returns a new empty compiler. +func NewCompiler() *Compiler { + return v1.NewCompiler().WithDefaultRegoVersion(DefaultRegoVersion) +} + +// ModuleLoader defines the interface that callers can implement to enable lazy +// loading of modules during compilation. +type ModuleLoader = v1.ModuleLoader + +// SafetyCheckVisitorParams defines the AST visitor parameters to use for collecting +// variables during the safety check. This has to be exported because it's relied on +// by the copy propagation implementation in topdown. +var SafetyCheckVisitorParams = v1.SafetyCheckVisitorParams + +// ComprehensionIndex specifies how the comprehension term can be indexed. The keys +// tell the evaluator what variables to use for indexing. In the future, the index +// could be expanded with more information that would allow the evaluator to index +// a larger fragment of comprehensions (e.g., by closing over variables in the outer +// query.) +type ComprehensionIndex = v1.ComprehensionIndex + +// ModuleTreeNode represents a node in the module tree. The module +// tree is keyed by the package path. +type ModuleTreeNode = v1.ModuleTreeNode + +// TreeNode represents a node in the rule tree. The rule tree is keyed by +// rule path. +type TreeNode = v1.TreeNode + +// NewRuleTree returns a new TreeNode that represents the root +// of the rule tree populated with the given rules. +func NewRuleTree(mtree *ModuleTreeNode) *TreeNode { + return v1.NewRuleTree(mtree) +} + +// Graph represents the graph of dependencies between rules. +type Graph = v1.Graph + +// NewGraph returns a new Graph based on modules. The list function must return +// the rules referred to directly by the ref. +func NewGraph(modules map[string]*Module, list func(Ref) []*Rule) *Graph { + return v1.NewGraph(modules, list) +} + +// GraphTraversal is a Traversal that understands the dependency graph +type GraphTraversal = v1.GraphTraversal + +// NewGraphTraversal returns a Traversal for the dependency graph +func NewGraphTraversal(graph *Graph) *GraphTraversal { + return v1.NewGraphTraversal(graph) +} + +// OutputVarsFromBody returns all variables which are the "output" for +// the given body. For safety checks this means that they would be +// made safe by the body. +func OutputVarsFromBody(c *Compiler, body Body, safe VarSet) VarSet { + return v1.OutputVarsFromBody(c, body, safe) +} + +// OutputVarsFromExpr returns all variables which are the "output" for +// the given expression. For safety checks this means that they would be +// made safe by the expr. +func OutputVarsFromExpr(c *Compiler, expr *Expr, safe VarSet) VarSet { + return v1.OutputVarsFromExpr(c, expr, safe) +} + +// CompileModules takes a set of Rego modules represented as strings and +// compiles them for evaluation. The keys of the map are used as filenames. +func CompileModules(modules map[string]string) (*Compiler, error) { + return CompileModulesWithOpt(modules, CompileOpts{ + ParserOptions: ParserOptions{ + RegoVersion: DefaultRegoVersion, + }, + }) +} + +// CompileOpts defines a set of options for the compiler. +type CompileOpts = v1.CompileOpts + +// CompileModulesWithOpt takes a set of Rego modules represented as strings and +// compiles them for evaluation. The keys of the map are used as filenames. +func CompileModulesWithOpt(modules map[string]string, opts CompileOpts) (*Compiler, error) { + if opts.ParserOptions.RegoVersion == RegoUndefined { + opts.ParserOptions.RegoVersion = DefaultRegoVersion + } + + return v1.CompileModulesWithOpt(modules, opts) +} + +// MustCompileModules compiles a set of Rego modules represented as strings. If +// the compilation process fails, this function panics. +func MustCompileModules(modules map[string]string) *Compiler { + return MustCompileModulesWithOpts(modules, CompileOpts{}) +} + +// MustCompileModulesWithOpts compiles a set of Rego modules represented as strings. If +// the compilation process fails, this function panics. +func MustCompileModulesWithOpts(modules map[string]string, opts CompileOpts) *Compiler { + + compiler, err := CompileModulesWithOpt(modules, opts) + if err != nil { + panic(err) + } + + return compiler +} + +// CheckPathConflicts returns a set of errors indicating paths that +// are in conflict with the result of the provided callable. +func CheckPathConflicts(c *Compiler, exists func([]string) (bool, error)) Errors { + return v1.CheckPathConflicts(c, exists) +} + +// TypeEnv contains type info for static analysis such as type checking. +type TypeEnv = v1.TypeEnv + +// Errors represents a series of errors encountered during parsing, compiling, +// etc. +type Errors = v1.Errors + +const ( + // ParseErr indicates an unclassified parse error occurred. + ParseErr = v1.ParseErr + + // CompileErr indicates an unclassified compile error occurred. + CompileErr = v1.CompileErr + + // TypeErr indicates a type error was caught. + TypeErr = v1.TypeErr + + // UnsafeVarErr indicates an unsafe variable was found during compilation. + UnsafeVarErr = v1.UnsafeVarErr + + // RecursionErr indicates recursion was found during compilation. + RecursionErr = v1.RecursionErr +) + +// IsError returns true if err is an AST error with code. +func IsError(code string, err error) bool { + return v1.IsError(code, err) +} + +// ErrorDetails defines the interface for detailed error messages. +type ErrorDetails = v1.ErrorDetails + +// Error represents a single error caught during parsing, compiling, etc. +type Error = v1.Error + +// NewError returns a new Error object. +func NewError(code string, loc *Location, f string, a ...any) *Error { + return v1.NewError(code, loc, f, a...) +} + +// RuleIndex defines the interface for rule indices. +type RuleIndex v1.RuleIndex + +// IndexResult contains the result of an index lookup. +type IndexResult = v1.IndexResult + +// NewIndexResult returns a new IndexResult object. +func NewIndexResult(kind RuleKind) *IndexResult { + return v1.NewIndexResult(kind) +} + +func InternedBooleanTerm(b bool) *Term { + return v1.InternedTerm(b) +} + +// InternedIntNumberTerm returns a term with the given integer value. The term is +// cached between -1 to 512, and for values outside of that range, this function +// is equivalent to ast.IntNumberTerm. +func InternedIntNumberTerm(i int) *Term { + return v1.InternedTerm(i) +} + +func HasInternedIntNumberTerm(i int) bool { + return v1.HasInternedIntNumberTerm(i) +} + +// ValueMap represents a key/value map between AST term values. Any type of term +// can be used as a key in the map. +type ValueMap = v1.ValueMap + +// NewValueMap returns a new ValueMap. +func NewValueMap() *ValueMap { + return v1.NewValueMap() +} + +var RegoV1CompatibleRef = v1.RegoV1CompatibleRef + +// RegoVersion defines the Rego syntax requirements for a module. +type RegoVersion = v1.RegoVersion + +const DefaultRegoVersion = RegoV0 + +const ( + RegoUndefined = v1.RegoUndefined + // RegoV0 is the default, original Rego syntax. + RegoV0 = v1.RegoV0 + // RegoV0CompatV1 requires modules to comply with both the RegoV0 and RegoV1 syntax (as when 'rego.v1' is imported in a module). + // Shortly, RegoV1 compatibility is required, but 'rego.v1' or 'future.keywords' must also be imported. + RegoV0CompatV1 = v1.RegoV0CompatV1 + // RegoV1 is the Rego syntax enforced by OPA 1.0; e.g.: + // future.keywords part of default keyword set, and don't require imports; + // 'if' and 'contains' required in rule heads; + // (some) strict checks on by default. + RegoV1 = v1.RegoV1 +) + +func RegoVersionFromInt(i int) RegoVersion { + return v1.RegoVersionFromInt(i) +} + +// Parser is used to parse Rego statements. +type Parser = v1.Parser + +// ParserOptions defines the options for parsing Rego statements. +type ParserOptions = v1.ParserOptions + +// NewParser creates and initializes a Parser. +func NewParser() *Parser { + return v1.NewParser().WithRegoVersion(DefaultRegoVersion) +} + +func IsFutureKeyword(s string) bool { + return v1.IsFutureKeywordForRegoVersion(s, RegoV0) +} + +// MustParseBody returns a parsed body. +// If an error occurs during parsing, panic. +func MustParseBody(input string) Body { + return MustParseBodyWithOpts(input, ParserOptions{}) +} + +// MustParseBodyWithOpts returns a parsed body. +// If an error occurs during parsing, panic. +func MustParseBodyWithOpts(input string, opts ParserOptions) Body { + return v1.MustParseBodyWithOpts(input, setDefaultRegoVersion(opts)) +} + +// MustParseExpr returns a parsed expression. +// If an error occurs during parsing, panic. +func MustParseExpr(input string) *Expr { + parsed, err := ParseExpr(input) + if err != nil { + panic(err) + } + return parsed +} + +// MustParseImports returns a slice of imports. +// If an error occurs during parsing, panic. +func MustParseImports(input string) []*Import { + parsed, err := ParseImports(input) + if err != nil { + panic(err) + } + return parsed +} + +// MustParseModule returns a parsed module. +// If an error occurs during parsing, panic. +func MustParseModule(input string) *Module { + return MustParseModuleWithOpts(input, ParserOptions{}) +} + +// MustParseModuleWithOpts returns a parsed module. +// If an error occurs during parsing, panic. +func MustParseModuleWithOpts(input string, opts ParserOptions) *Module { + return v1.MustParseModuleWithOpts(input, setDefaultRegoVersion(opts)) +} + +// MustParsePackage returns a Package. +// If an error occurs during parsing, panic. +func MustParsePackage(input string) *Package { + parsed, err := ParsePackage(input) + if err != nil { + panic(err) + } + return parsed +} + +// MustParseStatements returns a slice of parsed statements. +// If an error occurs during parsing, panic. +func MustParseStatements(input string) []Statement { + parsed, _, err := ParseStatements("", input) + if err != nil { + panic(err) + } + return parsed +} + +// MustParseStatement returns exactly one statement. +// If an error occurs during parsing, panic. +func MustParseStatement(input string) Statement { + parsed, err := ParseStatement(input) + if err != nil { + panic(err) + } + return parsed +} + +func MustParseStatementWithOpts(input string, popts ParserOptions) Statement { + return v1.MustParseStatementWithOpts(input, setDefaultRegoVersion(popts)) +} + +// MustParseRef returns a parsed reference. +// If an error occurs during parsing, panic. +func MustParseRef(input string) Ref { + parsed, err := ParseRef(input) + if err != nil { + panic(err) + } + return parsed +} + +// MustParseRule returns a parsed rule. +// If an error occurs during parsing, panic. +func MustParseRule(input string) *Rule { + parsed, err := ParseRule(input) + if err != nil { + panic(err) + } + return parsed +} + +// MustParseRuleWithOpts returns a parsed rule. +// If an error occurs during parsing, panic. +func MustParseRuleWithOpts(input string, opts ParserOptions) *Rule { + return v1.MustParseRuleWithOpts(input, setDefaultRegoVersion(opts)) +} + +// MustParseTerm returns a parsed term. +// If an error occurs during parsing, panic. +func MustParseTerm(input string) *Term { + parsed, err := ParseTerm(input) + if err != nil { + panic(err) + } + return parsed +} + +// ParseRuleFromBody returns a rule if the body can be interpreted as a rule +// definition. Otherwise, an error is returned. +func ParseRuleFromBody(module *Module, body Body) (*Rule, error) { + return v1.ParseRuleFromBody(module, body) +} + +// ParseRuleFromExpr returns a rule if the expression can be interpreted as a +// rule definition. +func ParseRuleFromExpr(module *Module, expr *Expr) (*Rule, error) { + return v1.ParseRuleFromExpr(module, expr) +} + +// ParseCompleteDocRuleFromAssignmentExpr returns a rule if the expression can +// be interpreted as a complete document definition declared with the assignment +// operator. +func ParseCompleteDocRuleFromAssignmentExpr(module *Module, lhs, rhs *Term) (*Rule, error) { + return v1.ParseCompleteDocRuleFromAssignmentExpr(module, lhs, rhs) +} + +// ParseCompleteDocRuleFromEqExpr returns a rule if the expression can be +// interpreted as a complete document definition. +func ParseCompleteDocRuleFromEqExpr(module *Module, lhs, rhs *Term) (*Rule, error) { + return v1.ParseCompleteDocRuleFromEqExpr(module, lhs, rhs) +} + +func ParseCompleteDocRuleWithDotsFromTerm(module *Module, term *Term) (*Rule, error) { + return v1.ParseCompleteDocRuleWithDotsFromTerm(module, term) +} + +// ParsePartialObjectDocRuleFromEqExpr returns a rule if the expression can be +// interpreted as a partial object document definition. +func ParsePartialObjectDocRuleFromEqExpr(module *Module, lhs, rhs *Term) (*Rule, error) { + return v1.ParsePartialObjectDocRuleFromEqExpr(module, lhs, rhs) +} + +// ParsePartialSetDocRuleFromTerm returns a rule if the term can be interpreted +// as a partial set document definition. +func ParsePartialSetDocRuleFromTerm(module *Module, term *Term) (*Rule, error) { + return v1.ParsePartialSetDocRuleFromTerm(module, term) +} + +// ParseRuleFromCallEqExpr returns a rule if the term can be interpreted as a +// function definition (e.g., f(x) = y => f(x) = y { true }). +func ParseRuleFromCallEqExpr(module *Module, lhs, rhs *Term) (*Rule, error) { + return v1.ParseRuleFromCallEqExpr(module, lhs, rhs) +} + +// ParseRuleFromCallExpr returns a rule if the terms can be interpreted as a +// function returning true or some value (e.g., f(x) => f(x) = true { true }). +func ParseRuleFromCallExpr(module *Module, terms []*Term) (*Rule, error) { + return v1.ParseRuleFromCallExpr(module, terms) +} + +// ParseImports returns a slice of Import objects. +func ParseImports(input string) ([]*Import, error) { + return v1.ParseImports(input) +} + +// ParseModule returns a parsed Module object. +// For details on Module objects and their fields, see policy.go. +// Empty input will return nil, nil. +func ParseModule(filename, input string) (*Module, error) { + return ParseModuleWithOpts(filename, input, ParserOptions{}) +} + +// ParseModuleWithOpts returns a parsed Module object, and has an additional input ParserOptions +// For details on Module objects and their fields, see policy.go. +// Empty input will return nil, nil. +func ParseModuleWithOpts(filename, input string, popts ParserOptions) (*Module, error) { + return v1.ParseModuleWithOpts(filename, input, setDefaultRegoVersion(popts)) +} + +// ParseBody returns exactly one body. +// If multiple bodies are parsed, an error is returned. +func ParseBody(input string) (Body, error) { + return ParseBodyWithOpts(input, ParserOptions{SkipRules: true}) +} + +// ParseBodyWithOpts returns exactly one body. It does _not_ set SkipRules: true on its own, +// but respects whatever ParserOptions it's been given. +func ParseBodyWithOpts(input string, popts ParserOptions) (Body, error) { + return v1.ParseBodyWithOpts(input, setDefaultRegoVersion(popts)) +} + +// ParseExpr returns exactly one expression. +// If multiple expressions are parsed, an error is returned. +func ParseExpr(input string) (*Expr, error) { + body, err := ParseBody(input) + if err != nil { + return nil, fmt.Errorf("failed to parse expression: %w", err) + } + if len(body) != 1 { + return nil, fmt.Errorf("expected exactly one expression but got: %v", body) + } + return body[0], nil +} + +// ParsePackage returns exactly one Package. +// If multiple statements are parsed, an error is returned. +func ParsePackage(input string) (*Package, error) { + return v1.ParsePackage(input) +} + +// ParseTerm returns exactly one term. +// If multiple terms are parsed, an error is returned. +func ParseTerm(input string) (*Term, error) { + body, err := ParseBody(input) + if err != nil { + return nil, fmt.Errorf("failed to parse term: %w", err) + } + if len(body) != 1 { + return nil, fmt.Errorf("expected exactly one term but got: %v", body) + } + term, ok := body[0].Terms.(*Term) + if !ok { + return nil, fmt.Errorf("expected term but got %v", body[0].Terms) + } + return term, nil +} + +// ParseRef returns exactly one reference. +func ParseRef(input string) (Ref, error) { + term, err := ParseTerm(input) + if err != nil { + return nil, fmt.Errorf("failed to parse ref: %w", err) + } + ref, ok := term.Value.(Ref) + if !ok { + return nil, fmt.Errorf("expected ref but got %v", term) + } + return ref, nil +} + +// ParseRuleWithOpts returns exactly one rule. +// If multiple rules are parsed, an error is returned. +func ParseRuleWithOpts(input string, opts ParserOptions) (*Rule, error) { + return v1.ParseRuleWithOpts(input, setDefaultRegoVersion(opts)) +} + +// ParseRule returns exactly one rule. +// If multiple rules are parsed, an error is returned. +func ParseRule(input string) (*Rule, error) { + return ParseRuleWithOpts(input, ParserOptions{}) +} + +// ParseStatement returns exactly one statement. +// A statement might be a term, expression, rule, etc. Regardless, +// this function expects *exactly* one statement. If multiple +// statements are parsed, an error is returned. +func ParseStatement(input string) (Statement, error) { + stmts, _, err := ParseStatements("", input) + if err != nil { + return nil, err + } + if len(stmts) != 1 { + return nil, errors.New("expected exactly one statement") + } + return stmts[0], nil +} + +func ParseStatementWithOpts(input string, popts ParserOptions) (Statement, error) { + return v1.ParseStatementWithOpts(input, setDefaultRegoVersion(popts)) +} + +// ParseStatements is deprecated. Use ParseStatementWithOpts instead. +func ParseStatements(filename, input string) ([]Statement, []*Comment, error) { + return ParseStatementsWithOpts(filename, input, ParserOptions{}) +} + +// ParseStatementsWithOpts returns a slice of parsed statements. This is the +// default return value from the parser. +func ParseStatementsWithOpts(filename, input string, popts ParserOptions) ([]Statement, []*Comment, error) { + return v1.ParseStatementsWithOpts(filename, input, setDefaultRegoVersion(popts)) +} + +// ParserErrorDetail holds additional details for parser errors. +type ParserErrorDetail = v1.ParserErrorDetail + +func setDefaultRegoVersion(opts ParserOptions) ParserOptions { + if opts.RegoVersion == RegoUndefined { + opts.RegoVersion = DefaultRegoVersion + } + return opts +} + +// DefaultRootDocument is the default root document. +// +// All package directives inside source files are implicitly prefixed with the +// DefaultRootDocument value. +var DefaultRootDocument = v1.DefaultRootDocument + +// InputRootDocument names the document containing query arguments. +var InputRootDocument = v1.InputRootDocument + +// SchemaRootDocument names the document containing external data schemas. +var SchemaRootDocument = v1.SchemaRootDocument + +// FunctionArgRootDocument names the document containing function arguments. +// It's only for internal usage, for referencing function arguments between +// the index and topdown. +var FunctionArgRootDocument = v1.FunctionArgRootDocument + +// FutureRootDocument names the document containing new, to-become-default, +// features. +var FutureRootDocument = v1.FutureRootDocument + +// RegoRootDocument names the document containing new, to-become-default, +// features in a future versioned release. +var RegoRootDocument = v1.RegoRootDocument + +// RootDocumentNames contains the names of top-level documents that can be +// referred to in modules and queries. +// +// Note, the schema document is not currently implemented in the evaluator so it +// is not registered as a root document name (yet). +var RootDocumentNames = v1.RootDocumentNames + +// DefaultRootRef is a reference to the root of the default document. +// +// All refs to data in the policy engine's storage layer are prefixed with this ref. +var DefaultRootRef = v1.DefaultRootRef + +// InputRootRef is a reference to the root of the input document. +// +// All refs to query arguments are prefixed with this ref. +var InputRootRef = v1.InputRootRef + +// SchemaRootRef is a reference to the root of the schema document. +// +// All refs to schema documents are prefixed with this ref. Note, the schema +// document is not currently implemented in the evaluator so it is not +// registered as a root document ref (yet). +var SchemaRootRef = v1.SchemaRootRef + +// RootDocumentRefs contains the prefixes of top-level documents that all +// non-local references start with. +var RootDocumentRefs = v1.RootDocumentRefs + +// SystemDocumentKey is the name of the top-level key that identifies the system +// document. +const SystemDocumentKey = v1.SystemDocumentKey + +// ReservedVars is the set of names that refer to implicitly ground vars. +var ReservedVars = v1.ReservedVars + +// Wildcard represents the wildcard variable as defined in the language. +var Wildcard = v1.Wildcard + +// WildcardPrefix is the special character that all wildcard variables are +// prefixed with when the statement they are contained in is parsed. +const WildcardPrefix = v1.WildcardPrefix + +// Keywords contains strings that map to language keywords. +var Keywords = v1.Keywords + +var KeywordsV0 = v1.KeywordsV0 + +var KeywordsV1 = v1.KeywordsV1 + +func KeywordsForRegoVersion(v RegoVersion) []string { + return v1.KeywordsForRegoVersion(v) +} + +// IsKeyword returns true if s is a language keyword. +func IsKeyword(s string) bool { + return v1.IsKeyword(s) +} + +func IsInKeywords(s string, keywords []string) bool { + return v1.IsInKeywords(s, keywords) +} + +// IsKeywordInRegoVersion returns true if s is a language keyword. +func IsKeywordInRegoVersion(s string, regoVersion RegoVersion) bool { + return v1.IsKeywordInRegoVersion(s, regoVersion) +} + +type ( + // Node represents a node in an AST. Nodes may be statements in a policy module + // or elements of an ad-hoc query, expression, etc. + Node = v1.Node + + // Statement represents a single statement in a policy module. + Statement = v1.Statement +) + +type ( + + // Module represents a collection of policies (defined by rules) + // within a namespace (defined by the package) and optional + // dependencies on external documents (defined by imports). + Module = v1.Module + + // Comment contains the raw text from the comment in the definition. + Comment = v1.Comment + + // Package represents the namespace of the documents produced + // by rules inside the module. + Package = v1.Package + + // Import represents a dependency on a document outside of the policy + // namespace. Imports are optional. + Import = v1.Import + + // Rule represents a rule as defined in the language. Rules define the + // content of documents that represent policy decisions. + Rule = v1.Rule + + // Head represents the head of a rule. + Head = v1.Head + + // Args represents zero or more arguments to a rule. + Args = v1.Args + + // Body represents one or more expressions contained inside a rule or user + // function. + Body = v1.Body + + // Expr represents a single expression contained inside the body of a rule. + Expr = v1.Expr + + // SomeDecl represents a variable declaration statement. The symbols are variables. + SomeDecl = v1.SomeDecl + + Every = v1.Every + + // With represents a modifier on an expression. + With = v1.With +) + +// NewComment returns a new Comment object. +func NewComment(text []byte) *Comment { + return v1.NewComment(text) +} + +// IsValidImportPath returns an error indicating if the import path is invalid. +// If the import path is valid, err is nil. +func IsValidImportPath(v Value) (err error) { + return v1.IsValidImportPath(v) +} + +// NewHead returns a new Head object. If args are provided, the first will be +// used for the key and the second will be used for the value. +func NewHead(name Var, args ...*Term) *Head { + return v1.NewHead(name, args...) +} + +// VarHead creates a head object, initializes its Name, Location, and Options, +// and returns the new head. +func VarHead(name Var, location *Location, jsonOpts *astJSON.Options) *Head { + return v1.VarHead(name, location, jsonOpts) +} + +// RefHead returns a new Head object with the passed Ref. If args are provided, +// the first will be used for the value. +func RefHead(ref Ref, args ...*Term) *Head { + return v1.RefHead(ref, args...) +} + +// DocKind represents the collection of document types that can be produced by rules. +type DocKind = v1.DocKind + +const ( + // CompleteDoc represents a document that is completely defined by the rule. + CompleteDoc = v1.CompleteDoc + + // PartialSetDoc represents a set document that is partially defined by the rule. + PartialSetDoc = v1.PartialSetDoc + + // PartialObjectDoc represents an object document that is partially defined by the rule. + PartialObjectDoc = v1.PartialObjectDoc +) + +type RuleKind = v1.RuleKind + +const ( + SingleValue = v1.SingleValue + MultiValue = v1.MultiValue +) + +// NewBody returns a new Body containing the given expressions. The indices of +// the immediate expressions will be reset. +func NewBody(exprs ...*Expr) Body { + return v1.NewBody(exprs...) +} + +// NewExpr returns a new Expr object. +func NewExpr(terms any) *Expr { + return v1.NewExpr(terms) +} + +// NewBuiltinExpr creates a new Expr object with the supplied terms. +// The builtin operator must be the first term. +func NewBuiltinExpr(terms ...*Term) *Expr { + return v1.NewBuiltinExpr(terms...) +} + +// Copy returns a deep copy of the AST node x. If x is not an AST node, x is returned unmodified. +func Copy(x any) any { + return v1.Copy(x) +} + +// RuleSet represents a collection of rules that produce a virtual document. +type RuleSet = v1.RuleSet + +// NewRuleSet returns a new RuleSet containing the given rules. +func NewRuleSet(rules ...*Rule) RuleSet { + return v1.NewRuleSet(rules...) +} + +// Pretty writes a pretty representation of the AST rooted at x to w. +// +// This is function is intended for debug purposes when inspecting ASTs. +func Pretty(w io.Writer, x any) { + v1.Pretty(w, x) +} + +// SchemaSet holds a map from a path to a schema. +type SchemaSet = v1.SchemaSet + +// NewSchemaSet returns an empty SchemaSet. +func NewSchemaSet() *SchemaSet { + return v1.NewSchemaSet() +} + +// TypeName returns a human readable name for the AST element type. +func TypeName(x any) string { + return v1.TypeName(x) +} + +// Location records a position in source code. +type Location = v1.Location + +// NewLocation returns a new Location object. +func NewLocation(text []byte, file string, row int, col int) *Location { + return v1.NewLocation(text, file, row, col) +} + +// Value declares the common interface for all Term values. Every kind of Term value +// in the language is represented as a type that implements this interface: +// +// - Null, Boolean, Number, String +// - Object, Array, Set +// - Variables, References +// - Array, Set, and Object Comprehensions +// - Calls +type Value = v1.Value + +// InterfaceToValue converts a native Go value x to a Value. +func InterfaceToValue(x any) (Value, error) { + return v1.InterfaceToValue(x) +} + +// ValueFromReader returns an AST value from a JSON serialized value in the reader. +func ValueFromReader(r io.Reader) (Value, error) { + return v1.ValueFromReader(r) +} + +// As converts v into a Go native type referred to by x. +func As(v Value, x any) error { + return v1.As(v, x) +} + +// Resolver defines the interface for resolving references to native Go values. +type Resolver = v1.Resolver + +// ValueResolver defines the interface for resolving references to AST values. +type ValueResolver = v1.ValueResolver + +// UnknownValueErr indicates a ValueResolver was unable to resolve a reference +// because the reference refers to an unknown value. +type UnknownValueErr = v1.UnknownValueErr + +// IsUnknownValueErr returns true if the err is an UnknownValueErr. +func IsUnknownValueErr(err error) bool { + return v1.IsUnknownValueErr(err) +} + +// ValueToInterface returns the Go representation of an AST value. The AST +// value should not contain any values that require evaluation (e.g., vars, +// comprehensions, etc.) +func ValueToInterface(v Value, resolver Resolver) (any, error) { + return v1.ValueToInterface(v, resolver) +} + +// JSON returns the JSON representation of v. The value must not contain any +// refs or terms that require evaluation (e.g., vars, comprehensions, etc.) +func JSON(v Value) (any, error) { + return v1.JSON(v) +} + +// JSONOpt defines parameters for AST to JSON conversion. +type JSONOpt = v1.JSONOpt + +// JSONWithOpt returns the JSON representation of v. The value must not contain any +// refs or terms that require evaluation (e.g., vars, comprehensions, etc.) +func JSONWithOpt(v Value, opt JSONOpt) (any, error) { + return v1.JSONWithOpt(v, opt) +} + +// MustJSON returns the JSON representation of v. The value must not contain any +// refs or terms that require evaluation (e.g., vars, comprehensions, etc.) If +// the conversion fails, this function will panic. This function is mostly for +// test purposes. +func MustJSON(v Value) any { + return v1.MustJSON(v) +} + +// MustInterfaceToValue converts a native Go value x to a Value. If the +// conversion fails, this function will panic. This function is mostly for test +// purposes. +func MustInterfaceToValue(x any) Value { + return v1.MustInterfaceToValue(x) +} + +// Term is an argument to a function. +type Term = v1.Term + +// NewTerm returns a new Term object. +func NewTerm(v Value) *Term { + return v1.NewTerm(v) +} + +// IsConstant returns true if the AST value is constant. +func IsConstant(v Value) bool { + return v1.IsConstant(v) +} + +// IsComprehension returns true if the supplied value is a comprehension. +func IsComprehension(x Value) bool { + return v1.IsComprehension(x) +} + +// ContainsRefs returns true if the Value v contains refs. +func ContainsRefs(v any) bool { + return v1.ContainsRefs(v) +} + +// ContainsComprehensions returns true if the Value v contains comprehensions. +func ContainsComprehensions(v any) bool { + return v1.ContainsComprehensions(v) +} + +// ContainsClosures returns true if the Value v contains closures. +func ContainsClosures(v any) bool { + return v1.ContainsClosures(v) +} + +// IsScalar returns true if the AST value is a scalar. +func IsScalar(v Value) bool { + return v1.IsScalar(v) +} + +// Null represents the null value defined by JSON. +type Null = v1.Null + +// NullTerm creates a new Term with a Null value. +func NullTerm() *Term { + return v1.NullTerm() +} + +// Boolean represents a boolean value defined by JSON. +type Boolean = v1.Boolean + +// BooleanTerm creates a new Term with a Boolean value. +func BooleanTerm(b bool) *Term { + return v1.BooleanTerm(b) +} + +// Number represents a numeric value as defined by JSON. +type Number = v1.Number + +// NumberTerm creates a new Term with a Number value. +func NumberTerm(n json.Number) *Term { + return v1.NumberTerm(n) +} + +// IntNumberTerm creates a new Term with an integer Number value. +func IntNumberTerm(i int) *Term { + return v1.IntNumberTerm(i) +} + +// UIntNumberTerm creates a new Term with an unsigned integer Number value. +func UIntNumberTerm(u uint64) *Term { + return v1.UIntNumberTerm(u) +} + +// FloatNumberTerm creates a new Term with a floating point Number value. +func FloatNumberTerm(f float64) *Term { + return v1.FloatNumberTerm(f) +} + +// String represents a string value as defined by JSON. +type String = v1.String + +// StringTerm creates a new Term with a String value. +func StringTerm(s string) *Term { + return v1.StringTerm(s) +} + +// Var represents a variable as defined by the language. +type Var = v1.Var + +// VarTerm creates a new Term with a Variable value. +func VarTerm(v string) *Term { + return v1.VarTerm(v) +} + +// Ref represents a reference as defined by the language. +type Ref = v1.Ref + +// EmptyRef returns a new, empty reference. +func EmptyRef() Ref { + return v1.EmptyRef() +} + +// PtrRef returns a new reference against the head for the pointer +// s. Path components in the pointer are unescaped. +func PtrRef(head *Term, s string) (Ref, error) { + return v1.PtrRef(head, s) +} + +// RefTerm creates a new Term with a Ref value. +func RefTerm(r ...*Term) *Term { + return v1.RefTerm(r...) +} + +func IsVarCompatibleString(s string) bool { + return v1.IsVarCompatibleString(s) +} + +// QueryIterator defines the interface for querying AST documents with references. +type QueryIterator = v1.QueryIterator + +// ArrayTerm creates a new Term with an Array value. +func ArrayTerm(a ...*Term) *Term { + return v1.ArrayTerm(a...) +} + +// NewArray creates an Array with the terms provided. The array will +// use the provided term slice. +func NewArray(a ...*Term) *Array { + return v1.NewArray(a...) +} + +// Array represents an array as defined by the language. Arrays are similar to the +// same types as defined by JSON with the exception that they can contain Vars +// and References. +type Array = v1.Array + +// Set represents a set as defined by the language. +type Set = v1.Set + +// NewSet returns a new Set containing t. +func NewSet(t ...*Term) Set { + return v1.NewSet(t...) +} + +func SetTerm(t ...*Term) *Term { + return v1.SetTerm(t...) +} + +// Object represents an object as defined by the language. +type Object = v1.Object + +// NewObject creates a new Object with t. +func NewObject(t ...[2]*Term) Object { + return v1.NewObject(t...) +} + +// ObjectTerm creates a new Term with an Object value. +func ObjectTerm(o ...[2]*Term) *Term { + return v1.ObjectTerm(o...) +} + +func LazyObject(blob map[string]any) Object { + return v1.LazyObject(blob) +} + +// Item is a helper for constructing an tuple containing two Terms +// representing a key/value pair in an Object. +func Item(key, value *Term) [2]*Term { + return v1.Item(key, value) +} + +// NOTE(philipc): The only way to get an ObjectKeyIterator should be +// from an Object. This ensures that the iterator can have implementation- +// specific details internally, with no contracts except to the very +// limited interface. +type ObjectKeysIterator = v1.ObjectKeysIterator + +// ArrayComprehension represents an array comprehension as defined in the language. +type ArrayComprehension = v1.ArrayComprehension + +// ArrayComprehensionTerm creates a new Term with an ArrayComprehension value. +func ArrayComprehensionTerm(term *Term, body Body) *Term { + return v1.ArrayComprehensionTerm(term, body) +} + +// ObjectComprehension represents an object comprehension as defined in the language. +type ObjectComprehension = v1.ObjectComprehension + +// ObjectComprehensionTerm creates a new Term with an ObjectComprehension value. +func ObjectComprehensionTerm(key, value *Term, body Body) *Term { + return v1.ObjectComprehensionTerm(key, value, body) +} + +// SetComprehension represents a set comprehension as defined in the language. +type SetComprehension = v1.SetComprehension + +// SetComprehensionTerm creates a new Term with an SetComprehension value. +func SetComprehensionTerm(term *Term, body Body) *Term { + return v1.SetComprehensionTerm(term, body) +} + +// Call represents as function call in the language. +type Call = v1.Call + +// CallTerm returns a new Term with a Call value defined by terms. The first +// term is the operator and the rest are operands. +func CallTerm(terms ...*Term) *Term { + return v1.CallTerm(terms...) +} + +// Transformer defines the interface for transforming AST elements. If the +// transformer returns nil and does not indicate an error, the AST element will +// be set to nil and no transformations will be applied to children of the +// element. +type Transformer = v1.Transformer + +// Transform iterates the AST and calls the Transform function on the +// Transformer t for x before recursing. +func Transform(t Transformer, x any) (any, error) { + return v1.Transform(t, x) +} + +// TransformRefs calls the function f on all references under x. +func TransformRefs(x any, f func(Ref) (Value, error)) (any, error) { + return v1.TransformRefs(x, f) +} + +// TransformVars calls the function f on all vars under x. +func TransformVars(x any, f func(Var) (Value, error)) (any, error) { + return v1.TransformVars(x, f) +} + +// TransformComprehensions calls the functio nf on all comprehensions under x. +func TransformComprehensions(x any, f func(any) (Value, error)) (any, error) { + return v1.TransformComprehensions(x, f) +} + +// GenericTransformer implements the Transformer interface to provide a utility +// to transform AST nodes using a closure. +type GenericTransformer = v1.GenericTransformer + +// NewGenericTransformer returns a new GenericTransformer that will transform +// AST nodes using the function f. +func NewGenericTransformer(f func(x any) (any, error)) *GenericTransformer { + return v1.NewGenericTransformer(f) +} + +// Unify returns a set of variables that will be unified when the equality expression defined by +// terms a and b is evaluated. The unifier assumes that variables in the VarSet safe are already +// unified. +func Unify(safe VarSet, a *Term, b *Term) VarSet { + return v1.Unify(safe, a, b) +} + +// VarSet represents a set of variables. +type VarSet = v1.VarSet + +// NewVarSet returns a new VarSet containing the specified variables. +func NewVarSet(vs ...Var) VarSet { + return v1.NewVarSet(vs...) +} + +// Visitor defines the interface for iterating AST elements. The Visit function +// can return a Visitor w which will be used to visit the children of the AST +// element v. If the Visit function returns nil, the children will not be +// visited. +// +// Deprecated: use GenericVisitor or another visitor implementation +type Visitor = v1.Visitor + +// BeforeAndAfterVisitor wraps Visitor to provide hooks for being called before +// and after the AST has been visited. +// +// Deprecated: use GenericVisitor or another visitor implementation +type BeforeAndAfterVisitor = v1.BeforeAndAfterVisitor + +// Walk iterates the AST by calling the Visit function on the Visitor +// v for x before recursing. +// +// Deprecated: use GenericVisitor.Walk +func Walk(v Visitor, x any) { + v1.Walk(v, x) +} + +// WalkBeforeAndAfter iterates the AST by calling the Visit function on the +// Visitor v for x before recursing. +// +// Deprecated: use GenericVisitor.Walk +func WalkBeforeAndAfter(v BeforeAndAfterVisitor, x any) { + v1.WalkBeforeAndAfter(v, x) +} + +// WalkVars calls the function f on all vars under x. If the function f +// returns true, AST nodes under the last node will not be visited. +func WalkVars(x any, f func(Var) bool) { + v1.WalkVars(x, f) +} + +// WalkClosures calls the function f on all closures under x. If the function f +// returns true, AST nodes under the last node will not be visited. +func WalkClosures(x any, f func(any) bool) { + v1.WalkClosures(x, f) +} + +// WalkRefs calls the function f on all references under x. If the function f +// returns true, AST nodes under the last node will not be visited. +func WalkRefs(x any, f func(Ref) bool) { + v1.WalkRefs(x, f) +} + +// WalkTerms calls the function f on all terms under x. If the function f +// returns true, AST nodes under the last node will not be visited. +func WalkTerms(x any, f func(*Term) bool) { + v1.WalkTerms(x, f) +} + +// WalkWiths calls the function f on all with modifiers under x. If the function f +// returns true, AST nodes under the last node will not be visited. +func WalkWiths(x any, f func(*With) bool) { + v1.WalkWiths(x, f) +} + +// WalkExprs calls the function f on all expressions under x. If the function f +// returns true, AST nodes under the last node will not be visited. +func WalkExprs(x any, f func(*Expr) bool) { + v1.WalkExprs(x, f) +} + +// WalkBodies calls the function f on all bodies under x. If the function f +// returns true, AST nodes under the last node will not be visited. +func WalkBodies(x any, f func(Body) bool) { + v1.WalkBodies(x, f) +} + +// WalkRules calls the function f on all rules under x. If the function f +// returns true, AST nodes under the last node will not be visited. +func WalkRules(x any, f func(*Rule) bool) { + v1.WalkRules(x, f) +} + +// WalkNodes calls the function f on all nodes under x. If the function f +// returns true, AST nodes under the last node will not be visited. +func WalkNodes(x any, f func(Node) bool) { + v1.WalkNodes(x, f) +} + +// GenericVisitor provides a utility to walk over AST nodes using a +// closure. If the closure returns true, the visitor will not walk +// over AST nodes under x. +type GenericVisitor = v1.GenericVisitor + +// NewGenericVisitor returns a new GenericVisitor that will invoke the function +// f on AST nodes. +func NewGenericVisitor(f func(x any) bool) *GenericVisitor { + return v1.NewGenericVisitor(f) +} + +// BeforeAfterVisitor provides a utility to walk over AST nodes using +// closures. If the before closure returns true, the visitor will not +// walk over AST nodes under x. The after closure is invoked always +// after visiting a node. +type BeforeAfterVisitor = v1.BeforeAfterVisitor + +// NewBeforeAfterVisitor returns a new BeforeAndAfterVisitor that +// will invoke the functions before and after AST nodes. +func NewBeforeAfterVisitor(before func(x any) bool, after func(x any)) *BeforeAfterVisitor { + return v1.NewBeforeAfterVisitor(before, after) +} + +// VarVisitor walks AST nodes under a given node and collects all encountered +// variables. The collected variables can be controlled by specifying +// VarVisitorParams when creating the visitor. +type VarVisitor = v1.VarVisitor + +// VarVisitorParams contains settings for a VarVisitor. +type VarVisitorParams = v1.VarVisitorParams + +// NewVarVisitor returns a new VarVisitor object. +func NewVarVisitor() *VarVisitor { + return v1.NewVarVisitor() +} diff --git a/vendor/github.com/open-policy-agent/opa/ast/builtins.go b/vendor/github.com/open-policy-agent/opa/ast/builtins.go deleted file mode 100644 index 65de5abef2..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/builtins.go +++ /dev/null @@ -1,634 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// Builtins is the registry of built-in functions supported by OPA. -// Call RegisterBuiltin to add a new built-in. -var Builtins = v1.Builtins - -// RegisterBuiltin adds a new built-in function to the registry. -func RegisterBuiltin(b *Builtin) { - v1.RegisterBuiltin(b) -} - -// DefaultBuiltins is the registry of built-in functions supported in OPA -// by default. When adding a new built-in function to OPA, update this -// list. -var DefaultBuiltins = v1.DefaultBuiltins - -// BuiltinMap provides a convenient mapping of built-in names to -// built-in definitions. -var BuiltinMap = v1.BuiltinMap - -// Deprecated: Builtins can now be directly annotated with the -// Nondeterministic property, and when set to true, will be ignored -// for partial evaluation. -var IgnoreDuringPartialEval = v1.IgnoreDuringPartialEval //nolint:staticcheck - -/** - * Unification - */ - -// Equality represents the "=" operator. -var Equality = v1.Equality - -/** - * Assignment - */ - -// Assign represents the assignment (":=") operator. -var Assign = v1.Assign - -// Member represents the `in` (infix) operator. -var Member = v1.Member - -// MemberWithKey represents the `in` (infix) operator when used -// with two terms on the lhs, i.e., `k, v in obj`. -var MemberWithKey = v1.MemberWithKey - -var GreaterThan = v1.GreaterThan - -var GreaterThanEq = v1.GreaterThanEq - -// LessThan represents the "<" comparison operator. -var LessThan = v1.LessThan - -var LessThanEq = v1.LessThanEq - -var NotEqual = v1.NotEqual - -// Equal represents the "==" comparison operator. -var Equal = v1.Equal - -var Plus = v1.Plus - -var Minus = v1.Minus - -var Multiply = v1.Multiply - -var Divide = v1.Divide - -var Round = v1.Round - -var Ceil = v1.Ceil - -var Floor = v1.Floor - -var Abs = v1.Abs - -var Rem = v1.Rem - -/** - * Bitwise - */ - -var BitsOr = v1.BitsOr - -var BitsAnd = v1.BitsAnd - -var BitsNegate = v1.BitsNegate - -var BitsXOr = v1.BitsXOr - -var BitsShiftLeft = v1.BitsShiftLeft - -var BitsShiftRight = v1.BitsShiftRight - -/** - * Sets - */ - -var And = v1.And - -// Or performs a union operation on sets. -var Or = v1.Or - -var Intersection = v1.Intersection - -var Union = v1.Union - -/** - * Aggregates - */ - -var Count = v1.Count - -var Sum = v1.Sum - -var Product = v1.Product - -var Max = v1.Max - -var Min = v1.Min - -/** - * Sorting - */ - -var Sort = v1.Sort - -/** - * Arrays - */ - -var ArrayConcat = v1.ArrayConcat - -var ArraySlice = v1.ArraySlice - -var ArrayReverse = v1.ArrayReverse - -/** - * Conversions - */ - -var ToNumber = v1.ToNumber - -/** - * Regular Expressions - */ - -var RegexMatch = v1.RegexMatch - -var RegexIsValid = v1.RegexIsValid - -var RegexFindAllStringSubmatch = v1.RegexFindAllStringSubmatch - -var RegexTemplateMatch = v1.RegexTemplateMatch - -var RegexSplit = v1.RegexSplit - -// RegexFind takes two strings and a number, the pattern, the value and number of match values to -// return, -1 means all match values. -var RegexFind = v1.RegexFind - -// GlobsMatch takes two strings regexp-style strings and evaluates to true if their -// intersection matches a non-empty set of non-empty strings. -// Examples: -// - "a.a." and ".b.b" -> true. -// - "[a-z]*" and [0-9]+" -> not true. -var GlobsMatch = v1.GlobsMatch - -/** - * Strings - */ - -var AnyPrefixMatch = v1.AnyPrefixMatch - -var AnySuffixMatch = v1.AnySuffixMatch - -var Concat = v1.Concat - -var FormatInt = v1.FormatInt - -var IndexOf = v1.IndexOf - -var IndexOfN = v1.IndexOfN - -var Substring = v1.Substring - -var Contains = v1.Contains - -var StringCount = v1.StringCount - -var StartsWith = v1.StartsWith - -var EndsWith = v1.EndsWith - -var Lower = v1.Lower - -var Upper = v1.Upper - -var Split = v1.Split - -var Replace = v1.Replace - -var ReplaceN = v1.ReplaceN - -var RegexReplace = v1.RegexReplace - -var Trim = v1.Trim - -var TrimLeft = v1.TrimLeft - -var TrimPrefix = v1.TrimPrefix - -var TrimRight = v1.TrimRight - -var TrimSuffix = v1.TrimSuffix - -var TrimSpace = v1.TrimSpace - -var Sprintf = v1.Sprintf - -var StringReverse = v1.StringReverse - -var RenderTemplate = v1.RenderTemplate - -/** - * Numbers - */ - -// RandIntn returns a random number 0 - n -// Marked non-deterministic because it relies on RNG internally. -var RandIntn = v1.RandIntn - -var NumbersRange = v1.NumbersRange - -var NumbersRangeStep = v1.NumbersRangeStep - -/** - * Units - */ - -var UnitsParse = v1.UnitsParse - -var UnitsParseBytes = v1.UnitsParseBytes - -// -/** - * Type - */ - -// UUIDRFC4122 returns a version 4 UUID string. -// Marked non-deterministic because it relies on RNG internally. -var UUIDRFC4122 = v1.UUIDRFC4122 - -var UUIDParse = v1.UUIDParse - -/** - * JSON - */ - -var JSONFilter = v1.JSONFilter - -var JSONRemove = v1.JSONRemove - -var JSONPatch = v1.JSONPatch - -var ObjectSubset = v1.ObjectSubset - -var ObjectUnion = v1.ObjectUnion - -var ObjectUnionN = v1.ObjectUnionN - -var ObjectRemove = v1.ObjectRemove - -var ObjectFilter = v1.ObjectFilter - -var ObjectGet = v1.ObjectGet - -var ObjectKeys = v1.ObjectKeys - -/* - * Encoding - */ - -var JSONMarshal = v1.JSONMarshal - -var JSONMarshalWithOptions = v1.JSONMarshalWithOptions - -var JSONUnmarshal = v1.JSONUnmarshal - -var JSONIsValid = v1.JSONIsValid - -var Base64Encode = v1.Base64Encode - -var Base64Decode = v1.Base64Decode - -var Base64IsValid = v1.Base64IsValid - -var Base64UrlEncode = v1.Base64UrlEncode - -var Base64UrlEncodeNoPad = v1.Base64UrlEncodeNoPad - -var Base64UrlDecode = v1.Base64UrlDecode - -var URLQueryDecode = v1.URLQueryDecode - -var URLQueryEncode = v1.URLQueryEncode - -var URLQueryEncodeObject = v1.URLQueryEncodeObject - -var URLQueryDecodeObject = v1.URLQueryDecodeObject - -var YAMLMarshal = v1.YAMLMarshal - -var YAMLUnmarshal = v1.YAMLUnmarshal - -// YAMLIsValid verifies the input string is a valid YAML document. -var YAMLIsValid = v1.YAMLIsValid - -var HexEncode = v1.HexEncode - -var HexDecode = v1.HexDecode - -/** - * Tokens - */ - -var JWTDecode = v1.JWTDecode - -var JWTVerifyRS256 = v1.JWTVerifyRS256 - -var JWTVerifyRS384 = v1.JWTVerifyRS384 - -var JWTVerifyRS512 = v1.JWTVerifyRS512 - -var JWTVerifyPS256 = v1.JWTVerifyPS256 - -var JWTVerifyPS384 = v1.JWTVerifyPS384 - -var JWTVerifyPS512 = v1.JWTVerifyPS512 - -var JWTVerifyES256 = v1.JWTVerifyES256 - -var JWTVerifyES384 = v1.JWTVerifyES384 - -var JWTVerifyES512 = v1.JWTVerifyES512 - -var JWTVerifyHS256 = v1.JWTVerifyHS256 - -var JWTVerifyHS384 = v1.JWTVerifyHS384 - -var JWTVerifyHS512 = v1.JWTVerifyHS512 - -// Marked non-deterministic because it relies on time internally. -var JWTDecodeVerify = v1.JWTDecodeVerify - -// Marked non-deterministic because it relies on RNG internally. -var JWTEncodeSignRaw = v1.JWTEncodeSignRaw - -// Marked non-deterministic because it relies on RNG internally. -var JWTEncodeSign = v1.JWTEncodeSign - -/** - * Time - */ - -// Marked non-deterministic because it relies on time directly. -var NowNanos = v1.NowNanos - -var ParseNanos = v1.ParseNanos - -var ParseRFC3339Nanos = v1.ParseRFC3339Nanos - -var ParseDurationNanos = v1.ParseDurationNanos - -var Format = v1.Format - -var Date = v1.Date - -var Clock = v1.Clock - -var Weekday = v1.Weekday - -var AddDate = v1.AddDate - -var Diff = v1.Diff - -/** - * Crypto. - */ - -var CryptoX509ParseCertificates = v1.CryptoX509ParseCertificates - -var CryptoX509ParseAndVerifyCertificates = v1.CryptoX509ParseAndVerifyCertificates - -var CryptoX509ParseAndVerifyCertificatesWithOptions = v1.CryptoX509ParseAndVerifyCertificatesWithOptions - -var CryptoX509ParseCertificateRequest = v1.CryptoX509ParseCertificateRequest - -var CryptoX509ParseKeyPair = v1.CryptoX509ParseKeyPair -var CryptoX509ParseRSAPrivateKey = v1.CryptoX509ParseRSAPrivateKey - -var CryptoParsePrivateKeys = v1.CryptoParsePrivateKeys - -var CryptoMd5 = v1.CryptoMd5 - -var CryptoSha1 = v1.CryptoSha1 - -var CryptoSha256 = v1.CryptoSha256 - -var CryptoHmacMd5 = v1.CryptoHmacMd5 - -var CryptoHmacSha1 = v1.CryptoHmacSha1 - -var CryptoHmacSha256 = v1.CryptoHmacSha256 - -var CryptoHmacSha512 = v1.CryptoHmacSha512 - -var CryptoHmacEqual = v1.CryptoHmacEqual - -/** - * Graphs. - */ - -var WalkBuiltin = v1.WalkBuiltin - -var ReachableBuiltin = v1.ReachableBuiltin - -var ReachablePathsBuiltin = v1.ReachablePathsBuiltin - -/** - * Type - */ - -var IsNumber = v1.IsNumber - -var IsString = v1.IsString - -var IsBoolean = v1.IsBoolean - -var IsArray = v1.IsArray - -var IsSet = v1.IsSet - -var IsObject = v1.IsObject - -var IsNull = v1.IsNull - -/** - * Type Name - */ - -// TypeNameBuiltin returns the type of the input. -var TypeNameBuiltin = v1.TypeNameBuiltin - -/** - * HTTP Request - */ - -// Marked non-deterministic because HTTP request results can be non-deterministic. -var HTTPSend = v1.HTTPSend - -/** - * GraphQL - */ - -// GraphQLParse returns a pair of AST objects from parsing/validation. -var GraphQLParse = v1.GraphQLParse - -// GraphQLParseAndVerify returns a boolean and a pair of AST object from parsing/validation. -var GraphQLParseAndVerify = v1.GraphQLParseAndVerify - -// GraphQLParseQuery parses the input GraphQL query and returns a JSON -// representation of its AST. -var GraphQLParseQuery = v1.GraphQLParseQuery - -// GraphQLParseSchema parses the input GraphQL schema and returns a JSON -// representation of its AST. -var GraphQLParseSchema = v1.GraphQLParseSchema - -// GraphQLIsValid returns true if a GraphQL query is valid with a given -// schema, and returns false for all other inputs. -var GraphQLIsValid = v1.GraphQLIsValid - -// GraphQLSchemaIsValid returns true if the input is valid GraphQL schema, -// and returns false for all other inputs. -var GraphQLSchemaIsValid = v1.GraphQLSchemaIsValid - -/** - * JSON Schema - */ - -// JSONSchemaVerify returns empty string if the input is valid JSON schema -// and returns error string for all other inputs. -var JSONSchemaVerify = v1.JSONSchemaVerify - -// JSONMatchSchema returns empty array if the document matches the JSON schema, -// and returns non-empty array with error objects otherwise. -var JSONMatchSchema = v1.JSONMatchSchema - -/** - * Cloud Provider Helper Functions - */ - -var ProvidersAWSSignReqObj = v1.ProvidersAWSSignReqObj - -/** - * Rego - */ - -var RegoParseModule = v1.RegoParseModule - -var RegoMetadataChain = v1.RegoMetadataChain - -// RegoMetadataRule returns the metadata for the active rule -var RegoMetadataRule = v1.RegoMetadataRule - -/** - * OPA - */ - -// Marked non-deterministic because of unpredictable config/environment-dependent results. -var OPARuntime = v1.OPARuntime - -/** - * Trace - */ - -var Trace = v1.Trace - -/** - * Glob - */ - -var GlobMatch = v1.GlobMatch - -var GlobQuoteMeta = v1.GlobQuoteMeta - -/** - * Networking - */ - -var NetCIDRIntersects = v1.NetCIDRIntersects - -var NetCIDRExpand = v1.NetCIDRExpand - -var NetCIDRContains = v1.NetCIDRContains - -var NetCIDRContainsMatches = v1.NetCIDRContainsMatches - -var NetCIDRMerge = v1.NetCIDRMerge - -var NetCIDRIsValid = v1.NetCIDRIsValid - -// Marked non-deterministic because DNS resolution results can be non-deterministic. -var NetLookupIPAddr = v1.NetLookupIPAddr - -/** - * Semantic Versions - */ - -var SemVerIsValid = v1.SemVerIsValid - -var SemVerCompare = v1.SemVerCompare - -/** - * Printing - */ - -// Print is a special built-in function that writes zero or more operands -// to a message buffer. The caller controls how the buffer is displayed. The -// operands may be of any type. Furthermore, unlike other built-in functions, -// undefined operands DO NOT cause the print() function to fail during -// evaluation. -var Print = v1.Print - -// InternalPrint represents the internal implementation of the print() function. -// The compiler rewrites print() calls to refer to the internal implementation. -var InternalPrint = v1.InternalPrint - -/** - * Deprecated built-ins. - */ - -// SetDiff has been replaced by the minus built-in. -var SetDiff = v1.SetDiff - -// NetCIDROverlap has been replaced by the `net.cidr_contains` built-in. -var NetCIDROverlap = v1.NetCIDROverlap - -// CastArray checks the underlying type of the input. If it is array or set, an array -// containing the values is returned. If it is not an array, an error is thrown. -var CastArray = v1.CastArray - -// CastSet checks the underlying type of the input. -// If it is a set, the set is returned. -// If it is an array, the array is returned in set form (all duplicates removed) -// If neither, an error is thrown -var CastSet = v1.CastSet - -// CastString returns input if it is a string; if not returns error. -// For formatting variables, see sprintf -var CastString = v1.CastString - -// CastBoolean returns input if it is a boolean; if not returns error. -var CastBoolean = v1.CastBoolean - -// CastNull returns null if input is null; if not returns error. -var CastNull = v1.CastNull - -// CastObject returns the given object if it is null; throws an error otherwise -var CastObject = v1.CastObject - -// RegexMatchDeprecated declares `re_match` which has been deprecated. Use `regex.match` instead. -var RegexMatchDeprecated = v1.RegexMatchDeprecated - -// All takes a list and returns true if all of the items -// are true. A collection of length 0 returns true. -var All = v1.All - -// Any takes a collection and returns true if any of the items -// is true. A collection of length 0 returns false. -var Any = v1.Any - -// Builtin represents a built-in function supported by OPA. Every built-in -// function is uniquely identified by a name. -type Builtin = v1.Builtin diff --git a/vendor/github.com/open-policy-agent/opa/ast/capabilities.go b/vendor/github.com/open-policy-agent/opa/ast/capabilities.go deleted file mode 100644 index bc7278a885..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/capabilities.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - "io" - - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// VersonIndex contains an index from built-in function name, language feature, -// and future rego keyword to version number. During the build, this is used to -// create an index of the minimum version required for the built-in/feature/kw. -type VersionIndex = v1.VersionIndex - -// In the compiler, we used this to check that we're OK working with ref heads. -// If this isn't present, we'll fail. This is to ensure that older versions of -// OPA can work with policies that we're compiling -- if they don't know ref -// heads, they wouldn't be able to parse them. -const FeatureRefHeadStringPrefixes = v1.FeatureRefHeadStringPrefixes -const FeatureRefHeads = v1.FeatureRefHeads -const FeatureRegoV1 = v1.FeatureRegoV1 -const FeatureRegoV1Import = v1.FeatureRegoV1Import - -// Capabilities defines a structure containing data that describes the capabilities -// or features supported by a particular version of OPA. -type Capabilities = v1.Capabilities - -// WasmABIVersion captures the Wasm ABI version. Its `Minor` version is indicating -// backwards-compatible changes. -type WasmABIVersion = v1.WasmABIVersion - -// CapabilitiesForThisVersion returns the capabilities of this version of OPA. -func CapabilitiesForThisVersion() *Capabilities { - return v1.CapabilitiesForThisVersion(v1.CapabilitiesRegoVersion(DefaultRegoVersion)) -} - -// LoadCapabilitiesJSON loads a JSON serialized capabilities structure from the reader r. -func LoadCapabilitiesJSON(r io.Reader) (*Capabilities, error) { - return v1.LoadCapabilitiesJSON(r) -} - -// LoadCapabilitiesVersion loads a JSON serialized capabilities structure from the specific version. -func LoadCapabilitiesVersion(version string) (*Capabilities, error) { - return v1.LoadCapabilitiesVersion(version) -} - -// LoadCapabilitiesFile loads a JSON serialized capabilities structure from a file. -func LoadCapabilitiesFile(file string) (*Capabilities, error) { - return v1.LoadCapabilitiesFile(file) -} - -// LoadCapabilitiesVersions loads all capabilities versions -func LoadCapabilitiesVersions() ([]string, error) { - return v1.LoadCapabilitiesVersions() -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/check.go b/vendor/github.com/open-policy-agent/opa/ast/check.go deleted file mode 100644 index 4cf00436df..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/check.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2017 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// UnificationErrDetail describes a type mismatch error when two values are -// unified (e.g., x = [1,2,y]). -type UnificationErrDetail = v1.UnificationErrDetail - -// RefErrUnsupportedDetail describes an undefined reference error where the -// referenced value does not support dereferencing (e.g., scalars). -type RefErrUnsupportedDetail = v1.RefErrUnsupportedDetail - -// RefErrInvalidDetail describes an undefined reference error where the referenced -// value does not support the reference operand (e.g., missing object key, -// invalid key type, etc.) -type RefErrInvalidDetail = v1.RefErrInvalidDetail diff --git a/vendor/github.com/open-policy-agent/opa/ast/compare.go b/vendor/github.com/open-policy-agent/opa/ast/compare.go deleted file mode 100644 index 5e617e992f..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/compare.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// Compare returns an integer indicating whether two AST values are less than, -// equal to, or greater than each other. -// -// If a is less than b, the return value is negative. If a is greater than b, -// the return value is positive. If a is equal to b, the return value is zero. -// -// Different types are never equal to each other. For comparison purposes, types -// are sorted as follows: -// -// nil < Null < Boolean < Number < String < Var < Ref < Array < Object < Set < -// ArrayComprehension < ObjectComprehension < SetComprehension < Expr < SomeDecl -// < With < Body < Rule < Import < Package < Module. -// -// Arrays and Refs are equal if and only if both a and b have the same length -// and all corresponding elements are equal. If one element is not equal, the -// return value is the same as for the first differing element. If all elements -// are equal but a and b have different lengths, the shorter is considered less -// than the other. -// -// Objects are considered equal if and only if both a and b have the same sorted -// (key, value) pairs and are of the same length. Other comparisons are -// consistent but not defined. -// -// Sets are considered equal if and only if the symmetric difference of a and b -// is empty. -// Other comparisons are consistent but not defined. -func Compare(a, b any) int { - return v1.Compare(a, b) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/compile.go b/vendor/github.com/open-policy-agent/opa/ast/compile.go deleted file mode 100644 index 5a3daa910a..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/compile.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// CompileErrorLimitDefault is the default number errors a compiler will allow before -// exiting. -const CompileErrorLimitDefault = 10 - -// Compiler contains the state of a compilation process. -type Compiler = v1.Compiler - -// CompilerStage defines the interface for stages in the compiler. -type CompilerStage = v1.CompilerStage - -// CompilerEvalMode allows toggling certain stages that are only -// needed for certain modes, Concretely, only "topdown" mode will -// have the compiler build comprehension and rule indices. -type CompilerEvalMode = v1.CompilerEvalMode - -const ( - // EvalModeTopdown (default) instructs the compiler to build rule - // and comprehension indices used by topdown evaluation. - EvalModeTopdown = v1.EvalModeTopdown - - // EvalModeIR makes the compiler skip the stages for comprehension - // and rule indices. - EvalModeIR = v1.EvalModeIR -) - -// CompilerStageDefinition defines a compiler stage -type CompilerStageDefinition = v1.CompilerStageDefinition - -// RulesOptions defines the options for retrieving rules by Ref from the -// compiler. -type RulesOptions = v1.RulesOptions - -// QueryContext contains contextual information for running an ad-hoc query. -// -// Ad-hoc queries can be run in the context of a package and imports may be -// included to provide concise access to data. -type QueryContext = v1.QueryContext - -// NewQueryContext returns a new QueryContext object. -func NewQueryContext() *QueryContext { - return v1.NewQueryContext() -} - -// QueryCompiler defines the interface for compiling ad-hoc queries. -type QueryCompiler = v1.QueryCompiler - -// QueryCompilerStage defines the interface for stages in the query compiler. -type QueryCompilerStage = v1.QueryCompilerStage - -// QueryCompilerStageDefinition defines a QueryCompiler stage -type QueryCompilerStageDefinition = v1.QueryCompilerStageDefinition - -// NewCompiler returns a new empty compiler. -func NewCompiler() *Compiler { - return v1.NewCompiler().WithDefaultRegoVersion(DefaultRegoVersion) -} - -// ModuleLoader defines the interface that callers can implement to enable lazy -// loading of modules during compilation. -type ModuleLoader = v1.ModuleLoader - -// SafetyCheckVisitorParams defines the AST visitor parameters to use for collecting -// variables during the safety check. This has to be exported because it's relied on -// by the copy propagation implementation in topdown. -var SafetyCheckVisitorParams = v1.SafetyCheckVisitorParams - -// ComprehensionIndex specifies how the comprehension term can be indexed. The keys -// tell the evaluator what variables to use for indexing. In the future, the index -// could be expanded with more information that would allow the evaluator to index -// a larger fragment of comprehensions (e.g., by closing over variables in the outer -// query.) -type ComprehensionIndex = v1.ComprehensionIndex - -// ModuleTreeNode represents a node in the module tree. The module -// tree is keyed by the package path. -type ModuleTreeNode = v1.ModuleTreeNode - -// TreeNode represents a node in the rule tree. The rule tree is keyed by -// rule path. -type TreeNode = v1.TreeNode - -// NewRuleTree returns a new TreeNode that represents the root -// of the rule tree populated with the given rules. -func NewRuleTree(mtree *ModuleTreeNode) *TreeNode { - return v1.NewRuleTree(mtree) -} - -// Graph represents the graph of dependencies between rules. -type Graph = v1.Graph - -// NewGraph returns a new Graph based on modules. The list function must return -// the rules referred to directly by the ref. -func NewGraph(modules map[string]*Module, list func(Ref) []*Rule) *Graph { - return v1.NewGraph(modules, list) -} - -// GraphTraversal is a Traversal that understands the dependency graph -type GraphTraversal = v1.GraphTraversal - -// NewGraphTraversal returns a Traversal for the dependency graph -func NewGraphTraversal(graph *Graph) *GraphTraversal { - return v1.NewGraphTraversal(graph) -} - -// OutputVarsFromBody returns all variables which are the "output" for -// the given body. For safety checks this means that they would be -// made safe by the body. -func OutputVarsFromBody(c *Compiler, body Body, safe VarSet) VarSet { - return v1.OutputVarsFromBody(c, body, safe) -} - -// OutputVarsFromExpr returns all variables which are the "output" for -// the given expression. For safety checks this means that they would be -// made safe by the expr. -func OutputVarsFromExpr(c *Compiler, expr *Expr, safe VarSet) VarSet { - return v1.OutputVarsFromExpr(c, expr, safe) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/compilehelper.go b/vendor/github.com/open-policy-agent/opa/ast/compilehelper.go deleted file mode 100644 index 37ede329ea..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/compilehelper.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import v1 "github.com/open-policy-agent/opa/v1/ast" - -// CompileModules takes a set of Rego modules represented as strings and -// compiles them for evaluation. The keys of the map are used as filenames. -func CompileModules(modules map[string]string) (*Compiler, error) { - return CompileModulesWithOpt(modules, CompileOpts{ - ParserOptions: ParserOptions{ - RegoVersion: DefaultRegoVersion, - }, - }) -} - -// CompileOpts defines a set of options for the compiler. -type CompileOpts = v1.CompileOpts - -// CompileModulesWithOpt takes a set of Rego modules represented as strings and -// compiles them for evaluation. The keys of the map are used as filenames. -func CompileModulesWithOpt(modules map[string]string, opts CompileOpts) (*Compiler, error) { - if opts.ParserOptions.RegoVersion == RegoUndefined { - opts.ParserOptions.RegoVersion = DefaultRegoVersion - } - - return v1.CompileModulesWithOpt(modules, opts) -} - -// MustCompileModules compiles a set of Rego modules represented as strings. If -// the compilation process fails, this function panics. -func MustCompileModules(modules map[string]string) *Compiler { - return MustCompileModulesWithOpts(modules, CompileOpts{}) -} - -// MustCompileModulesWithOpts compiles a set of Rego modules represented as strings. If -// the compilation process fails, this function panics. -func MustCompileModulesWithOpts(modules map[string]string, opts CompileOpts) *Compiler { - - compiler, err := CompileModulesWithOpt(modules, opts) - if err != nil { - panic(err) - } - - return compiler -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/conflicts.go b/vendor/github.com/open-policy-agent/opa/ast/conflicts.go deleted file mode 100644 index 10edce382c..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/conflicts.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2019 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// CheckPathConflicts returns a set of errors indicating paths that -// are in conflict with the result of the provided callable. -func CheckPathConflicts(c *Compiler, exists func([]string) (bool, error)) Errors { - return v1.CheckPathConflicts(c, exists) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/doc.go b/vendor/github.com/open-policy-agent/opa/ast/doc.go deleted file mode 100644 index ba974e5ba6..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2024 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. -// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. -// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. -package ast diff --git a/vendor/github.com/open-policy-agent/opa/ast/env.go b/vendor/github.com/open-policy-agent/opa/ast/env.go deleted file mode 100644 index ef0ccf89ce..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/env.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2017 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// TypeEnv contains type info for static analysis such as type checking. -type TypeEnv = v1.TypeEnv diff --git a/vendor/github.com/open-policy-agent/opa/ast/errors.go b/vendor/github.com/open-policy-agent/opa/ast/errors.go deleted file mode 100644 index 722cfc0fb7..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/errors.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// Errors represents a series of errors encountered during parsing, compiling, -// etc. -type Errors = v1.Errors - -const ( - // ParseErr indicates an unclassified parse error occurred. - ParseErr = v1.ParseErr - - // CompileErr indicates an unclassified compile error occurred. - CompileErr = v1.CompileErr - - // TypeErr indicates a type error was caught. - TypeErr = v1.TypeErr - - // UnsafeVarErr indicates an unsafe variable was found during compilation. - UnsafeVarErr = v1.UnsafeVarErr - - // RecursionErr indicates recursion was found during compilation. - RecursionErr = v1.RecursionErr -) - -// IsError returns true if err is an AST error with code. -func IsError(code string, err error) bool { - return v1.IsError(code, err) -} - -// ErrorDetails defines the interface for detailed error messages. -type ErrorDetails = v1.ErrorDetails - -// Error represents a single error caught during parsing, compiling, etc. -type Error = v1.Error - -// NewError returns a new Error object. -func NewError(code string, loc *Location, f string, a ...any) *Error { - return v1.NewError(code, loc, f, a...) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/index.go b/vendor/github.com/open-policy-agent/opa/ast/index.go deleted file mode 100644 index 7e80bb7716..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/index.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2017 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// RuleIndex defines the interface for rule indices. -type RuleIndex v1.RuleIndex - -// IndexResult contains the result of an index lookup. -type IndexResult = v1.IndexResult - -// NewIndexResult returns a new IndexResult object. -func NewIndexResult(kind RuleKind) *IndexResult { - return v1.NewIndexResult(kind) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/interning.go b/vendor/github.com/open-policy-agent/opa/ast/interning.go deleted file mode 100644 index 29231006aa..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/interning.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2024 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -func InternedBooleanTerm(b bool) *Term { - return v1.InternedTerm(b) -} - -// InternedIntNumberTerm returns a term with the given integer value. The term is -// cached between -1 to 512, and for values outside of that range, this function -// is equivalent to ast.IntNumberTerm. -func InternedIntNumberTerm(i int) *Term { - return v1.InternedTerm(i) -} - -func HasInternedIntNumberTerm(i int) bool { - return v1.HasInternedIntNumberTerm(i) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/json/doc.go b/vendor/github.com/open-policy-agent/opa/ast/json/doc.go deleted file mode 100644 index 26aee9b994..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/json/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2024 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. -// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. -// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. -package json diff --git a/vendor/github.com/open-policy-agent/opa/ast/json/json.go b/vendor/github.com/open-policy-agent/opa/ast/json/json.go index 8a3a36bb9b..6fe3e76e93 100644 --- a/vendor/github.com/open-policy-agent/opa/ast/json/json.go +++ b/vendor/github.com/open-policy-agent/opa/ast/json/json.go @@ -1,6 +1,15 @@ +// Copyright 2026 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. +// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. +// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. package json -import v1 "github.com/open-policy-agent/opa/v1/ast/json" +import ( + v1 "github.com/open-policy-agent/opa/v1/ast/json" +) // Options defines the options for JSON operations, // currently only marshaling can be configured diff --git a/vendor/github.com/open-policy-agent/opa/ast/map.go b/vendor/github.com/open-policy-agent/opa/ast/map.go deleted file mode 100644 index 070ad3e5de..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/map.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// ValueMap represents a key/value map between AST term values. Any type of term -// can be used as a key in the map. -type ValueMap = v1.ValueMap - -// NewValueMap returns a new ValueMap. -func NewValueMap() *ValueMap { - return v1.NewValueMap() -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/parser.go b/vendor/github.com/open-policy-agent/opa/ast/parser.go deleted file mode 100644 index 45cd4da06e..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/parser.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2024 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -var RegoV1CompatibleRef = v1.RegoV1CompatibleRef - -// RegoVersion defines the Rego syntax requirements for a module. -type RegoVersion = v1.RegoVersion - -const DefaultRegoVersion = RegoV0 - -const ( - RegoUndefined = v1.RegoUndefined - // RegoV0 is the default, original Rego syntax. - RegoV0 = v1.RegoV0 - // RegoV0CompatV1 requires modules to comply with both the RegoV0 and RegoV1 syntax (as when 'rego.v1' is imported in a module). - // Shortly, RegoV1 compatibility is required, but 'rego.v1' or 'future.keywords' must also be imported. - RegoV0CompatV1 = v1.RegoV0CompatV1 - // RegoV1 is the Rego syntax enforced by OPA 1.0; e.g.: - // future.keywords part of default keyword set, and don't require imports; - // 'if' and 'contains' required in rule heads; - // (some) strict checks on by default. - RegoV1 = v1.RegoV1 -) - -func RegoVersionFromInt(i int) RegoVersion { - return v1.RegoVersionFromInt(i) -} - -// Parser is used to parse Rego statements. -type Parser = v1.Parser - -// ParserOptions defines the options for parsing Rego statements. -type ParserOptions = v1.ParserOptions - -// NewParser creates and initializes a Parser. -func NewParser() *Parser { - return v1.NewParser().WithRegoVersion(DefaultRegoVersion) -} - -func IsFutureKeyword(s string) bool { - return v1.IsFutureKeywordForRegoVersion(s, RegoV0) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/parser_ext.go b/vendor/github.com/open-policy-agent/opa/ast/parser_ext.go deleted file mode 100644 index 2d59616932..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/parser_ext.go +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright 2024 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - "errors" - "fmt" - - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// MustParseBody returns a parsed body. -// If an error occurs during parsing, panic. -func MustParseBody(input string) Body { - return MustParseBodyWithOpts(input, ParserOptions{}) -} - -// MustParseBodyWithOpts returns a parsed body. -// If an error occurs during parsing, panic. -func MustParseBodyWithOpts(input string, opts ParserOptions) Body { - return v1.MustParseBodyWithOpts(input, setDefaultRegoVersion(opts)) -} - -// MustParseExpr returns a parsed expression. -// If an error occurs during parsing, panic. -func MustParseExpr(input string) *Expr { - parsed, err := ParseExpr(input) - if err != nil { - panic(err) - } - return parsed -} - -// MustParseImports returns a slice of imports. -// If an error occurs during parsing, panic. -func MustParseImports(input string) []*Import { - parsed, err := ParseImports(input) - if err != nil { - panic(err) - } - return parsed -} - -// MustParseModule returns a parsed module. -// If an error occurs during parsing, panic. -func MustParseModule(input string) *Module { - return MustParseModuleWithOpts(input, ParserOptions{}) -} - -// MustParseModuleWithOpts returns a parsed module. -// If an error occurs during parsing, panic. -func MustParseModuleWithOpts(input string, opts ParserOptions) *Module { - return v1.MustParseModuleWithOpts(input, setDefaultRegoVersion(opts)) -} - -// MustParsePackage returns a Package. -// If an error occurs during parsing, panic. -func MustParsePackage(input string) *Package { - parsed, err := ParsePackage(input) - if err != nil { - panic(err) - } - return parsed -} - -// MustParseStatements returns a slice of parsed statements. -// If an error occurs during parsing, panic. -func MustParseStatements(input string) []Statement { - parsed, _, err := ParseStatements("", input) - if err != nil { - panic(err) - } - return parsed -} - -// MustParseStatement returns exactly one statement. -// If an error occurs during parsing, panic. -func MustParseStatement(input string) Statement { - parsed, err := ParseStatement(input) - if err != nil { - panic(err) - } - return parsed -} - -func MustParseStatementWithOpts(input string, popts ParserOptions) Statement { - return v1.MustParseStatementWithOpts(input, setDefaultRegoVersion(popts)) -} - -// MustParseRef returns a parsed reference. -// If an error occurs during parsing, panic. -func MustParseRef(input string) Ref { - parsed, err := ParseRef(input) - if err != nil { - panic(err) - } - return parsed -} - -// MustParseRule returns a parsed rule. -// If an error occurs during parsing, panic. -func MustParseRule(input string) *Rule { - parsed, err := ParseRule(input) - if err != nil { - panic(err) - } - return parsed -} - -// MustParseRuleWithOpts returns a parsed rule. -// If an error occurs during parsing, panic. -func MustParseRuleWithOpts(input string, opts ParserOptions) *Rule { - return v1.MustParseRuleWithOpts(input, setDefaultRegoVersion(opts)) -} - -// MustParseTerm returns a parsed term. -// If an error occurs during parsing, panic. -func MustParseTerm(input string) *Term { - parsed, err := ParseTerm(input) - if err != nil { - panic(err) - } - return parsed -} - -// ParseRuleFromBody returns a rule if the body can be interpreted as a rule -// definition. Otherwise, an error is returned. -func ParseRuleFromBody(module *Module, body Body) (*Rule, error) { - return v1.ParseRuleFromBody(module, body) -} - -// ParseRuleFromExpr returns a rule if the expression can be interpreted as a -// rule definition. -func ParseRuleFromExpr(module *Module, expr *Expr) (*Rule, error) { - return v1.ParseRuleFromExpr(module, expr) -} - -// ParseCompleteDocRuleFromAssignmentExpr returns a rule if the expression can -// be interpreted as a complete document definition declared with the assignment -// operator. -func ParseCompleteDocRuleFromAssignmentExpr(module *Module, lhs, rhs *Term) (*Rule, error) { - return v1.ParseCompleteDocRuleFromAssignmentExpr(module, lhs, rhs) -} - -// ParseCompleteDocRuleFromEqExpr returns a rule if the expression can be -// interpreted as a complete document definition. -func ParseCompleteDocRuleFromEqExpr(module *Module, lhs, rhs *Term) (*Rule, error) { - return v1.ParseCompleteDocRuleFromEqExpr(module, lhs, rhs) -} - -func ParseCompleteDocRuleWithDotsFromTerm(module *Module, term *Term) (*Rule, error) { - return v1.ParseCompleteDocRuleWithDotsFromTerm(module, term) -} - -// ParsePartialObjectDocRuleFromEqExpr returns a rule if the expression can be -// interpreted as a partial object document definition. -func ParsePartialObjectDocRuleFromEqExpr(module *Module, lhs, rhs *Term) (*Rule, error) { - return v1.ParsePartialObjectDocRuleFromEqExpr(module, lhs, rhs) -} - -// ParsePartialSetDocRuleFromTerm returns a rule if the term can be interpreted -// as a partial set document definition. -func ParsePartialSetDocRuleFromTerm(module *Module, term *Term) (*Rule, error) { - return v1.ParsePartialSetDocRuleFromTerm(module, term) -} - -// ParseRuleFromCallEqExpr returns a rule if the term can be interpreted as a -// function definition (e.g., f(x) = y => f(x) = y { true }). -func ParseRuleFromCallEqExpr(module *Module, lhs, rhs *Term) (*Rule, error) { - return v1.ParseRuleFromCallEqExpr(module, lhs, rhs) -} - -// ParseRuleFromCallExpr returns a rule if the terms can be interpreted as a -// function returning true or some value (e.g., f(x) => f(x) = true { true }). -func ParseRuleFromCallExpr(module *Module, terms []*Term) (*Rule, error) { - return v1.ParseRuleFromCallExpr(module, terms) -} - -// ParseImports returns a slice of Import objects. -func ParseImports(input string) ([]*Import, error) { - return v1.ParseImports(input) -} - -// ParseModule returns a parsed Module object. -// For details on Module objects and their fields, see policy.go. -// Empty input will return nil, nil. -func ParseModule(filename, input string) (*Module, error) { - return ParseModuleWithOpts(filename, input, ParserOptions{}) -} - -// ParseModuleWithOpts returns a parsed Module object, and has an additional input ParserOptions -// For details on Module objects and their fields, see policy.go. -// Empty input will return nil, nil. -func ParseModuleWithOpts(filename, input string, popts ParserOptions) (*Module, error) { - return v1.ParseModuleWithOpts(filename, input, setDefaultRegoVersion(popts)) -} - -// ParseBody returns exactly one body. -// If multiple bodies are parsed, an error is returned. -func ParseBody(input string) (Body, error) { - return ParseBodyWithOpts(input, ParserOptions{SkipRules: true}) -} - -// ParseBodyWithOpts returns exactly one body. It does _not_ set SkipRules: true on its own, -// but respects whatever ParserOptions it's been given. -func ParseBodyWithOpts(input string, popts ParserOptions) (Body, error) { - return v1.ParseBodyWithOpts(input, setDefaultRegoVersion(popts)) -} - -// ParseExpr returns exactly one expression. -// If multiple expressions are parsed, an error is returned. -func ParseExpr(input string) (*Expr, error) { - body, err := ParseBody(input) - if err != nil { - return nil, fmt.Errorf("failed to parse expression: %w", err) - } - if len(body) != 1 { - return nil, fmt.Errorf("expected exactly one expression but got: %v", body) - } - return body[0], nil -} - -// ParsePackage returns exactly one Package. -// If multiple statements are parsed, an error is returned. -func ParsePackage(input string) (*Package, error) { - return v1.ParsePackage(input) -} - -// ParseTerm returns exactly one term. -// If multiple terms are parsed, an error is returned. -func ParseTerm(input string) (*Term, error) { - body, err := ParseBody(input) - if err != nil { - return nil, fmt.Errorf("failed to parse term: %w", err) - } - if len(body) != 1 { - return nil, fmt.Errorf("expected exactly one term but got: %v", body) - } - term, ok := body[0].Terms.(*Term) - if !ok { - return nil, fmt.Errorf("expected term but got %v", body[0].Terms) - } - return term, nil -} - -// ParseRef returns exactly one reference. -func ParseRef(input string) (Ref, error) { - term, err := ParseTerm(input) - if err != nil { - return nil, fmt.Errorf("failed to parse ref: %w", err) - } - ref, ok := term.Value.(Ref) - if !ok { - return nil, fmt.Errorf("expected ref but got %v", term) - } - return ref, nil -} - -// ParseRuleWithOpts returns exactly one rule. -// If multiple rules are parsed, an error is returned. -func ParseRuleWithOpts(input string, opts ParserOptions) (*Rule, error) { - return v1.ParseRuleWithOpts(input, setDefaultRegoVersion(opts)) -} - -// ParseRule returns exactly one rule. -// If multiple rules are parsed, an error is returned. -func ParseRule(input string) (*Rule, error) { - return ParseRuleWithOpts(input, ParserOptions{}) -} - -// ParseStatement returns exactly one statement. -// A statement might be a term, expression, rule, etc. Regardless, -// this function expects *exactly* one statement. If multiple -// statements are parsed, an error is returned. -func ParseStatement(input string) (Statement, error) { - stmts, _, err := ParseStatements("", input) - if err != nil { - return nil, err - } - if len(stmts) != 1 { - return nil, errors.New("expected exactly one statement") - } - return stmts[0], nil -} - -func ParseStatementWithOpts(input string, popts ParserOptions) (Statement, error) { - return v1.ParseStatementWithOpts(input, setDefaultRegoVersion(popts)) -} - -// ParseStatements is deprecated. Use ParseStatementWithOpts instead. -func ParseStatements(filename, input string) ([]Statement, []*Comment, error) { - return ParseStatementsWithOpts(filename, input, ParserOptions{}) -} - -// ParseStatementsWithOpts returns a slice of parsed statements. This is the -// default return value from the parser. -func ParseStatementsWithOpts(filename, input string, popts ParserOptions) ([]Statement, []*Comment, error) { - return v1.ParseStatementsWithOpts(filename, input, setDefaultRegoVersion(popts)) -} - -// ParserErrorDetail holds additional details for parser errors. -type ParserErrorDetail = v1.ParserErrorDetail - -func setDefaultRegoVersion(opts ParserOptions) ParserOptions { - if opts.RegoVersion == RegoUndefined { - opts.RegoVersion = DefaultRegoVersion - } - return opts -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/policy.go b/vendor/github.com/open-policy-agent/opa/ast/policy.go deleted file mode 100644 index 5055e8f23f..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/policy.go +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2024 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - astJSON "github.com/open-policy-agent/opa/ast/json" - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// DefaultRootDocument is the default root document. -// -// All package directives inside source files are implicitly prefixed with the -// DefaultRootDocument value. -var DefaultRootDocument = v1.DefaultRootDocument - -// InputRootDocument names the document containing query arguments. -var InputRootDocument = v1.InputRootDocument - -// SchemaRootDocument names the document containing external data schemas. -var SchemaRootDocument = v1.SchemaRootDocument - -// FunctionArgRootDocument names the document containing function arguments. -// It's only for internal usage, for referencing function arguments between -// the index and topdown. -var FunctionArgRootDocument = v1.FunctionArgRootDocument - -// FutureRootDocument names the document containing new, to-become-default, -// features. -var FutureRootDocument = v1.FutureRootDocument - -// RegoRootDocument names the document containing new, to-become-default, -// features in a future versioned release. -var RegoRootDocument = v1.RegoRootDocument - -// RootDocumentNames contains the names of top-level documents that can be -// referred to in modules and queries. -// -// Note, the schema document is not currently implemented in the evaluator so it -// is not registered as a root document name (yet). -var RootDocumentNames = v1.RootDocumentNames - -// DefaultRootRef is a reference to the root of the default document. -// -// All refs to data in the policy engine's storage layer are prefixed with this ref. -var DefaultRootRef = v1.DefaultRootRef - -// InputRootRef is a reference to the root of the input document. -// -// All refs to query arguments are prefixed with this ref. -var InputRootRef = v1.InputRootRef - -// SchemaRootRef is a reference to the root of the schema document. -// -// All refs to schema documents are prefixed with this ref. Note, the schema -// document is not currently implemented in the evaluator so it is not -// registered as a root document ref (yet). -var SchemaRootRef = v1.SchemaRootRef - -// RootDocumentRefs contains the prefixes of top-level documents that all -// non-local references start with. -var RootDocumentRefs = v1.RootDocumentRefs - -// SystemDocumentKey is the name of the top-level key that identifies the system -// document. -const SystemDocumentKey = v1.SystemDocumentKey - -// ReservedVars is the set of names that refer to implicitly ground vars. -var ReservedVars = v1.ReservedVars - -// Wildcard represents the wildcard variable as defined in the language. -var Wildcard = v1.Wildcard - -// WildcardPrefix is the special character that all wildcard variables are -// prefixed with when the statement they are contained in is parsed. -const WildcardPrefix = v1.WildcardPrefix - -// Keywords contains strings that map to language keywords. -var Keywords = v1.Keywords - -var KeywordsV0 = v1.KeywordsV0 - -var KeywordsV1 = v1.KeywordsV1 - -func KeywordsForRegoVersion(v RegoVersion) []string { - return v1.KeywordsForRegoVersion(v) -} - -// IsKeyword returns true if s is a language keyword. -func IsKeyword(s string) bool { - return v1.IsKeyword(s) -} - -func IsInKeywords(s string, keywords []string) bool { - return v1.IsInKeywords(s, keywords) -} - -// IsKeywordInRegoVersion returns true if s is a language keyword. -func IsKeywordInRegoVersion(s string, regoVersion RegoVersion) bool { - return v1.IsKeywordInRegoVersion(s, regoVersion) -} - -type ( - // Node represents a node in an AST. Nodes may be statements in a policy module - // or elements of an ad-hoc query, expression, etc. - Node = v1.Node - - // Statement represents a single statement in a policy module. - Statement = v1.Statement -) - -type ( - - // Module represents a collection of policies (defined by rules) - // within a namespace (defined by the package) and optional - // dependencies on external documents (defined by imports). - Module = v1.Module - - // Comment contains the raw text from the comment in the definition. - Comment = v1.Comment - - // Package represents the namespace of the documents produced - // by rules inside the module. - Package = v1.Package - - // Import represents a dependency on a document outside of the policy - // namespace. Imports are optional. - Import = v1.Import - - // Rule represents a rule as defined in the language. Rules define the - // content of documents that represent policy decisions. - Rule = v1.Rule - - // Head represents the head of a rule. - Head = v1.Head - - // Args represents zero or more arguments to a rule. - Args = v1.Args - - // Body represents one or more expressions contained inside a rule or user - // function. - Body = v1.Body - - // Expr represents a single expression contained inside the body of a rule. - Expr = v1.Expr - - // SomeDecl represents a variable declaration statement. The symbols are variables. - SomeDecl = v1.SomeDecl - - Every = v1.Every - - // With represents a modifier on an expression. - With = v1.With -) - -// NewComment returns a new Comment object. -func NewComment(text []byte) *Comment { - return v1.NewComment(text) -} - -// IsValidImportPath returns an error indicating if the import path is invalid. -// If the import path is valid, err is nil. -func IsValidImportPath(v Value) (err error) { - return v1.IsValidImportPath(v) -} - -// NewHead returns a new Head object. If args are provided, the first will be -// used for the key and the second will be used for the value. -func NewHead(name Var, args ...*Term) *Head { - return v1.NewHead(name, args...) -} - -// VarHead creates a head object, initializes its Name, Location, and Options, -// and returns the new head. -func VarHead(name Var, location *Location, jsonOpts *astJSON.Options) *Head { - return v1.VarHead(name, location, jsonOpts) -} - -// RefHead returns a new Head object with the passed Ref. If args are provided, -// the first will be used for the value. -func RefHead(ref Ref, args ...*Term) *Head { - return v1.RefHead(ref, args...) -} - -// DocKind represents the collection of document types that can be produced by rules. -type DocKind = v1.DocKind - -const ( - // CompleteDoc represents a document that is completely defined by the rule. - CompleteDoc = v1.CompleteDoc - - // PartialSetDoc represents a set document that is partially defined by the rule. - PartialSetDoc = v1.PartialSetDoc - - // PartialObjectDoc represents an object document that is partially defined by the rule. - PartialObjectDoc = v1.PartialObjectDoc -) - -type RuleKind = v1.RuleKind - -const ( - SingleValue = v1.SingleValue - MultiValue = v1.MultiValue -) - -// NewBody returns a new Body containing the given expressions. The indices of -// the immediate expressions will be reset. -func NewBody(exprs ...*Expr) Body { - return v1.NewBody(exprs...) -} - -// NewExpr returns a new Expr object. -func NewExpr(terms any) *Expr { - return v1.NewExpr(terms) -} - -// NewBuiltinExpr creates a new Expr object with the supplied terms. -// The builtin operator must be the first term. -func NewBuiltinExpr(terms ...*Term) *Expr { - return v1.NewBuiltinExpr(terms...) -} - -// Copy returns a deep copy of the AST node x. If x is not an AST node, x is returned unmodified. -func Copy(x any) any { - return v1.Copy(x) -} - -// RuleSet represents a collection of rules that produce a virtual document. -type RuleSet = v1.RuleSet - -// NewRuleSet returns a new RuleSet containing the given rules. -func NewRuleSet(rules ...*Rule) RuleSet { - return v1.NewRuleSet(rules...) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/pretty.go b/vendor/github.com/open-policy-agent/opa/ast/pretty.go deleted file mode 100644 index 84e42f9aec..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/pretty.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2018 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - "io" - - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// Pretty writes a pretty representation of the AST rooted at x to w. -// -// This is function is intended for debug purposes when inspecting ASTs. -func Pretty(w io.Writer, x any) { - v1.Pretty(w, x) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/schema.go b/vendor/github.com/open-policy-agent/opa/ast/schema.go deleted file mode 100644 index 979958a3c0..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/schema.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2021 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// SchemaSet holds a map from a path to a schema. -type SchemaSet = v1.SchemaSet - -// NewSchemaSet returns an empty SchemaSet. -func NewSchemaSet() *SchemaSet { - return v1.NewSchemaSet() -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/strings.go b/vendor/github.com/open-policy-agent/opa/ast/strings.go deleted file mode 100644 index c2c81de8b7..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/strings.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// TypeName returns a human readable name for the AST element type. -func TypeName(x any) string { - return v1.TypeName(x) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/term.go b/vendor/github.com/open-policy-agent/opa/ast/term.go deleted file mode 100644 index 202355070f..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/term.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2024 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - "encoding/json" - "io" - - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// Location records a position in source code. -type Location = v1.Location - -// NewLocation returns a new Location object. -func NewLocation(text []byte, file string, row int, col int) *Location { - return v1.NewLocation(text, file, row, col) -} - -// Value declares the common interface for all Term values. Every kind of Term value -// in the language is represented as a type that implements this interface: -// -// - Null, Boolean, Number, String -// - Object, Array, Set -// - Variables, References -// - Array, Set, and Object Comprehensions -// - Calls -type Value = v1.Value - -// InterfaceToValue converts a native Go value x to a Value. -func InterfaceToValue(x any) (Value, error) { - return v1.InterfaceToValue(x) -} - -// ValueFromReader returns an AST value from a JSON serialized value in the reader. -func ValueFromReader(r io.Reader) (Value, error) { - return v1.ValueFromReader(r) -} - -// As converts v into a Go native type referred to by x. -func As(v Value, x any) error { - return v1.As(v, x) -} - -// Resolver defines the interface for resolving references to native Go values. -type Resolver = v1.Resolver - -// ValueResolver defines the interface for resolving references to AST values. -type ValueResolver = v1.ValueResolver - -// UnknownValueErr indicates a ValueResolver was unable to resolve a reference -// because the reference refers to an unknown value. -type UnknownValueErr = v1.UnknownValueErr - -// IsUnknownValueErr returns true if the err is an UnknownValueErr. -func IsUnknownValueErr(err error) bool { - return v1.IsUnknownValueErr(err) -} - -// ValueToInterface returns the Go representation of an AST value. The AST -// value should not contain any values that require evaluation (e.g., vars, -// comprehensions, etc.) -func ValueToInterface(v Value, resolver Resolver) (any, error) { - return v1.ValueToInterface(v, resolver) -} - -// JSON returns the JSON representation of v. The value must not contain any -// refs or terms that require evaluation (e.g., vars, comprehensions, etc.) -func JSON(v Value) (any, error) { - return v1.JSON(v) -} - -// JSONOpt defines parameters for AST to JSON conversion. -type JSONOpt = v1.JSONOpt - -// JSONWithOpt returns the JSON representation of v. The value must not contain any -// refs or terms that require evaluation (e.g., vars, comprehensions, etc.) -func JSONWithOpt(v Value, opt JSONOpt) (any, error) { - return v1.JSONWithOpt(v, opt) -} - -// MustJSON returns the JSON representation of v. The value must not contain any -// refs or terms that require evaluation (e.g., vars, comprehensions, etc.) If -// the conversion fails, this function will panic. This function is mostly for -// test purposes. -func MustJSON(v Value) any { - return v1.MustJSON(v) -} - -// MustInterfaceToValue converts a native Go value x to a Value. If the -// conversion fails, this function will panic. This function is mostly for test -// purposes. -func MustInterfaceToValue(x any) Value { - return v1.MustInterfaceToValue(x) -} - -// Term is an argument to a function. -type Term = v1.Term - -// NewTerm returns a new Term object. -func NewTerm(v Value) *Term { - return v1.NewTerm(v) -} - -// IsConstant returns true if the AST value is constant. -func IsConstant(v Value) bool { - return v1.IsConstant(v) -} - -// IsComprehension returns true if the supplied value is a comprehension. -func IsComprehension(x Value) bool { - return v1.IsComprehension(x) -} - -// ContainsRefs returns true if the Value v contains refs. -func ContainsRefs(v any) bool { - return v1.ContainsRefs(v) -} - -// ContainsComprehensions returns true if the Value v contains comprehensions. -func ContainsComprehensions(v any) bool { - return v1.ContainsComprehensions(v) -} - -// ContainsClosures returns true if the Value v contains closures. -func ContainsClosures(v any) bool { - return v1.ContainsClosures(v) -} - -// IsScalar returns true if the AST value is a scalar. -func IsScalar(v Value) bool { - return v1.IsScalar(v) -} - -// Null represents the null value defined by JSON. -type Null = v1.Null - -// NullTerm creates a new Term with a Null value. -func NullTerm() *Term { - return v1.NullTerm() -} - -// Boolean represents a boolean value defined by JSON. -type Boolean = v1.Boolean - -// BooleanTerm creates a new Term with a Boolean value. -func BooleanTerm(b bool) *Term { - return v1.BooleanTerm(b) -} - -// Number represents a numeric value as defined by JSON. -type Number = v1.Number - -// NumberTerm creates a new Term with a Number value. -func NumberTerm(n json.Number) *Term { - return v1.NumberTerm(n) -} - -// IntNumberTerm creates a new Term with an integer Number value. -func IntNumberTerm(i int) *Term { - return v1.IntNumberTerm(i) -} - -// UIntNumberTerm creates a new Term with an unsigned integer Number value. -func UIntNumberTerm(u uint64) *Term { - return v1.UIntNumberTerm(u) -} - -// FloatNumberTerm creates a new Term with a floating point Number value. -func FloatNumberTerm(f float64) *Term { - return v1.FloatNumberTerm(f) -} - -// String represents a string value as defined by JSON. -type String = v1.String - -// StringTerm creates a new Term with a String value. -func StringTerm(s string) *Term { - return v1.StringTerm(s) -} - -// Var represents a variable as defined by the language. -type Var = v1.Var - -// VarTerm creates a new Term with a Variable value. -func VarTerm(v string) *Term { - return v1.VarTerm(v) -} - -// Ref represents a reference as defined by the language. -type Ref = v1.Ref - -// EmptyRef returns a new, empty reference. -func EmptyRef() Ref { - return v1.EmptyRef() -} - -// PtrRef returns a new reference against the head for the pointer -// s. Path components in the pointer are unescaped. -func PtrRef(head *Term, s string) (Ref, error) { - return v1.PtrRef(head, s) -} - -// RefTerm creates a new Term with a Ref value. -func RefTerm(r ...*Term) *Term { - return v1.RefTerm(r...) -} - -func IsVarCompatibleString(s string) bool { - return v1.IsVarCompatibleString(s) -} - -// QueryIterator defines the interface for querying AST documents with references. -type QueryIterator = v1.QueryIterator - -// ArrayTerm creates a new Term with an Array value. -func ArrayTerm(a ...*Term) *Term { - return v1.ArrayTerm(a...) -} - -// NewArray creates an Array with the terms provided. The array will -// use the provided term slice. -func NewArray(a ...*Term) *Array { - return v1.NewArray(a...) -} - -// Array represents an array as defined by the language. Arrays are similar to the -// same types as defined by JSON with the exception that they can contain Vars -// and References. -type Array = v1.Array - -// Set represents a set as defined by the language. -type Set = v1.Set - -// NewSet returns a new Set containing t. -func NewSet(t ...*Term) Set { - return v1.NewSet(t...) -} - -func SetTerm(t ...*Term) *Term { - return v1.SetTerm(t...) -} - -// Object represents an object as defined by the language. -type Object = v1.Object - -// NewObject creates a new Object with t. -func NewObject(t ...[2]*Term) Object { - return v1.NewObject(t...) -} - -// ObjectTerm creates a new Term with an Object value. -func ObjectTerm(o ...[2]*Term) *Term { - return v1.ObjectTerm(o...) -} - -func LazyObject(blob map[string]any) Object { - return v1.LazyObject(blob) -} - -// Item is a helper for constructing an tuple containing two Terms -// representing a key/value pair in an Object. -func Item(key, value *Term) [2]*Term { - return v1.Item(key, value) -} - -// NOTE(philipc): The only way to get an ObjectKeyIterator should be -// from an Object. This ensures that the iterator can have implementation- -// specific details internally, with no contracts except to the very -// limited interface. -type ObjectKeysIterator = v1.ObjectKeysIterator - -// ArrayComprehension represents an array comprehension as defined in the language. -type ArrayComprehension = v1.ArrayComprehension - -// ArrayComprehensionTerm creates a new Term with an ArrayComprehension value. -func ArrayComprehensionTerm(term *Term, body Body) *Term { - return v1.ArrayComprehensionTerm(term, body) -} - -// ObjectComprehension represents an object comprehension as defined in the language. -type ObjectComprehension = v1.ObjectComprehension - -// ObjectComprehensionTerm creates a new Term with an ObjectComprehension value. -func ObjectComprehensionTerm(key, value *Term, body Body) *Term { - return v1.ObjectComprehensionTerm(key, value, body) -} - -// SetComprehension represents a set comprehension as defined in the language. -type SetComprehension = v1.SetComprehension - -// SetComprehensionTerm creates a new Term with an SetComprehension value. -func SetComprehensionTerm(term *Term, body Body) *Term { - return v1.SetComprehensionTerm(term, body) -} - -// Call represents as function call in the language. -type Call = v1.Call - -// CallTerm returns a new Term with a Call value defined by terms. The first -// term is the operator and the rest are operands. -func CallTerm(terms ...*Term) *Term { - return v1.CallTerm(terms...) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/transform.go b/vendor/github.com/open-policy-agent/opa/ast/transform.go deleted file mode 100644 index 8c03c48663..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/transform.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// Transformer defines the interface for transforming AST elements. If the -// transformer returns nil and does not indicate an error, the AST element will -// be set to nil and no transformations will be applied to children of the -// element. -type Transformer = v1.Transformer - -// Transform iterates the AST and calls the Transform function on the -// Transformer t for x before recursing. -func Transform(t Transformer, x any) (any, error) { - return v1.Transform(t, x) -} - -// TransformRefs calls the function f on all references under x. -func TransformRefs(x any, f func(Ref) (Value, error)) (any, error) { - return v1.TransformRefs(x, f) -} - -// TransformVars calls the function f on all vars under x. -func TransformVars(x any, f func(Var) (Value, error)) (any, error) { - return v1.TransformVars(x, f) -} - -// TransformComprehensions calls the functio nf on all comprehensions under x. -func TransformComprehensions(x any, f func(any) (Value, error)) (any, error) { - return v1.TransformComprehensions(x, f) -} - -// GenericTransformer implements the Transformer interface to provide a utility -// to transform AST nodes using a closure. -type GenericTransformer = v1.GenericTransformer - -// NewGenericTransformer returns a new GenericTransformer that will transform -// AST nodes using the function f. -func NewGenericTransformer(f func(x any) (any, error)) *GenericTransformer { - return v1.NewGenericTransformer(f) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/unify.go b/vendor/github.com/open-policy-agent/opa/ast/unify.go deleted file mode 100644 index 3cb260272a..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/unify.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import v1 "github.com/open-policy-agent/opa/v1/ast" - -// Unify returns a set of variables that will be unified when the equality expression defined by -// terms a and b is evaluated. The unifier assumes that variables in the VarSet safe are already -// unified. -func Unify(safe VarSet, a *Term, b *Term) VarSet { - return v1.Unify(safe, a, b) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/varset.go b/vendor/github.com/open-policy-agent/opa/ast/varset.go deleted file mode 100644 index 9e7db8efda..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/varset.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import ( - v1 "github.com/open-policy-agent/opa/v1/ast" -) - -// VarSet represents a set of variables. -type VarSet = v1.VarSet - -// NewVarSet returns a new VarSet containing the specified variables. -func NewVarSet(vs ...Var) VarSet { - return v1.NewVarSet(vs...) -} diff --git a/vendor/github.com/open-policy-agent/opa/ast/visit.go b/vendor/github.com/open-policy-agent/opa/ast/visit.go deleted file mode 100644 index 50028269d2..0000000000 --- a/vendor/github.com/open-policy-agent/opa/ast/visit.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -import v1 "github.com/open-policy-agent/opa/v1/ast" - -// Visitor defines the interface for iterating AST elements. The Visit function -// can return a Visitor w which will be used to visit the children of the AST -// element v. If the Visit function returns nil, the children will not be -// visited. -// -// Deprecated: use GenericVisitor or another visitor implementation -type Visitor = v1.Visitor //nolint:staticcheck - -// BeforeAndAfterVisitor wraps Visitor to provide hooks for being called before -// and after the AST has been visited. -// -// Deprecated: use GenericVisitor or another visitor implementation -type BeforeAndAfterVisitor = v1.BeforeAndAfterVisitor //nolint:staticcheck - -// Walk iterates the AST by calling the Visit function on the Visitor -// v for x before recursing. -// -// Deprecated: use GenericVisitor.Walk -func Walk(v Visitor, x any) { - v1.Walk(v, x) -} - -// WalkBeforeAndAfter iterates the AST by calling the Visit function on the -// Visitor v for x before recursing. -// -// Deprecated: use GenericVisitor.Walk -func WalkBeforeAndAfter(v BeforeAndAfterVisitor, x any) { - v1.WalkBeforeAndAfter(v, x) -} - -// WalkVars calls the function f on all vars under x. If the function f -// returns true, AST nodes under the last node will not be visited. -func WalkVars(x any, f func(Var) bool) { - v1.WalkVars(x, f) -} - -// WalkClosures calls the function f on all closures under x. If the function f -// returns true, AST nodes under the last node will not be visited. -func WalkClosures(x any, f func(any) bool) { - v1.WalkClosures(x, f) -} - -// WalkRefs calls the function f on all references under x. If the function f -// returns true, AST nodes under the last node will not be visited. -func WalkRefs(x any, f func(Ref) bool) { - v1.WalkRefs(x, f) -} - -// WalkTerms calls the function f on all terms under x. If the function f -// returns true, AST nodes under the last node will not be visited. -func WalkTerms(x any, f func(*Term) bool) { - v1.WalkTerms(x, f) -} - -// WalkWiths calls the function f on all with modifiers under x. If the function f -// returns true, AST nodes under the last node will not be visited. -func WalkWiths(x any, f func(*With) bool) { - v1.WalkWiths(x, f) -} - -// WalkExprs calls the function f on all expressions under x. If the function f -// returns true, AST nodes under the last node will not be visited. -func WalkExprs(x any, f func(*Expr) bool) { - v1.WalkExprs(x, f) -} - -// WalkBodies calls the function f on all bodies under x. If the function f -// returns true, AST nodes under the last node will not be visited. -func WalkBodies(x any, f func(Body) bool) { - v1.WalkBodies(x, f) -} - -// WalkRules calls the function f on all rules under x. If the function f -// returns true, AST nodes under the last node will not be visited. -func WalkRules(x any, f func(*Rule) bool) { - v1.WalkRules(x, f) -} - -// WalkNodes calls the function f on all nodes under x. If the function f -// returns true, AST nodes under the last node will not be visited. -func WalkNodes(x any, f func(Node) bool) { - v1.WalkNodes(x, f) -} - -// GenericVisitor provides a utility to walk over AST nodes using a -// closure. If the closure returns true, the visitor will not walk -// over AST nodes under x. -type GenericVisitor = v1.GenericVisitor - -// NewGenericVisitor returns a new GenericVisitor that will invoke the function -// f on AST nodes. -func NewGenericVisitor(f func(x any) bool) *GenericVisitor { - return v1.NewGenericVisitor(f) -} - -// BeforeAfterVisitor provides a utility to walk over AST nodes using -// closures. If the before closure returns true, the visitor will not -// walk over AST nodes under x. The after closure is invoked always -// after visiting a node. -type BeforeAfterVisitor = v1.BeforeAfterVisitor - -// NewBeforeAfterVisitor returns a new BeforeAndAfterVisitor that -// will invoke the functions before and after AST nodes. -func NewBeforeAfterVisitor(before func(x any) bool, after func(x any)) *BeforeAfterVisitor { - return v1.NewBeforeAfterVisitor(before, after) -} - -// VarVisitor walks AST nodes under a given node and collects all encountered -// variables. The collected variables can be controlled by specifying -// VarVisitorParams when creating the visitor. -type VarVisitor = v1.VarVisitor - -// VarVisitorParams contains settings for a VarVisitor. -type VarVisitorParams = v1.VarVisitorParams - -// NewVarVisitor returns a new VarVisitor object. -func NewVarVisitor() *VarVisitor { - return v1.NewVarVisitor() -} diff --git a/vendor/github.com/open-policy-agent/opa/bundle/bundle.go b/vendor/github.com/open-policy-agent/opa/bundle/bundle.go index 50ad97349a..1b416c706b 100644 --- a/vendor/github.com/open-policy-agent/opa/bundle/bundle.go +++ b/vendor/github.com/open-policy-agent/opa/bundle/bundle.go @@ -1,14 +1,27 @@ -// Copyright 2018 The OPA Authors. All rights reserved. +// Copyright 2026 The OPA Authors. All rights reserved. // Use of this source code is governed by an Apache2 // license that can be found in the LICENSE file. // Package bundle implements bundle loading. +// +// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. +// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. +// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. +// +// # Package bundle provide helpers that assist in creating the verification and signing key configuration +// +// # Package bundle provide helpers that assist in the creating a signed bundle +// +// Package bundle provide helpers that assist in the bundle signature verification process package bundle import ( + "context" "io" + "io/fs" "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/storage" v1 "github.com/open-policy-agent/opa/v1/bundle" ) @@ -132,3 +145,294 @@ func RootPathsOverlap(pathA string, pathB string) bool { func RootPathsContain(roots []string, path string) bool { return v1.RootPathsContain(roots, path) } + +// Descriptor contains information about a file and +// can be used to read the file contents. +type Descriptor = v1.Descriptor + +func NewDescriptor(url, path string, reader io.Reader) *Descriptor { + return v1.NewDescriptor(url, path, reader) +} + +type PathFormat = v1.PathFormat + +const ( + Chrooted = v1.Chrooted + SlashRooted = v1.SlashRooted + Passthrough = v1.Passthrough +) + +// DirectoryLoader defines an interface which can be used to load +// files from a directory by iterating over each one in the tree. +type DirectoryLoader = v1.DirectoryLoader + +// NewDirectoryLoader returns a basic DirectoryLoader implementation +// that will load files from a given root directory path. +func NewDirectoryLoader(root string) DirectoryLoader { + return v1.NewDirectoryLoader(root) +} + +// NewTarballLoader is deprecated. Use NewTarballLoaderWithBaseURL instead. +func NewTarballLoader(r io.Reader) DirectoryLoader { + return v1.NewTarballLoader(r) +} + +// NewTarballLoaderWithBaseURL returns a new DirectoryLoader that reads +// files out of a gzipped tar archive. The file URLs will be prefixed +// with the baseURL. +func NewTarballLoaderWithBaseURL(r io.Reader, baseURL string) DirectoryLoader { + return v1.NewTarballLoaderWithBaseURL(r, baseURL) +} + +func NewIterator(raw []Raw) storage.Iterator { + return v1.NewIterator(raw) +} + +// NewFSLoader returns a basic DirectoryLoader implementation +// that will load files from a fs.FS interface +func NewFSLoader(filesystem fs.FS) (DirectoryLoader, error) { + return v1.NewFSLoader(filesystem) +} + +// NewFSLoaderWithRoot returns a basic DirectoryLoader implementation +// that will load files from a fs.FS interface at the supplied root +func NewFSLoaderWithRoot(filesystem fs.FS, root string) DirectoryLoader { + return v1.NewFSLoaderWithRoot(filesystem, root) +} + +// HashingAlgorithm represents a subset of hashing algorithms implemented in Go +type HashingAlgorithm = v1.HashingAlgorithm + +// Supported values for HashingAlgorithm +const ( + MD5 = v1.MD5 + SHA1 = v1.SHA1 + SHA224 = v1.SHA224 + SHA256 = v1.SHA256 + SHA384 = v1.SHA384 + SHA512 = v1.SHA512 + SHA512224 = v1.SHA512224 + SHA512256 = v1.SHA512256 +) + +// SignatureHasher computes a signature digest for a file with (structured or unstructured) data and policy +type SignatureHasher = v1.SignatureHasher + +// NewSignatureHasher returns a signature hasher suitable for a particular hashing algorithm +func NewSignatureHasher(alg HashingAlgorithm) (SignatureHasher, error) { + return v1.NewSignatureHasher(alg) +} + +// KeyConfig holds the keys used to sign or verify bundles and tokens +// Moved to own package, alias kept for backwards compatibility +type KeyConfig = v1.KeyConfig + +// VerificationConfig represents the key configuration used to verify a signed bundle +type VerificationConfig = v1.VerificationConfig + +// NewVerificationConfig return a new VerificationConfig +func NewVerificationConfig(keys map[string]*KeyConfig, id, scope string, exclude []string) *VerificationConfig { + return v1.NewVerificationConfig(keys, id, scope, exclude) +} + +// SigningConfig represents the key configuration used to generate a signed bundle +type SigningConfig = v1.SigningConfig + +// NewSigningConfig return a new SigningConfig +func NewSigningConfig(key, alg, claimsPath string) *SigningConfig { + return v1.NewSigningConfig(key, alg, claimsPath) +} + +// Signer is the interface expected for implementations that generate bundle signatures. +type Signer v1.Signer + +// GenerateSignedToken will retrieve the Signer implementation based on the Plugin specified +// in SigningConfig, and call its implementation of GenerateSignedToken. The signer generates +// a signed token given the list of files to be included in the payload and the bundle +// signing config. The keyID if non-empty, represents the value for the "keyid" claim in the token. +func GenerateSignedToken(files []FileInfo, sc *SigningConfig, keyID string) (string, error) { + return v1.GenerateSignedToken(files, sc, keyID) +} + +// DefaultSigner is the default bundle signing implementation. It signs bundles by generating +// a JWT and signing it using a locally-accessible private key. +type DefaultSigner v1.DefaultSigner + +// GetSigner returns the Signer registered under the given id +func GetSigner(id string) (Signer, error) { + return v1.GetSigner(id) +} + +// RegisterSigner registers a Signer under the given id +func RegisterSigner(id string, s Signer) error { + return v1.RegisterSigner(id, s) +} + +// BundlesBasePath is the storage path used for storing bundle metadata +var BundlesBasePath = v1.BundlesBasePath + +// Note: As needed these helpers could be memoized. + +// ManifestStoragePath is the storage path used for the given named bundle manifest. +func ManifestStoragePath(name string) storage.Path { + return v1.ManifestStoragePath(name) +} + +// EtagStoragePath is the storage path used for the given named bundle etag. +func EtagStoragePath(name string) storage.Path { + return v1.EtagStoragePath(name) +} + +// ReadBundleNamesFromStore will return a list of bundle names which have had their metadata stored. +func ReadBundleNamesFromStore(ctx context.Context, store storage.Store, txn storage.Transaction) ([]string, error) { + return v1.ReadBundleNamesFromStore(ctx, store, txn) +} + +// WriteManifestToStore will write the manifest into the storage. This function is called when +// the bundle is activated. +func WriteManifestToStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string, manifest Manifest) error { + return v1.WriteManifestToStore(ctx, store, txn, name, manifest) +} + +// WriteEtagToStore will write the bundle etag into the storage. This function is called when the bundle is activated. +func WriteEtagToStore(ctx context.Context, store storage.Store, txn storage.Transaction, name, etag string) error { + return v1.WriteEtagToStore(ctx, store, txn, name, etag) +} + +// EraseManifestFromStore will remove the manifest from storage. This function is called +// when the bundle is deactivated. +func EraseManifestFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) error { + return v1.EraseManifestFromStore(ctx, store, txn, name) +} + +// ReadWasmModulesFromStore will write Wasm module resolver metadata from the store. +func ReadWasmModulesFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (map[string][]byte, error) { + return v1.ReadWasmModulesFromStore(ctx, store, txn, name) +} + +// ReadBundleRootsFromStore returns the roots in the specified bundle. +// If the bundle is not activated, this function will return +// storage NotFound error. +func ReadBundleRootsFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) ([]string, error) { + return v1.ReadBundleRootsFromStore(ctx, store, txn, name) +} + +// ReadBundleRevisionFromStore returns the revision in the specified bundle. +// If the bundle is not activated, this function will return +// storage NotFound error. +func ReadBundleRevisionFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (string, error) { + return v1.ReadBundleRevisionFromStore(ctx, store, txn, name) +} + +// ReadBundleMetadataFromStore returns the metadata in the specified bundle. +// If the bundle is not activated, this function will return +// storage NotFound error. +func ReadBundleMetadataFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (map[string]any, error) { + return v1.ReadBundleMetadataFromStore(ctx, store, txn, name) +} + +// ReadBundleEtagFromStore returns the etag for the specified bundle. +// If the bundle is not activated, this function will return +// storage NotFound error. +func ReadBundleEtagFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (string, error) { + return v1.ReadBundleEtagFromStore(ctx, store, txn, name) +} + +// ActivateOpts defines options for the Activate API call. +type ActivateOpts = v1.ActivateOpts + +// Activate the bundle(s) by loading into the given Store. This will load policies, data, and record +// the manifest in storage. The compiler provided will have had the polices compiled on it. +func Activate(opts *ActivateOpts) error { + return v1.Activate(setActivateDefaultRegoVersion(opts)) +} + +// DeactivateOpts defines options for the Deactivate API call +type DeactivateOpts = v1.DeactivateOpts + +// Deactivate the bundle(s). This will erase associated data, policies, and the manifest entry from the store. +func Deactivate(opts *DeactivateOpts) error { + return v1.Deactivate(setDeactivateDefaultRegoVersion(opts)) +} + +// LegacyWriteManifestToStore will write the bundle manifest to the older single (unnamed) bundle manifest location. +// +// Deprecated: Use WriteManifestToStore and named bundles instead. +func LegacyWriteManifestToStore(ctx context.Context, store storage.Store, txn storage.Transaction, manifest Manifest) error { + return v1.LegacyWriteManifestToStore(ctx, store, txn, manifest) +} + +// LegacyEraseManifestFromStore will erase the bundle manifest from the older single (unnamed) bundle manifest location. +// +// Deprecated: Use WriteManifestToStore and named bundles instead. +func LegacyEraseManifestFromStore(ctx context.Context, store storage.Store, txn storage.Transaction) error { + return v1.LegacyEraseManifestFromStore(ctx, store, txn) +} + +// LegacyReadRevisionFromStore will read the bundle manifest revision from the older single (unnamed) bundle manifest location. +// +// Deprecated: Use ReadBundleRevisionFromStore and named bundles instead. +func LegacyReadRevisionFromStore(ctx context.Context, store storage.Store, txn storage.Transaction) (string, error) { + return v1.LegacyReadRevisionFromStore(ctx, store, txn) +} + +// ActivateLegacy calls Activate for the bundles but will also write their manifest to the older unnamed store location. +// +// Deprecated: Use Activate with named bundles instead. +func ActivateLegacy(opts *ActivateOpts) error { + return v1.ActivateLegacy(opts) +} + +func setActivateDefaultRegoVersion(opts *ActivateOpts) *ActivateOpts { + if opts == nil { + return nil + } + + if opts.ParserOptions.RegoVersion == ast.RegoUndefined { + cpy := *opts + cpy.ParserOptions.RegoVersion = ast.DefaultRegoVersion + return &cpy + } + + return opts +} + +func setDeactivateDefaultRegoVersion(opts *DeactivateOpts) *DeactivateOpts { + if opts == nil { + return nil + } + + if opts.ParserOptions.RegoVersion == ast.RegoUndefined { + cpy := *opts + cpy.ParserOptions.RegoVersion = ast.DefaultRegoVersion + return &cpy + } + + return opts +} + +// Verifier is the interface expected for implementations that verify bundle signatures. +type Verifier v1.Verifier + +// VerifyBundleSignature will retrieve the Verifier implementation based +// on the Plugin specified in SignaturesConfig, and call its implementation +// of VerifyBundleSignature. VerifyBundleSignature verifies the bundle signature +// using the given public keys or secret. If a signature is verified, it keeps +// track of the files specified in the JWT payload +func VerifyBundleSignature(sc SignaturesConfig, bvc *VerificationConfig) (map[string]FileInfo, error) { + return v1.VerifyBundleSignature(sc, bvc) +} + +// DefaultVerifier is the default bundle verification implementation. It verifies bundles by checking +// the JWT signature using a locally-accessible public key. +type DefaultVerifier = v1.DefaultVerifier + +// GetVerifier returns the Verifier registered under the given id +func GetVerifier(id string) (Verifier, error) { + return v1.GetVerifier(id) +} + +// RegisterVerifier registers a Verifier under the given id +func RegisterVerifier(id string, v Verifier) error { + return v1.RegisterVerifier(id, v) +} diff --git a/vendor/github.com/open-policy-agent/opa/bundle/doc.go b/vendor/github.com/open-policy-agent/opa/bundle/doc.go deleted file mode 100644 index 7ec7c9b332..0000000000 --- a/vendor/github.com/open-policy-agent/opa/bundle/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2024 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. -// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. -// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. -package bundle diff --git a/vendor/github.com/open-policy-agent/opa/bundle/file.go b/vendor/github.com/open-policy-agent/opa/bundle/file.go deleted file mode 100644 index ccb7b23510..0000000000 --- a/vendor/github.com/open-policy-agent/opa/bundle/file.go +++ /dev/null @@ -1,50 +0,0 @@ -package bundle - -import ( - "io" - - "github.com/open-policy-agent/opa/storage" - v1 "github.com/open-policy-agent/opa/v1/bundle" -) - -// Descriptor contains information about a file and -// can be used to read the file contents. -type Descriptor = v1.Descriptor - -func NewDescriptor(url, path string, reader io.Reader) *Descriptor { - return v1.NewDescriptor(url, path, reader) -} - -type PathFormat = v1.PathFormat - -const ( - Chrooted = v1.Chrooted - SlashRooted = v1.SlashRooted - Passthrough = v1.Passthrough -) - -// DirectoryLoader defines an interface which can be used to load -// files from a directory by iterating over each one in the tree. -type DirectoryLoader = v1.DirectoryLoader - -// NewDirectoryLoader returns a basic DirectoryLoader implementation -// that will load files from a given root directory path. -func NewDirectoryLoader(root string) DirectoryLoader { - return v1.NewDirectoryLoader(root) -} - -// NewTarballLoader is deprecated. Use NewTarballLoaderWithBaseURL instead. -func NewTarballLoader(r io.Reader) DirectoryLoader { - return v1.NewTarballLoader(r) -} - -// NewTarballLoaderWithBaseURL returns a new DirectoryLoader that reads -// files out of a gzipped tar archive. The file URLs will be prefixed -// with the baseURL. -func NewTarballLoaderWithBaseURL(r io.Reader, baseURL string) DirectoryLoader { - return v1.NewTarballLoaderWithBaseURL(r, baseURL) -} - -func NewIterator(raw []Raw) storage.Iterator { - return v1.NewIterator(raw) -} diff --git a/vendor/github.com/open-policy-agent/opa/bundle/filefs.go b/vendor/github.com/open-policy-agent/opa/bundle/filefs.go deleted file mode 100644 index f6d559e8c2..0000000000 --- a/vendor/github.com/open-policy-agent/opa/bundle/filefs.go +++ /dev/null @@ -1,19 +0,0 @@ -package bundle - -import ( - "io/fs" - - v1 "github.com/open-policy-agent/opa/v1/bundle" -) - -// NewFSLoader returns a basic DirectoryLoader implementation -// that will load files from a fs.FS interface -func NewFSLoader(filesystem fs.FS) (DirectoryLoader, error) { - return v1.NewFSLoader(filesystem) -} - -// NewFSLoaderWithRoot returns a basic DirectoryLoader implementation -// that will load files from a fs.FS interface at the supplied root -func NewFSLoaderWithRoot(filesystem fs.FS, root string) DirectoryLoader { - return v1.NewFSLoaderWithRoot(filesystem, root) -} diff --git a/vendor/github.com/open-policy-agent/opa/bundle/hash.go b/vendor/github.com/open-policy-agent/opa/bundle/hash.go deleted file mode 100644 index d4cc601dea..0000000000 --- a/vendor/github.com/open-policy-agent/opa/bundle/hash.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2020 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package bundle - -import ( - v1 "github.com/open-policy-agent/opa/v1/bundle" -) - -// HashingAlgorithm represents a subset of hashing algorithms implemented in Go -type HashingAlgorithm = v1.HashingAlgorithm - -// Supported values for HashingAlgorithm -const ( - MD5 = v1.MD5 - SHA1 = v1.SHA1 - SHA224 = v1.SHA224 - SHA256 = v1.SHA256 - SHA384 = v1.SHA384 - SHA512 = v1.SHA512 - SHA512224 = v1.SHA512224 - SHA512256 = v1.SHA512256 -) - -// SignatureHasher computes a signature digest for a file with (structured or unstructured) data and policy -type SignatureHasher = v1.SignatureHasher - -// NewSignatureHasher returns a signature hasher suitable for a particular hashing algorithm -func NewSignatureHasher(alg HashingAlgorithm) (SignatureHasher, error) { - return v1.NewSignatureHasher(alg) -} diff --git a/vendor/github.com/open-policy-agent/opa/bundle/keys.go b/vendor/github.com/open-policy-agent/opa/bundle/keys.go deleted file mode 100644 index 99f9b0f165..0000000000 --- a/vendor/github.com/open-policy-agent/opa/bundle/keys.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2020 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -// Package bundle provide helpers that assist in creating the verification and signing key configuration -package bundle - -import ( - v1 "github.com/open-policy-agent/opa/v1/bundle" -) - -// KeyConfig holds the keys used to sign or verify bundles and tokens -// Moved to own package, alias kept for backwards compatibility -type KeyConfig = v1.KeyConfig - -// VerificationConfig represents the key configuration used to verify a signed bundle -type VerificationConfig = v1.VerificationConfig - -// NewVerificationConfig return a new VerificationConfig -func NewVerificationConfig(keys map[string]*KeyConfig, id, scope string, exclude []string) *VerificationConfig { - return v1.NewVerificationConfig(keys, id, scope, exclude) -} - -// SigningConfig represents the key configuration used to generate a signed bundle -type SigningConfig = v1.SigningConfig - -// NewSigningConfig return a new SigningConfig -func NewSigningConfig(key, alg, claimsPath string) *SigningConfig { - return v1.NewSigningConfig(key, alg, claimsPath) -} diff --git a/vendor/github.com/open-policy-agent/opa/bundle/sign.go b/vendor/github.com/open-policy-agent/opa/bundle/sign.go deleted file mode 100644 index 56e25eec9c..0000000000 --- a/vendor/github.com/open-policy-agent/opa/bundle/sign.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2020 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -// Package bundle provide helpers that assist in the creating a signed bundle -package bundle - -import ( - v1 "github.com/open-policy-agent/opa/v1/bundle" -) - -// Signer is the interface expected for implementations that generate bundle signatures. -type Signer v1.Signer - -// GenerateSignedToken will retrieve the Signer implementation based on the Plugin specified -// in SigningConfig, and call its implementation of GenerateSignedToken. The signer generates -// a signed token given the list of files to be included in the payload and the bundle -// signing config. The keyID if non-empty, represents the value for the "keyid" claim in the token. -func GenerateSignedToken(files []FileInfo, sc *SigningConfig, keyID string) (string, error) { - return v1.GenerateSignedToken(files, sc, keyID) -} - -// DefaultSigner is the default bundle signing implementation. It signs bundles by generating -// a JWT and signing it using a locally-accessible private key. -type DefaultSigner v1.DefaultSigner - -// GetSigner returns the Signer registered under the given id -func GetSigner(id string) (Signer, error) { - return v1.GetSigner(id) -} - -// RegisterSigner registers a Signer under the given id -func RegisterSigner(id string, s Signer) error { - return v1.RegisterSigner(id, s) -} diff --git a/vendor/github.com/open-policy-agent/opa/bundle/store.go b/vendor/github.com/open-policy-agent/opa/bundle/store.go deleted file mode 100644 index 85b8515eb2..0000000000 --- a/vendor/github.com/open-policy-agent/opa/bundle/store.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2019 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package bundle - -import ( - "context" - - "github.com/open-policy-agent/opa/ast" - "github.com/open-policy-agent/opa/storage" - v1 "github.com/open-policy-agent/opa/v1/bundle" -) - -// BundlesBasePath is the storage path used for storing bundle metadata -var BundlesBasePath = v1.BundlesBasePath - -// Note: As needed these helpers could be memoized. - -// ManifestStoragePath is the storage path used for the given named bundle manifest. -func ManifestStoragePath(name string) storage.Path { - return v1.ManifestStoragePath(name) -} - -// EtagStoragePath is the storage path used for the given named bundle etag. -func EtagStoragePath(name string) storage.Path { - return v1.EtagStoragePath(name) -} - -// ReadBundleNamesFromStore will return a list of bundle names which have had their metadata stored. -func ReadBundleNamesFromStore(ctx context.Context, store storage.Store, txn storage.Transaction) ([]string, error) { - return v1.ReadBundleNamesFromStore(ctx, store, txn) -} - -// WriteManifestToStore will write the manifest into the storage. This function is called when -// the bundle is activated. -func WriteManifestToStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string, manifest Manifest) error { - return v1.WriteManifestToStore(ctx, store, txn, name, manifest) -} - -// WriteEtagToStore will write the bundle etag into the storage. This function is called when the bundle is activated. -func WriteEtagToStore(ctx context.Context, store storage.Store, txn storage.Transaction, name, etag string) error { - return v1.WriteEtagToStore(ctx, store, txn, name, etag) -} - -// EraseManifestFromStore will remove the manifest from storage. This function is called -// when the bundle is deactivated. -func EraseManifestFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) error { - return v1.EraseManifestFromStore(ctx, store, txn, name) -} - -// ReadWasmModulesFromStore will write Wasm module resolver metadata from the store. -func ReadWasmModulesFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (map[string][]byte, error) { - return v1.ReadWasmModulesFromStore(ctx, store, txn, name) -} - -// ReadBundleRootsFromStore returns the roots in the specified bundle. -// If the bundle is not activated, this function will return -// storage NotFound error. -func ReadBundleRootsFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) ([]string, error) { - return v1.ReadBundleRootsFromStore(ctx, store, txn, name) -} - -// ReadBundleRevisionFromStore returns the revision in the specified bundle. -// If the bundle is not activated, this function will return -// storage NotFound error. -func ReadBundleRevisionFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (string, error) { - return v1.ReadBundleRevisionFromStore(ctx, store, txn, name) -} - -// ReadBundleMetadataFromStore returns the metadata in the specified bundle. -// If the bundle is not activated, this function will return -// storage NotFound error. -func ReadBundleMetadataFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (map[string]any, error) { - return v1.ReadBundleMetadataFromStore(ctx, store, txn, name) -} - -// ReadBundleEtagFromStore returns the etag for the specified bundle. -// If the bundle is not activated, this function will return -// storage NotFound error. -func ReadBundleEtagFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (string, error) { - return v1.ReadBundleEtagFromStore(ctx, store, txn, name) -} - -// ActivateOpts defines options for the Activate API call. -type ActivateOpts = v1.ActivateOpts - -// Activate the bundle(s) by loading into the given Store. This will load policies, data, and record -// the manifest in storage. The compiler provided will have had the polices compiled on it. -func Activate(opts *ActivateOpts) error { - return v1.Activate(setActivateDefaultRegoVersion(opts)) -} - -// DeactivateOpts defines options for the Deactivate API call -type DeactivateOpts = v1.DeactivateOpts - -// Deactivate the bundle(s). This will erase associated data, policies, and the manifest entry from the store. -func Deactivate(opts *DeactivateOpts) error { - return v1.Deactivate(setDeactivateDefaultRegoVersion(opts)) -} - -// LegacyWriteManifestToStore will write the bundle manifest to the older single (unnamed) bundle manifest location. -// -// Deprecated: Use WriteManifestToStore and named bundles instead. -func LegacyWriteManifestToStore(ctx context.Context, store storage.Store, txn storage.Transaction, manifest Manifest) error { - return v1.LegacyWriteManifestToStore(ctx, store, txn, manifest) -} - -// LegacyEraseManifestFromStore will erase the bundle manifest from the older single (unnamed) bundle manifest location. -// -// Deprecated: Use WriteManifestToStore and named bundles instead. -func LegacyEraseManifestFromStore(ctx context.Context, store storage.Store, txn storage.Transaction) error { - return v1.LegacyEraseManifestFromStore(ctx, store, txn) -} - -// LegacyReadRevisionFromStore will read the bundle manifest revision from the older single (unnamed) bundle manifest location. -// -// Deprecated: Use ReadBundleRevisionFromStore and named bundles instead. -func LegacyReadRevisionFromStore(ctx context.Context, store storage.Store, txn storage.Transaction) (string, error) { - return v1.LegacyReadRevisionFromStore(ctx, store, txn) -} - -// ActivateLegacy calls Activate for the bundles but will also write their manifest to the older unnamed store location. -// -// Deprecated: Use Activate with named bundles instead. -func ActivateLegacy(opts *ActivateOpts) error { - return v1.ActivateLegacy(opts) -} - -func setActivateDefaultRegoVersion(opts *ActivateOpts) *ActivateOpts { - if opts == nil { - return nil - } - - if opts.ParserOptions.RegoVersion == ast.RegoUndefined { - cpy := *opts - cpy.ParserOptions.RegoVersion = ast.DefaultRegoVersion - return &cpy - } - - return opts -} - -func setDeactivateDefaultRegoVersion(opts *DeactivateOpts) *DeactivateOpts { - if opts == nil { - return nil - } - - if opts.ParserOptions.RegoVersion == ast.RegoUndefined { - cpy := *opts - cpy.ParserOptions.RegoVersion = ast.DefaultRegoVersion - return &cpy - } - - return opts -} diff --git a/vendor/github.com/open-policy-agent/opa/bundle/verify.go b/vendor/github.com/open-policy-agent/opa/bundle/verify.go deleted file mode 100644 index ef2e1e32db..0000000000 --- a/vendor/github.com/open-policy-agent/opa/bundle/verify.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2020 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -// Package bundle provide helpers that assist in the bundle signature verification process -package bundle - -import ( - v1 "github.com/open-policy-agent/opa/v1/bundle" -) - -// Verifier is the interface expected for implementations that verify bundle signatures. -type Verifier v1.Verifier - -// VerifyBundleSignature will retrieve the Verifier implementation based -// on the Plugin specified in SignaturesConfig, and call its implementation -// of VerifyBundleSignature. VerifyBundleSignature verifies the bundle signature -// using the given public keys or secret. If a signature is verified, it keeps -// track of the files specified in the JWT payload -func VerifyBundleSignature(sc SignaturesConfig, bvc *VerificationConfig) (map[string]FileInfo, error) { - return v1.VerifyBundleSignature(sc, bvc) -} - -// DefaultVerifier is the default bundle verification implementation. It verifies bundles by checking -// the JWT signature using a locally-accessible public key. -type DefaultVerifier = v1.DefaultVerifier - -// GetVerifier returns the Verifier registered under the given id -func GetVerifier(id string) (Verifier, error) { - return v1.GetVerifier(id) -} - -// RegisterVerifier registers a Verifier under the given id -func RegisterVerifier(id string, v Verifier) error { - return v1.RegisterVerifier(id, v) -} diff --git a/vendor/github.com/open-policy-agent/opa/capabilities/capabilities.go b/vendor/github.com/open-policy-agent/opa/capabilities/capabilities.go index 5482720f7c..c2c35f744e 100644 --- a/vendor/github.com/open-policy-agent/opa/capabilities/capabilities.go +++ b/vendor/github.com/open-policy-agent/opa/capabilities/capabilities.go @@ -1,7 +1,10 @@ -// Copyright 2021 The OPA Authors. All rights reserved. +// Copyright 2026 The OPA Authors. All rights reserved. // Use of this source code is governed by an Apache2 // license that can be found in the LICENSE file. +// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. +// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. +// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. package capabilities import ( diff --git a/vendor/github.com/open-policy-agent/opa/capabilities/doc.go b/vendor/github.com/open-policy-agent/opa/capabilities/doc.go deleted file mode 100644 index 189c2e727a..0000000000 --- a/vendor/github.com/open-policy-agent/opa/capabilities/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2024 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. -// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. -// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. -package capabilities diff --git a/vendor/github.com/open-policy-agent/opa/capabilities/v1.20.0.json b/vendor/github.com/open-policy-agent/opa/capabilities/v1.20.0.json new file mode 100644 index 0000000000..ccecfa24b1 --- /dev/null +++ b/vendor/github.com/open-policy-agent/opa/capabilities/v1.20.0.json @@ -0,0 +1,5028 @@ +{ + "builtins": [ + { + "name": "abs", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "all", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "and", + "decl": { + "args": [ + { + "of": { + "type": "any" + }, + "type": "set" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "infix": "\u0026" + }, + { + "name": "any", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "array.concat", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "array.flatten", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "array.reverse", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "array.slice", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "assign", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": ":=" + }, + { + "name": "base64.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "base64url.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64url.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64url.encode_no_pad", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "bits.and", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.lsh", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.negate", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.or", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.rsh", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.xor", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "cast_array", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_boolean", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_null", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "null" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_object", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_set", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_string", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "ceil", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "concat", + "decl": { + "args": [ + { + "type": "string" + }, + { + "of": [ + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "contains", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "count", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.equal", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.md5", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.sha1", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.sha256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.sha512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.md5", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.parse_private_keys", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.sha1", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.sha256", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_and_verify_certificates", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_and_verify_certificates_with_options", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_certificate_request", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_certificates", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_keypair", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_rsa_private_key", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "div", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "/" + }, + { + "name": "endswith", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "eq", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "=" + }, + { + "name": "equal", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "==" + }, + { + "name": "floor", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "format_int", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "glob.match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "of": [ + { + "type": "null" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + } + ], + "type": "any" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "glob.quote_meta", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "graph.reachable", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "graph.reachable_paths", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "graphql.is_valid", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "graphql.parse", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "graphql.parse_and_verify", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "graphql.parse_query", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "graphql.parse_schema", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "graphql.schema_is_valid", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "gt", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003e" + }, + { + "name": "gte", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003e=" + }, + { + "name": "hex.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "hex.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "http.send", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "indexof", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "indexof_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "internal.member_2", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "in" + }, + { + "name": "internal.member_3", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "in" + }, + { + "name": "internal.print", + "decl": { + "args": [ + { + "dynamic": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "array" + } + ], + "type": "function" + } + }, + { + "name": "internal.template_string", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "internal.test_case", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "function" + } + }, + { + "name": "intersection", + "decl": { + "args": [ + { + "of": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "io.jwt.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "static": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "type": "string" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "io.jwt.decode_verify", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "array" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "io.jwt.encode_sign", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "io.jwt.encode_sign_raw", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "io.jwt.verify_eddsa", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_es256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_es384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_es512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_hs256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_hs384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_hs512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_ps256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_ps384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_ps512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_rs256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_rs384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_rs512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_array", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_boolean", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_null", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_number", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_object", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_set", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_string", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "json.filter", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "json.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "json.marshal", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "json.marshal_with_options", + "decl": { + "args": [ + { + "type": "any" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "static": [ + { + "key": "indent", + "value": { + "type": "string" + } + }, + { + "key": "prefix", + "value": { + "type": "string" + } + }, + { + "key": "pretty", + "value": { + "type": "boolean" + } + } + ], + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "json.match_schema", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "static": [ + { + "key": "desc", + "value": { + "type": "string" + } + }, + { + "key": "error", + "value": { + "type": "string" + } + }, + { + "key": "field", + "value": { + "type": "string" + } + }, + { + "key": "type", + "value": { + "type": "string" + } + } + ], + "type": "object" + }, + "type": "array" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "json.patch", + "decl": { + "args": [ + { + "type": "any" + }, + { + "dynamic": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "static": [ + { + "key": "op", + "value": { + "type": "string" + } + }, + { + "key": "path", + "value": { + "type": "any" + } + } + ], + "type": "object" + }, + "type": "array" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "json.remove", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "json.unmarshal", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "json.verify_schema", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "of": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "type": "any" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "lower", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "lt", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003c" + }, + { + "name": "lte", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003c=" + }, + { + "name": "max", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "min", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "minus", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "number" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": [ + { + "type": "number" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + "type": "function" + }, + "infix": "-" + }, + { + "name": "mul", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "*" + }, + { + "name": "neq", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "!=" + }, + { + "name": "net.cidr_contains", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "net.cidr_contains_matches", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "static": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "type": "array" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "net.cidr_expand", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "of": { + "type": "string" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "net.cidr_intersects", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "net.cidr_is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "net.cidr_merge", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "of": [ + { + "type": "string" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "type": "string" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "net.cidr_overlap", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "net.lookup_ip_addr", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "of": { + "type": "string" + }, + "type": "set" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "numbers.range", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "numbers.range_step", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "object.filter", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "object.get", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "object.keys", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "object.remove", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "object.subset", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "object.union", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "object.union_n", + "decl": { + "args": [ + { + "dynamic": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "opa.runtime", + "decl": { + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "or", + "decl": { + "args": [ + { + "of": { + "type": "any" + }, + "type": "set" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "infix": "|" + }, + { + "name": "plus", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "+" + }, + { + "name": "print", + "decl": { + "type": "function", + "variadic": { + "type": "any" + } + } + }, + { + "name": "product", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + { + "of": { + "type": "number" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "providers.aws.sign_req", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "rand.intn", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "re_match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "regex.find_all_string_submatch_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "regex.find_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "regex.globs_match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "regex.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "regex.match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "regex.replace", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "regex.split", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "regex.template_match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "rego.metadata.chain", + "decl": { + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "rego.metadata.rule", + "decl": { + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "rego.parse_module", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "rem", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "%" + }, + { + "name": "replace", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "round", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "semver.compare", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "semver.is_valid", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "set_diff", + "decl": { + "args": [ + { + "of": { + "type": "any" + }, + "type": "set" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "sort", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "split", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "sprintf", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "startswith", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "strings.any_prefix_match", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "strings.any_suffix_match", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "strings.count", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "strings.render_template", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "strings.replace_n", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "strings.reverse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "strings.split_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "substring", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "sum", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + { + "of": { + "type": "number" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.add_date", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.clock", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "time.date", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "time.diff", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "time.format", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "time.now_ns", + "decl": { + "result": { + "type": "number" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "time.parse_duration_ns", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.parse_ns", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.parse_rfc3339_ns", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.weekday", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "to_number", + "decl": { + "args": [ + { + "of": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "trace", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "trim", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_left", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_prefix", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_right", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_space", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_suffix", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "type_name", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "union", + "decl": { + "args": [ + { + "of": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "units.parse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "units.parse_bytes", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "upper", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "uri.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "uri.parse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "urlquery.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "urlquery.decode_object", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "dynamic": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "urlquery.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "urlquery.encode_object", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "uuid.parse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "uuid.rfc4122", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "walk", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "static": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "type": "any" + } + ], + "type": "array" + }, + "type": "function" + }, + "relation": true + }, + { + "name": "yaml.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "yaml.marshal", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "yaml.unmarshal", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + } + ], + "future_keywords": [ + "and", + "not", + "or" + ], + "wasm_abi_versions": [ + { + "version": 1, + "minor_version": 1 + }, + { + "version": 1, + "minor_version": 2 + } + ], + "features": [ + "keywords_in_refs", + "rego_v1", + "template_strings" + ] +} diff --git a/vendor/github.com/open-policy-agent/opa/capabilities/v1.20.1.json b/vendor/github.com/open-policy-agent/opa/capabilities/v1.20.1.json new file mode 100644 index 0000000000..ccecfa24b1 --- /dev/null +++ b/vendor/github.com/open-policy-agent/opa/capabilities/v1.20.1.json @@ -0,0 +1,5028 @@ +{ + "builtins": [ + { + "name": "abs", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "all", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "and", + "decl": { + "args": [ + { + "of": { + "type": "any" + }, + "type": "set" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "infix": "\u0026" + }, + { + "name": "any", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "array.concat", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "array.flatten", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "array.reverse", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "array.slice", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "assign", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": ":=" + }, + { + "name": "base64.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "base64url.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64url.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64url.encode_no_pad", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "bits.and", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.lsh", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.negate", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.or", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.rsh", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.xor", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "cast_array", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_boolean", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_null", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "null" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_object", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_set", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_string", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "ceil", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "concat", + "decl": { + "args": [ + { + "type": "string" + }, + { + "of": [ + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "contains", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "count", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.equal", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.md5", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.sha1", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.sha256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.sha512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.md5", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.parse_private_keys", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.sha1", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.sha256", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_and_verify_certificates", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_and_verify_certificates_with_options", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_certificate_request", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_certificates", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_keypair", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_rsa_private_key", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "div", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "/" + }, + { + "name": "endswith", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "eq", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "=" + }, + { + "name": "equal", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "==" + }, + { + "name": "floor", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "format_int", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "glob.match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "of": [ + { + "type": "null" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + } + ], + "type": "any" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "glob.quote_meta", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "graph.reachable", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "graph.reachable_paths", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "graphql.is_valid", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "graphql.parse", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "graphql.parse_and_verify", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "graphql.parse_query", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "graphql.parse_schema", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "graphql.schema_is_valid", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "gt", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003e" + }, + { + "name": "gte", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003e=" + }, + { + "name": "hex.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "hex.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "http.send", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "indexof", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "indexof_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "internal.member_2", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "in" + }, + { + "name": "internal.member_3", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "in" + }, + { + "name": "internal.print", + "decl": { + "args": [ + { + "dynamic": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "array" + } + ], + "type": "function" + } + }, + { + "name": "internal.template_string", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "internal.test_case", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "function" + } + }, + { + "name": "intersection", + "decl": { + "args": [ + { + "of": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "io.jwt.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "static": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "type": "string" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "io.jwt.decode_verify", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "array" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "io.jwt.encode_sign", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "io.jwt.encode_sign_raw", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "io.jwt.verify_eddsa", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_es256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_es384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_es512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_hs256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_hs384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_hs512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_ps256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_ps384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_ps512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_rs256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_rs384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_rs512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_array", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_boolean", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_null", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_number", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_object", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_set", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_string", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "json.filter", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "json.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "json.marshal", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "json.marshal_with_options", + "decl": { + "args": [ + { + "type": "any" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "static": [ + { + "key": "indent", + "value": { + "type": "string" + } + }, + { + "key": "prefix", + "value": { + "type": "string" + } + }, + { + "key": "pretty", + "value": { + "type": "boolean" + } + } + ], + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "json.match_schema", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "static": [ + { + "key": "desc", + "value": { + "type": "string" + } + }, + { + "key": "error", + "value": { + "type": "string" + } + }, + { + "key": "field", + "value": { + "type": "string" + } + }, + { + "key": "type", + "value": { + "type": "string" + } + } + ], + "type": "object" + }, + "type": "array" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "json.patch", + "decl": { + "args": [ + { + "type": "any" + }, + { + "dynamic": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "static": [ + { + "key": "op", + "value": { + "type": "string" + } + }, + { + "key": "path", + "value": { + "type": "any" + } + } + ], + "type": "object" + }, + "type": "array" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "json.remove", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "json.unmarshal", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "json.verify_schema", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "of": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "type": "any" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "lower", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "lt", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003c" + }, + { + "name": "lte", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003c=" + }, + { + "name": "max", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "min", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "minus", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "number" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": [ + { + "type": "number" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + "type": "function" + }, + "infix": "-" + }, + { + "name": "mul", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "*" + }, + { + "name": "neq", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "!=" + }, + { + "name": "net.cidr_contains", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "net.cidr_contains_matches", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "static": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "type": "array" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "net.cidr_expand", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "of": { + "type": "string" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "net.cidr_intersects", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "net.cidr_is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "net.cidr_merge", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "of": [ + { + "type": "string" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "type": "string" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "net.cidr_overlap", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "net.lookup_ip_addr", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "of": { + "type": "string" + }, + "type": "set" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "numbers.range", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "numbers.range_step", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "object.filter", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "object.get", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "object.keys", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "object.remove", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "object.subset", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "object.union", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "object.union_n", + "decl": { + "args": [ + { + "dynamic": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "opa.runtime", + "decl": { + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "or", + "decl": { + "args": [ + { + "of": { + "type": "any" + }, + "type": "set" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "infix": "|" + }, + { + "name": "plus", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "+" + }, + { + "name": "print", + "decl": { + "type": "function", + "variadic": { + "type": "any" + } + } + }, + { + "name": "product", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + { + "of": { + "type": "number" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "providers.aws.sign_req", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "rand.intn", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "re_match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "regex.find_all_string_submatch_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "regex.find_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "regex.globs_match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "regex.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "regex.match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "regex.replace", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "regex.split", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "regex.template_match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "rego.metadata.chain", + "decl": { + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "rego.metadata.rule", + "decl": { + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "rego.parse_module", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "rem", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "%" + }, + { + "name": "replace", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "round", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "semver.compare", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "semver.is_valid", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "set_diff", + "decl": { + "args": [ + { + "of": { + "type": "any" + }, + "type": "set" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "sort", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "split", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "sprintf", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "startswith", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "strings.any_prefix_match", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "strings.any_suffix_match", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "strings.count", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "strings.render_template", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "strings.replace_n", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "strings.reverse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "strings.split_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "substring", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "sum", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + { + "of": { + "type": "number" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.add_date", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.clock", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "time.date", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "time.diff", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "time.format", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "time.now_ns", + "decl": { + "result": { + "type": "number" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "time.parse_duration_ns", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.parse_ns", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.parse_rfc3339_ns", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.weekday", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "to_number", + "decl": { + "args": [ + { + "of": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "trace", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "trim", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_left", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_prefix", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_right", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_space", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_suffix", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "type_name", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "union", + "decl": { + "args": [ + { + "of": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "units.parse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "units.parse_bytes", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "upper", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "uri.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "uri.parse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "urlquery.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "urlquery.decode_object", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "dynamic": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "urlquery.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "urlquery.encode_object", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "uuid.parse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "uuid.rfc4122", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "walk", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "static": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "type": "any" + } + ], + "type": "array" + }, + "type": "function" + }, + "relation": true + }, + { + "name": "yaml.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "yaml.marshal", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "yaml.unmarshal", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + } + ], + "future_keywords": [ + "and", + "not", + "or" + ], + "wasm_abi_versions": [ + { + "version": 1, + "minor_version": 1 + }, + { + "version": 1, + "minor_version": 2 + } + ], + "features": [ + "keywords_in_refs", + "rego_v1", + "template_strings" + ] +} diff --git a/vendor/github.com/open-policy-agent/opa/capabilities/v1.20.2.json b/vendor/github.com/open-policy-agent/opa/capabilities/v1.20.2.json new file mode 100644 index 0000000000..ccecfa24b1 --- /dev/null +++ b/vendor/github.com/open-policy-agent/opa/capabilities/v1.20.2.json @@ -0,0 +1,5028 @@ +{ + "builtins": [ + { + "name": "abs", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "all", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "and", + "decl": { + "args": [ + { + "of": { + "type": "any" + }, + "type": "set" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "infix": "\u0026" + }, + { + "name": "any", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "array.concat", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "array.flatten", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "array.reverse", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "array.slice", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "assign", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": ":=" + }, + { + "name": "base64.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "base64url.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64url.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64url.encode_no_pad", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "bits.and", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.lsh", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.negate", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.or", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.rsh", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.xor", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "cast_array", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_boolean", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_null", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "null" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_object", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_set", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_string", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "ceil", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "concat", + "decl": { + "args": [ + { + "type": "string" + }, + { + "of": [ + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "contains", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "count", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.equal", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.md5", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.sha1", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.sha256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.sha512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.md5", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.parse_private_keys", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.sha1", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.sha256", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_and_verify_certificates", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_and_verify_certificates_with_options", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_certificate_request", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_certificates", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_keypair", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_rsa_private_key", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "div", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "/" + }, + { + "name": "endswith", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "eq", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "=" + }, + { + "name": "equal", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "==" + }, + { + "name": "floor", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "format_int", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "glob.match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "of": [ + { + "type": "null" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + } + ], + "type": "any" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "glob.quote_meta", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "graph.reachable", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "graph.reachable_paths", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "graphql.is_valid", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "graphql.parse", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "graphql.parse_and_verify", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "graphql.parse_query", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "graphql.parse_schema", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "graphql.schema_is_valid", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "gt", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003e" + }, + { + "name": "gte", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003e=" + }, + { + "name": "hex.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "hex.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "http.send", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "indexof", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "indexof_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "internal.member_2", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "in" + }, + { + "name": "internal.member_3", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "in" + }, + { + "name": "internal.print", + "decl": { + "args": [ + { + "dynamic": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "array" + } + ], + "type": "function" + } + }, + { + "name": "internal.template_string", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "internal.test_case", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "function" + } + }, + { + "name": "intersection", + "decl": { + "args": [ + { + "of": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "io.jwt.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "static": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "type": "string" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "io.jwt.decode_verify", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "array" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "io.jwt.encode_sign", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "io.jwt.encode_sign_raw", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "io.jwt.verify_eddsa", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_es256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_es384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_es512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_hs256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_hs384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_hs512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_ps256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_ps384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_ps512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_rs256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_rs384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_rs512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_array", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_boolean", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_null", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_number", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_object", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_set", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_string", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "json.filter", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "json.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "json.marshal", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "json.marshal_with_options", + "decl": { + "args": [ + { + "type": "any" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "static": [ + { + "key": "indent", + "value": { + "type": "string" + } + }, + { + "key": "prefix", + "value": { + "type": "string" + } + }, + { + "key": "pretty", + "value": { + "type": "boolean" + } + } + ], + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "json.match_schema", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "static": [ + { + "key": "desc", + "value": { + "type": "string" + } + }, + { + "key": "error", + "value": { + "type": "string" + } + }, + { + "key": "field", + "value": { + "type": "string" + } + }, + { + "key": "type", + "value": { + "type": "string" + } + } + ], + "type": "object" + }, + "type": "array" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "json.patch", + "decl": { + "args": [ + { + "type": "any" + }, + { + "dynamic": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "static": [ + { + "key": "op", + "value": { + "type": "string" + } + }, + { + "key": "path", + "value": { + "type": "any" + } + } + ], + "type": "object" + }, + "type": "array" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "json.remove", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "json.unmarshal", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "json.verify_schema", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "of": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "type": "any" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "lower", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "lt", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003c" + }, + { + "name": "lte", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003c=" + }, + { + "name": "max", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "min", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "minus", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "number" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": [ + { + "type": "number" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + "type": "function" + }, + "infix": "-" + }, + { + "name": "mul", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "*" + }, + { + "name": "neq", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "!=" + }, + { + "name": "net.cidr_contains", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "net.cidr_contains_matches", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "static": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "type": "array" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "net.cidr_expand", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "of": { + "type": "string" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "net.cidr_intersects", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "net.cidr_is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "net.cidr_merge", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "of": [ + { + "type": "string" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "type": "string" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "net.cidr_overlap", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "net.lookup_ip_addr", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "of": { + "type": "string" + }, + "type": "set" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "numbers.range", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "numbers.range_step", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "object.filter", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "object.get", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "object.keys", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "object.remove", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "object.subset", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "object.union", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "object.union_n", + "decl": { + "args": [ + { + "dynamic": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "opa.runtime", + "decl": { + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "or", + "decl": { + "args": [ + { + "of": { + "type": "any" + }, + "type": "set" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "infix": "|" + }, + { + "name": "plus", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "+" + }, + { + "name": "print", + "decl": { + "type": "function", + "variadic": { + "type": "any" + } + } + }, + { + "name": "product", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + { + "of": { + "type": "number" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "providers.aws.sign_req", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "rand.intn", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "re_match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "regex.find_all_string_submatch_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "regex.find_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "regex.globs_match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "regex.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "regex.match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "regex.replace", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "regex.split", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "regex.template_match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "rego.metadata.chain", + "decl": { + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "rego.metadata.rule", + "decl": { + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "rego.parse_module", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "rem", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "%" + }, + { + "name": "replace", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "round", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "semver.compare", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "semver.is_valid", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "set_diff", + "decl": { + "args": [ + { + "of": { + "type": "any" + }, + "type": "set" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "sort", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "split", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "sprintf", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "startswith", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "strings.any_prefix_match", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "strings.any_suffix_match", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "strings.count", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "strings.render_template", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "strings.replace_n", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "strings.reverse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "strings.split_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "substring", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "sum", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + { + "of": { + "type": "number" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.add_date", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.clock", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "time.date", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "time.diff", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "time.format", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "time.now_ns", + "decl": { + "result": { + "type": "number" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "time.parse_duration_ns", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.parse_ns", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.parse_rfc3339_ns", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.weekday", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "to_number", + "decl": { + "args": [ + { + "of": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "trace", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "trim", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_left", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_prefix", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_right", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_space", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_suffix", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "type_name", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "union", + "decl": { + "args": [ + { + "of": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "units.parse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "units.parse_bytes", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "upper", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "uri.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "uri.parse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "urlquery.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "urlquery.decode_object", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "dynamic": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "urlquery.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "urlquery.encode_object", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "uuid.parse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "uuid.rfc4122", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "walk", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "static": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "type": "any" + } + ], + "type": "array" + }, + "type": "function" + }, + "relation": true + }, + { + "name": "yaml.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "yaml.marshal", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "yaml.unmarshal", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + } + ], + "future_keywords": [ + "and", + "not", + "or" + ], + "wasm_abi_versions": [ + { + "version": 1, + "minor_version": 1 + }, + { + "version": 1, + "minor_version": 2 + } + ], + "features": [ + "keywords_in_refs", + "rego_v1", + "template_strings" + ] +} diff --git a/vendor/github.com/open-policy-agent/opa/capabilities/v1.21.0.json b/vendor/github.com/open-policy-agent/opa/capabilities/v1.21.0.json new file mode 100644 index 0000000000..73f2fe4b6d --- /dev/null +++ b/vendor/github.com/open-policy-agent/opa/capabilities/v1.21.0.json @@ -0,0 +1,5030 @@ +{ + "builtins": [ + { + "name": "abs", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "all", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "and", + "decl": { + "args": [ + { + "of": { + "type": "any" + }, + "type": "set" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "infix": "\u0026" + }, + { + "name": "any", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "array.concat", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "array.flatten", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "array.reverse", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "array.slice", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "assign", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": ":=" + }, + { + "name": "base64.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "base64url.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64url.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "base64url.encode_no_pad", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "bits.and", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.lsh", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.negate", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.or", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.rsh", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "bits.xor", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "cast_array", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_boolean", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_null", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "null" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_object", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_set", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "cast_string", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "ceil", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "concat", + "decl": { + "args": [ + { + "type": "string" + }, + { + "of": [ + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "contains", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "count", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.equal", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.md5", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.sha1", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.sha256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.hmac.sha512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.md5", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.parse_private_keys", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.sha1", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.sha256", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_and_verify_certificates", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_and_verify_certificates_with_options", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_certificate_request", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_certificates", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_keypair", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "crypto.x509.parse_rsa_private_key", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "div", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "/" + }, + { + "name": "endswith", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "eq", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "=" + }, + { + "name": "equal", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "==" + }, + { + "name": "floor", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "format_int", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "glob.match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "of": [ + { + "type": "null" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + } + ], + "type": "any" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "glob.quote_meta", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "graph.reachable", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "graph.reachable_paths", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "graphql.is_valid", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "graphql.parse", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "graphql.parse_and_verify", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "graphql.parse_query", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "graphql.parse_schema", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "graphql.schema_is_valid", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "gt", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003e" + }, + { + "name": "gte", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003e=" + }, + { + "name": "hex.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "hex.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "http.send", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "indexof", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "indexof_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "internal.member_2", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "in" + }, + { + "name": "internal.member_3", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "in" + }, + { + "name": "internal.print", + "decl": { + "args": [ + { + "dynamic": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "array" + } + ], + "type": "function" + } + }, + { + "name": "internal.template_string", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "internal.test_case", + "decl": { + "args": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "function" + } + }, + { + "name": "intersection", + "decl": { + "args": [ + { + "of": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "io.jwt.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "static": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "type": "string" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "io.jwt.decode_verify", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "array" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "io.jwt.encode_sign", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "io.jwt.encode_sign_raw", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "io.jwt.verify_eddsa", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_es256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_es384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_es512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_hs256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_hs384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_hs512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_ps256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_ps384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_ps512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_rs256", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_rs384", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "io.jwt.verify_rs512", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_array", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_boolean", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_null", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_number", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_object", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_set", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "is_string", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "json.filter", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "json.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "json.marshal", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "json.marshal_with_options", + "decl": { + "args": [ + { + "type": "any" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "static": [ + { + "key": "indent", + "value": { + "type": "string" + } + }, + { + "key": "prefix", + "value": { + "type": "string" + } + }, + { + "key": "pretty", + "value": { + "type": "boolean" + } + } + ], + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "json.match_schema", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "dynamic": { + "static": [ + { + "key": "desc", + "value": { + "type": "string" + } + }, + { + "key": "error", + "value": { + "type": "string" + } + }, + { + "key": "field", + "value": { + "type": "string" + } + }, + { + "key": "type", + "value": { + "type": "string" + } + } + ], + "type": "object" + }, + "type": "array" + } + ], + "type": "array" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "json.patch", + "decl": { + "args": [ + { + "type": "any" + }, + { + "dynamic": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "static": [ + { + "key": "op", + "value": { + "type": "string" + } + }, + { + "key": "path", + "value": { + "type": "any" + } + } + ], + "type": "object" + }, + "type": "array" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "json.remove", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "json.unmarshal", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "json.verify_schema", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "boolean" + }, + { + "of": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "type": "any" + } + ], + "type": "array" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "lower", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "lt", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003c" + }, + { + "name": "lte", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "\u003c=" + }, + { + "name": "max", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "min", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "minus", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "number" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": [ + { + "type": "number" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + "type": "function" + }, + "infix": "-" + }, + { + "name": "mul", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "*" + }, + { + "name": "neq", + "decl": { + "args": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "infix": "!=" + }, + { + "name": "net.cidr_contains", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "net.cidr_contains_matches", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "static": [ + { + "type": "any" + }, + { + "type": "any" + } + ], + "type": "array" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "net.cidr_expand", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "of": { + "type": "string" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "net.cidr_intersects", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "net.cidr_is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "net.cidr_merge", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "of": [ + { + "type": "string" + } + ], + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "of": { + "type": "string" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "net.cidr_overlap", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "net.lookup_ip_addr", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "of": { + "type": "string" + }, + "type": "set" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "numbers.range", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "numbers.range_step", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "object.filter", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "object.get", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "type": "any" + }, + { + "type": "any" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "object.keys", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "object.remove", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "object.subset", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "object.union", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "object.union_n", + "decl": { + "args": [ + { + "dynamic": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "array" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "opa.runtime", + "decl": { + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "or", + "decl": { + "args": [ + { + "of": { + "type": "any" + }, + "type": "set" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "infix": "|" + }, + { + "name": "plus", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "+" + }, + { + "name": "print", + "decl": { + "type": "function", + "variadic": { + "type": "any" + } + } + }, + { + "name": "product", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + { + "of": { + "type": "number" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "providers.aws.sign_req", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "key": { + "type": "any" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "rand.intn", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "re_match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "regex.find_all_string_submatch_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "regex.find_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "regex.globs_match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "regex.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "regex.match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "regex.replace", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "regex.split", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "regex.template_match", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "rego.metadata.chain", + "decl": { + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "rego.metadata.rule", + "decl": { + "result": { + "type": "any" + }, + "type": "function" + } + }, + { + "name": "rego.parse_module", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "rem", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + }, + "infix": "%" + }, + { + "name": "replace", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "round", + "decl": { + "args": [ + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "semver.compare", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "semver.is_valid", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "set_diff", + "decl": { + "args": [ + { + "of": { + "type": "any" + }, + "type": "set" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + }, + "deprecated": true + }, + { + "name": "sort", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "of": { + "type": "any" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "split", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "sprintf", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "any" + }, + "type": "array" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "startswith", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "strings.any_prefix_match", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "strings.any_suffix_match", + "decl": { + "args": [ + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "strings.count", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "strings.render_template", + "decl": { + "args": [ + { + "type": "string" + }, + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "strings.replace_n", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "strings.reverse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "strings.split_n", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "number" + } + ], + "result": { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + "type": "function" + } + }, + { + "name": "substring", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "sum", + "decl": { + "args": [ + { + "of": [ + { + "dynamic": { + "type": "number" + }, + "type": "array" + }, + { + "of": { + "type": "number" + }, + "type": "set" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.add_date", + "decl": { + "args": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.clock", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "time.date", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "time.diff", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + }, + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "static": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "type": "array" + }, + "type": "function" + } + }, + { + "name": "time.format", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "time.now_ns", + "decl": { + "result": { + "type": "number" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "time.parse_duration_ns", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.parse_ns", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.parse_rfc3339_ns", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "time.weekday", + "decl": { + "args": [ + { + "of": [ + { + "type": "number" + }, + { + "static": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "array" + } + ], + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "to_number", + "decl": { + "args": [ + { + "of": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ], + "type": "any" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "trace", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "trim", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_left", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_prefix", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_right", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_space", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "trim_suffix", + "decl": { + "args": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "type_name", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "union", + "decl": { + "args": [ + { + "of": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "set" + } + ], + "result": { + "of": { + "type": "any" + }, + "type": "set" + }, + "type": "function" + } + }, + { + "name": "units.parse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "units.parse_bytes", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "number" + }, + "type": "function" + } + }, + { + "name": "upper", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "uri.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "uri.parse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "urlquery.decode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "urlquery.decode_object", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "dynamic": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "urlquery.encode", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "urlquery.encode_object", + "decl": { + "args": [ + { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "of": [ + { + "type": "string" + }, + { + "dynamic": { + "type": "string" + }, + "type": "array" + }, + { + "of": { + "type": "string" + }, + "type": "set" + } + ], + "type": "any" + } + }, + "type": "object" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "uuid.parse", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "type": "any" + } + }, + "type": "object" + }, + "type": "function" + } + }, + { + "name": "uuid.rfc4122", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "string" + }, + "type": "function" + }, + "nondeterministic": true + }, + { + "name": "walk", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "static": [ + { + "dynamic": { + "type": "any" + }, + "type": "array" + }, + { + "type": "any" + } + ], + "type": "array" + }, + "type": "function" + }, + "relation": true + }, + { + "name": "yaml.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, + { + "name": "yaml.marshal", + "decl": { + "args": [ + { + "type": "any" + } + ], + "result": { + "type": "string" + }, + "type": "function" + } + }, + { + "name": "yaml.unmarshal", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "any" + }, + "type": "function" + } + } + ], + "future_keywords": [ + "and", + "not", + "or" + ], + "wasm_abi_versions": [ + { + "version": 1, + "minor_version": 1 + }, + { + "version": 1, + "minor_version": 2 + } + ], + "features": [ + "keywords_in_refs", + "rego_v1", + "template_strings" + ] +} diff --git a/vendor/github.com/open-policy-agent/opa/internal/cidr/merge/merge.go b/vendor/github.com/open-policy-agent/opa/internal/cidr/merge/merge.go index 85695b1292..e92c8d485c 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/cidr/merge/merge.go +++ b/vendor/github.com/open-policy-agent/opa/internal/cidr/merge/merge.go @@ -22,6 +22,7 @@ import ( "encoding/binary" "math/big" "net" + "slices" ) const ( @@ -61,7 +62,7 @@ func RangeToCIDRs(firstIP, lastIP net.IP) []*net.IPNet { } else { bitLen = ipv6BitLen } - _, _, right := partitionCIDR(spanningCIDR, net.IPNet{IP: prevFirstRangeIP, Mask: net.CIDRMask(bitLen, bitLen)}) + _, right := partitionCIDR(spanningCIDR, net.IPNet{IP: prevFirstRangeIP, Mask: net.CIDRMask(bitLen, bitLen)}) // Append all CIDRs but the first, as this CIDR includes the upper // bound of the spanning CIDR, which we still need to partition on. @@ -84,7 +85,7 @@ func RangeToCIDRs(firstIP, lastIP net.IP) []*net.IPNet { } else { bitLen = ipv6BitLen } - left, _, _ := partitionCIDR(spanningCIDR, net.IPNet{IP: nextFirstRangeIP, Mask: net.CIDRMask(bitLen, bitLen)}) + left, _ := partitionCIDR(spanningCIDR, net.IPNet{IP: nextFirstRangeIP, Mask: net.CIDRMask(bitLen, bitLen)}) cidrList = append(cidrList, left...) } else { // Otherwise, there is no need to partition; just use add the spanning @@ -110,8 +111,7 @@ func GetAddressRange(ipNet net.IPNet) (net.IP, net.IP) { lastIP = append(v4Mappedv6Prefix, lastIP...) } - lastIPMask := make(net.IPMask, len(ipNet.Mask)) - copy(lastIPMask, ipNet.Mask) + lastIPMask := slices.Clone(ipNet.Mask) for i := range lastIPMask { lastIPMask[len(lastIPMask)-i-1] = ^lastIPMask[len(lastIPMask)-i-1] lastIP[net.IPv6len-i-1] |= lastIPMask[len(lastIPMask)-i-1] @@ -127,8 +127,7 @@ func GetPreviousIP(ip net.IP) net.IP { return ip } - previousIP := make(net.IP, len(ip)) - copy(previousIP, ip) + previousIP := slices.Clone(ip) var overflow bool var lowerByteBound int @@ -215,7 +214,7 @@ func createSpanningCIDR(firstIP, lastIP *net.IP) net.IPNet { // contained within the targetCIDR (nil otherwise), and the // third is a list containing the networks to the right of the excludeCIDR in // the partition. -func partitionCIDR(targetCIDR net.IPNet, excludeCIDR net.IPNet) ([]*net.IPNet, []*net.IPNet, []*net.IPNet) { +func partitionCIDR(targetCIDR net.IPNet, excludeCIDR net.IPNet) ([]*net.IPNet, []*net.IPNet) { var targetIsIPv4 bool if targetCIDR.IP.To4() != nil { targetIsIPv4 = true @@ -228,25 +227,20 @@ func partitionCIDR(targetCIDR net.IPNet, excludeCIDR net.IPNet) ([]*net.IPNet, [ excludeMaskSize, _ := excludeCIDR.Mask.Size() if bytes.Compare(excludeLastIP, targetFirstIP) < 0 { - return nil, nil, []*net.IPNet{&targetCIDR} + return nil, []*net.IPNet{&targetCIDR} } else if bytes.Compare(targetLastIP, excludeFirstIP) < 0 { - return []*net.IPNet{&targetCIDR}, nil, nil + return []*net.IPNet{&targetCIDR}, nil } if targetMaskSize >= excludeMaskSize { - return nil, []*net.IPNet{&targetCIDR}, nil + return nil, nil } left := []*net.IPNet{} right := []*net.IPNet{} newPrefixLen := targetMaskSize + 1 - - targetFirstCopy := make(net.IP, len(targetFirstIP)) - copy(targetFirstCopy, targetFirstIP) - - iLowerOld := make(net.IP, len(targetFirstCopy)) - copy(iLowerOld, targetFirstCopy) + targetFirstCopy := slices.Clone(targetFirstIP) // Since golang only supports up to unsigned 64-bit integers, and we need // to perform addition on addresses, use math/big library, which allows @@ -258,12 +252,9 @@ func partitionCIDR(targetCIDR net.IPNet, excludeCIDR net.IPNet) ([]*net.IPNet, [ iUpper := big.NewInt(0) iLower = iLower.SetBytes(targetFirstCopy) - var bitLen int - + bitLen := ipv6BitLen if targetIsIPv4 { bitLen = ipv4BitLen - } else { - bitLen = ipv6BitLen } shiftAmount := (uint)(bitLen - newPrefixLen) @@ -297,7 +288,6 @@ func partitionCIDR(targetCIDR net.IPNet, excludeCIDR net.IPNet) ([]*net.IPNet, [ iUpperBytes = append(zeroBytes, iUpper.Bytes()...) } else { iUpperBytes = iUpper.Bytes() - } iLowerBytesLen := len(iLower.Bytes()) @@ -330,11 +320,9 @@ func partitionCIDR(targetCIDR net.IPNet, excludeCIDR net.IPNet) ([]*net.IPNet, [ iLower = iLower.Set(matched) iUpper = iUpper.Add(matched, big.NewInt(0).Lsh(big.NewInt(1), uint(bitLen-newPrefixLen))) - } - excludeList := []*net.IPNet{&excludeCIDR} - return left, excludeList, right + return left, right } func getNextIP(ip net.IP) net.IP { diff --git a/vendor/github.com/open-policy-agent/opa/internal/compiler/wasm/opa/callgraph.csv b/vendor/github.com/open-policy-agent/opa/internal/compiler/wasm/opa/callgraph.csv index 10dc4d482e..177aaee917 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/compiler/wasm/opa/callgraph.csv +++ b/vendor/github.com/open-policy-agent/opa/internal/compiler/wasm/opa/callgraph.csv @@ -636,11 +636,16 @@ opa_strings_replace,opa_realloc opa_strings_replace,memcpy opa_strings_replace,opa_string_allocated opa_strings_replace_n,opa_value_type +opa_strings_replace_n,opa_object_keys opa_strings_replace_n,opa_malloc +opa_strings_replace_n,memset +opa_strings_replace_n,opa_value_get +opa_strings_replace_n,opa_strncmp +opa_strings_replace_n,opa_realloc opa_strings_replace_n,memcpy opa_strings_replace_n,opa_string_allocated -opa_strings_replace_n,opa_strings_replace -opa_strings_replace_n,opa_value_free +opa_strings_replace_n,opa_free +opa_strings_replace_n,opa_array_free opa_strings_reverse,opa_value_type opa_strings_reverse,opa_malloc opa_strings_reverse,opa_unicode_decode_utf8 diff --git a/vendor/github.com/open-policy-agent/opa/internal/compiler/wasm/opa/opa.wasm b/vendor/github.com/open-policy-agent/opa/internal/compiler/wasm/opa/opa.wasm index 25d39a83e9..6be1842ddc 100644 Binary files a/vendor/github.com/open-policy-agent/opa/internal/compiler/wasm/opa/opa.wasm and b/vendor/github.com/open-policy-agent/opa/internal/compiler/wasm/opa/opa.wasm differ diff --git a/vendor/github.com/open-policy-agent/opa/internal/compiler/wasm/wasm.go b/vendor/github.com/open-policy-agent/opa/internal/compiler/wasm/wasm.go index b7f1a27812..95c02d9e22 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/compiler/wasm/wasm.go +++ b/vendor/github.com/open-policy-agent/opa/internal/compiler/wasm/wasm.go @@ -1026,9 +1026,7 @@ func (c *Compiler) compileBlock(block *ir.Block) ([]instruction.Instruction, err return nil, err } case *ir.CallDynamicStmt: - if err := c.compileCallDynamicStmt(stmt, &instrs); err != nil { - return nil, err - } + c.compileCallDynamicStmt(stmt, &instrs) case *ir.WithStmt: if err := c.compileWithStmt(stmt, &instrs); err != nil { return instrs, err @@ -1485,7 +1483,7 @@ func (c *Compiler) compileUpsert(local ir.Local, path []int, value ir.Operand, _ ) } -func (c *Compiler) compileCallDynamicStmt(stmt *ir.CallDynamicStmt, result *[]instruction.Instruction) error { +func (c *Compiler) compileCallDynamicStmt(stmt *ir.CallDynamicStmt, result *[]instruction.Instruction) { instrs := make([]instruction.Instruction, 0, 3+3*len(stmt.Path)+len(stmt.Args)+10) larray := c.genLocal() lidx := c.genLocal() @@ -1533,7 +1531,6 @@ func (c *Compiler) compileCallDynamicStmt(stmt *ir.CallDynamicStmt, result *[]in ) *result = append(*result, instrs...) - return nil } func (c *Compiler) compileCallStmt(stmt *ir.CallStmt, result *[]instruction.Instruction) error { diff --git a/vendor/github.com/open-policy-agent/opa/internal/deepcopy/deepcopy.go b/vendor/github.com/open-policy-agent/opa/internal/deepcopy/deepcopy.go index dc3a231bc1..b0e0bb2fb8 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/deepcopy/deepcopy.go +++ b/vendor/github.com/open-policy-agent/opa/internal/deepcopy/deepcopy.go @@ -4,17 +4,15 @@ package deepcopy +import "github.com/open-policy-agent/opa/v1/util" + // DeepCopy performs a recursive deep copy for nested slices/maps and // returns the copied object. Supports []any // and map[string]any only func DeepCopy(val any) any { switch val := val.(type) { case []any: - cpy := make([]any, len(val)) - for i := range cpy { - cpy[i] = DeepCopy(val[i]) - } - return cpy + return util.Map(val, DeepCopy) case map[string]any: return Map(val) default: diff --git a/vendor/github.com/open-policy-agent/opa/internal/edittree/edittree.go b/vendor/github.com/open-policy-agent/opa/internal/edittree/edittree.go index b710925199..39821f2e59 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/edittree/edittree.go +++ b/vendor/github.com/open-policy-agent/opa/internal/edittree/edittree.go @@ -168,6 +168,7 @@ package edittree import ( "errors" "fmt" + "slices" "strings" "github.com/open-policy-agent/opa/internal/edittree/bitvector" @@ -435,16 +436,16 @@ func (e *EditTree) unsafeInsertArray(idx int, value *ast.Term) *EditTree { } } // Do rewrites in reverse order to make room for the newly-inserted element. - for i := len(rewritesScalars) - 1; i >= 0; i-- { - originalIdx := rewritesScalars[i] - rewriteIdx := rewritesScalars[i] + 1 + for _, originalIdx := range slices.Backward(rewritesScalars) { + + rewriteIdx := originalIdx + 1 v := e.childScalarValues[originalIdx] e.deleteChildValue(originalIdx) e.setChildScalarValue(rewriteIdx, v) } - for i := len(rewritesComposites) - 1; i >= 0; i-- { - originalIdx := rewritesComposites[i] - rewriteIdx := rewritesComposites[i] + 1 + for _, originalIdx := range slices.Backward(rewritesComposites) { + + rewriteIdx := originalIdx + 1 v := e.childCompositeValues[originalIdx] e.deleteChildValue(originalIdx) e.setChildCompositeValue(rewriteIdx, v) diff --git a/vendor/github.com/open-policy-agent/opa/internal/file/archive/tarball.go b/vendor/github.com/open-policy-agent/opa/internal/file/archive/tarball.go index 93396aa96f..28189f7f99 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/file/archive/tarball.go +++ b/vendor/github.com/open-policy-agent/opa/internal/file/archive/tarball.go @@ -7,7 +7,8 @@ import ( "encoding/json" "errors" "io" - "strings" + + "github.com/open-policy-agent/opa/v1/util" ) type TarGzWriter struct { @@ -62,11 +63,7 @@ func MustWriteTarGz(files [][2]string) *bytes.Buffer { defer tgw.Close() for _, file := range files { - if !strings.HasPrefix(file[0], "/") { - file[0] = "/" + file[0] - } - - if err := tgw.WriteFile(file[0], []byte(file[1])); err != nil { + if err := tgw.WriteFile(util.WithPrefix(file[0], "/"), []byte(file[1])); err != nil { panic(err) } } diff --git a/vendor/github.com/open-policy-agent/opa/internal/future/filter_imports.go b/vendor/github.com/open-policy-agent/opa/internal/future/filter_imports.go index 27ca5559f1..c8d136c76b 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/future/filter_imports.go +++ b/vendor/github.com/open-policy-agent/opa/internal/future/filter_imports.go @@ -4,46 +4,36 @@ package future -import "github.com/open-policy-agent/opa/v1/ast" +import ( + "slices" + + "github.com/open-policy-agent/opa/v1/ast" +) // FilterFutureImports filters OUT any future imports from the passed slice of // `*ast.Import`s. func FilterFutureImports(imps []*ast.Import) []*ast.Import { - ret := []*ast.Import{} - for _, imp := range imps { - path := imp.Path.Value.(ast.Ref) - if !ast.FutureRootDocument.Equal(path[0]) { - ret = append(ret, imp) - } - } - return ret + return slices.DeleteFunc(slices.Clone(imps), isFutureKeywordImport) } // IsAllFutureKeywords returns true if the passed *ast.Import is `future.keywords` func IsAllFutureKeywords(imp *ast.Import) bool { path := imp.Path.Value.(ast.Ref) - return len(path) == 2 && - ast.FutureRootDocument.Equal(path[0]) && - path[1].Equal(ast.InternedTerm("keywords")) + return len(path) == 2 && path.HasPrefix(ast.FutureKeywordsRef) } // IsFutureKeyword returns true if the passed *ast.Import is `future.keywords.{kw}` func IsFutureKeyword(imp *ast.Import, kw string) bool { path := imp.Path.Value.(ast.Ref) - return len(path) == 3 && - ast.FutureRootDocument.Equal(path[0]) && - path[1].Equal(ast.InternedTerm("keywords")) && - path[2].Equal(ast.StringTerm(kw)) + return len(path) == 3 && path.HasPrefix(ast.FutureKeywordsRef) && path[2].Equal(ast.InternedTerm(kw)) } func WhichFutureKeyword(imp *ast.Import) (string, bool) { - path := imp.Path.Value.(ast.Ref) - if len(path) == 3 && - ast.FutureRootDocument.Equal(path[0]) && - path[1].Equal(ast.InternedTerm("keywords")) { - if str, ok := path[2].Value.(ast.String); ok { - return string(str), true - } - } - return "", false + name := imp.Name().String() + return name, imp.Alias == "" && IsFutureKeyword(imp, name) +} + +func isFutureKeywordImport(imp *ast.Import) bool { + path := imp.Path.Value.(ast.Ref) + return len(path) > 0 && path.HasPrefix(ast.FutureKeywordsRef[:1]) } diff --git a/vendor/github.com/open-policy-agent/opa/internal/future/parser_opts.go b/vendor/github.com/open-policy-agent/opa/internal/future/parser_opts.go index eaeb87e296..76d30dc51d 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/future/parser_opts.go +++ b/vendor/github.com/open-policy-agent/opa/internal/future/parser_opts.go @@ -15,16 +15,14 @@ import ( // `ast.ParserOptions` that can be used to parse a statement according to the // included "future.keywords" and "future.keywords.xyz" imports. func ParserOptionsFromFutureImports(imports []*ast.Import) (ast.ParserOptions, error) { - popts := ast.ParserOptions{ - FutureKeywords: []string{}, - } + popts := ast.ParserOptions{} for _, imp := range imports { path := imp.Path.Value.(ast.Ref) if !ast.FutureRootDocument.Equal(path[0]) { continue } if len(path) >= 2 { - if string(path[1].Value.(ast.String)) != "keywords" { + if !path.HasPrefix(ast.FutureKeywordsRef) { return popts, fmt.Errorf("unknown future import: %v", imp) } if len(path) == 2 { diff --git a/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/errors.go b/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/errors.go index 0168f59727..3ffde1e2a3 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/errors.go +++ b/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/errors.go @@ -1,4 +1,3 @@ -// nolint: goconst // String duplication will be handled later by using errors.Is. package gojsonschema import ( diff --git a/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/jsonLoader.go b/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/jsonLoader.go index 73f25e3b7f..569faf6060 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/jsonLoader.go +++ b/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/jsonLoader.go @@ -28,6 +28,7 @@ package gojsonschema import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -38,39 +39,40 @@ import ( "path/filepath" "runtime" "strings" - "sync" "github.com/xeipuuv/gojsonreference" ) -// NOTE(sr): We need to control from which hosts remote references are -// allowed to be resolved via HTTP requests. It's quite cumbersome to -// add extra parameters to all calls and interfaces involved, so we're -// using a global variable instead: -var allowNet map[string]struct{} -var netMut sync.RWMutex +// maxRemoteRefRedirects bounds the redirect chain a single remote reference +// fetch may follow. net/http applies its own limit only when CheckRedirect is +// nil, so policing the allowlist there means reimposing it here; the value +// matches the stdlib default. +const maxRemoteRefRedirects = 10 -func SetAllowNet(hosts []string) { - netMut.Lock() - defer netMut.Unlock() +// remoteRefLimits bounds the outbound requests a loader may make while +// resolving remote references. The zero value is unrestricted. +type remoteRefLimits struct { + // A nil set permits any host; an empty set permits none. + allowNet map[string]struct{} + + // LoadJSON takes no arguments, so the context rides on the loader instead. + // Loaders are built per Compile call, so its scope is that one compilation. + // A nil ctx means context.Background(). + ctx context.Context +} + +// newAllowNetSet turns a list of permitted hosts into a set. A nil list +// yields a nil set, which permits every host; a non-nil empty list yields +// an empty set, which permits none. +func newAllowNetSet(hosts []string) map[string]struct{} { if hosts == nil { - allowNet = nil // resetting the global - return + return nil } - allowNet = make(map[string]struct{}, len(hosts)) + allowNet := make(map[string]struct{}, len(hosts)) for _, host := range hosts { allowNet[host] = struct{}{} } -} - -func isAllowed(ref *url.URL) bool { - netMut.RLock() - defer netMut.RUnlock() - if allowNet == nil { - return true - } - _, ok := allowNet[ref.Hostname()] - return ok + return allowNet } var osFS = osFileSystem(os.Open) @@ -87,15 +89,20 @@ type JSONLoader interface { type JSONLoaderFactory interface { // New creates a new JSON loader for the given source New(source string) JSONLoader + // withRemoteRefLimits returns a copy of the factory whose loaders resolve + // remote references subject to the given limits. + withRemoteRefLimits(limits remoteRefLimits) JSONLoaderFactory } // DefaultJSONLoaderFactory is the default JSON loader factory type DefaultJSONLoaderFactory struct { + limits remoteRefLimits } // FileSystemJSONLoaderFactory is a JSON loader factory that uses http.FileSystem type FileSystemJSONLoaderFactory struct { - fs http.FileSystem + fs http.FileSystem + limits remoteRefLimits } // New creates a new JSON loader for the given source @@ -103,6 +110,7 @@ func (d DefaultJSONLoaderFactory) New(source string) JSONLoader { return &jsonReferenceLoader{ fs: osFS, source: source, + limits: d.limits, } } @@ -111,9 +119,20 @@ func (f FileSystemJSONLoaderFactory) New(source string) JSONLoader { return &jsonReferenceLoader{ fs: f.fs, source: source, + limits: f.limits, } } +func (d DefaultJSONLoaderFactory) withRemoteRefLimits(limits remoteRefLimits) JSONLoaderFactory { + d.limits = limits + return d +} + +func (f FileSystemJSONLoaderFactory) withRemoteRefLimits(limits remoteRefLimits) JSONLoaderFactory { + f.limits = limits + return f +} + // osFileSystem is a functional wrapper for os.Open that implements http.FileSystem. type osFileSystem func(string) (*os.File, error) @@ -128,6 +147,22 @@ func (o osFileSystem) Open(name string) (http.File, error) { type jsonReferenceLoader struct { fs http.FileSystem source string + limits remoteRefLimits +} + +func (l *jsonReferenceLoader) isAllowed(ref *url.URL) bool { + if l.limits.allowNet == nil { + return true + } + _, ok := l.limits.allowNet[ref.Hostname()] + return ok +} + +func (l *jsonReferenceLoader) context() context.Context { + if l.limits.ctx == nil { + return context.Background() + } + return l.limits.ctx } func (l *jsonReferenceLoader) JSONSource() any { @@ -140,7 +175,8 @@ func (l *jsonReferenceLoader) JSONReference() (gojsonreference.JsonReference, er func (l *jsonReferenceLoader) LoaderFactory() JSONLoaderFactory { return &FileSystemJSONLoaderFactory{ - fs: l.fs, + fs: l.fs, + limits: l.limits, } } @@ -200,7 +236,7 @@ func (l *jsonReferenceLoader) LoadJSON() (any, error) { return decodeJSONUsingNumber(strings.NewReader(metaSchema)) } - if isAllowed(refToURL.GetUrl()) { + if l.isAllowed(refToURL.GetUrl()) { return l.loadFromHTTP(refToURL.String()) } @@ -209,11 +245,31 @@ func (l *jsonReferenceLoader) LoadJSON() (any, error) { func (l *jsonReferenceLoader) loadFromHTTP(address string) (any, error) { - resp, err := http.Get(address) + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + if len(via) >= maxRemoteRefRedirects { + return fmt.Errorf("stopped after %d redirects", maxRemoteRefRedirects) + } + // Checking every hop, not just the first, stops a permitted host + // from bouncing the request onward to one that isn't allowed. + if !l.isAllowed(req.URL) { + return fmt.Errorf("remote reference loading disabled: %s", req.URL.String()) + } + return nil + }, + } + + req, err := http.NewRequestWithContext(l.context(), http.MethodGet, address, nil) if err != nil { return nil, err } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + // must return HTTP Status 200 OK if resp.StatusCode != http.StatusOK { return nil, errors.New(formatErrorDescription(Locale.HTTPBadStatus(), ErrorDetails{"status": resp.Status})) diff --git a/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/schema.go b/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/schema.go index 89f4f52177..4e0d66e424 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/schema.go +++ b/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/schema.go @@ -676,18 +676,24 @@ func (d *Schema) parseSchema(documentNode any, currentSchema *SubSchema) error { if err != nil { return err } - for _, v := range enum { - is, err := marshalWithoutNumber(v) - if err != nil { - return err + // Distinguish a present empty enum from a missing enum keyword. + // JSON Schema: enum validation succeeds only if the instance equals one of + // the listed values, so {"enum": []} is unsatisfiable (always fails). + if enum != nil { + currentSchema.enum = make([]string, 0, len(enum)) + for _, v := range enum { + is, err := marshalWithoutNumber(v) + if err != nil { + return err + } + if isStringInSlice(currentSchema.enum, *is) { + return errors.New(formatErrorDescription( + Locale.KeyItemsMustBeUnique(), + ErrorDetails{"key": KeyEnum}, + )) + } + currentSchema.enum = append(currentSchema.enum, *is) } - if isStringInSlice(currentSchema.enum, *is) { - return errors.New(formatErrorDescription( - Locale.KeyItemsMustBeUnique(), - ErrorDetails{"key": KeyEnum}, - )) - } - currentSchema.enum = append(currentSchema.enum, *is) } // validation : SubSchema diff --git a/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/schemaLoader.go b/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/schemaLoader.go index a27113d83f..50a1333178 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/schemaLoader.go +++ b/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/schemaLoader.go @@ -16,6 +16,7 @@ package gojsonschema import ( "bytes" + "context" "errors" "github.com/xeipuuv/gojsonreference" @@ -28,15 +29,20 @@ type SchemaLoader struct { Validate bool Draft Draft ValidatePatterns bool + // AllowNet is the list of hosts that remote references may be fetched + // from. A nil list permits any host; a non-nil empty list permits none. + AllowNet []string + // Context, when set, aborts in-flight remote reference fetches. Callers + // that have one -- evaluation, which holds the query's context -- should + // pass it so a cancelled or timed-out query doesn't leave requests + // running behind it. + Context context.Context } // NewSchemaLoader creates a new NewSchemaLoader func NewSchemaLoader() *SchemaLoader { - ps := &SchemaLoader{ - pool: &schemaPool{ - schemaPoolDocuments: make(map[string]*schemaPoolDocument), - }, + pool: &schemaPool{schemaPoolDocuments: make(map[string]*schemaPoolDocument)}, AutoDetect: true, Validate: false, Draft: Hybrid, @@ -46,15 +52,10 @@ func NewSchemaLoader() *SchemaLoader { return ps } -func (sl *SchemaLoader) validateMetaschema(documentNode any) error { - - var ( - schema string - err error - ) +func (sl *SchemaLoader) validateMetaschema(documentNode any) (err error) { + var schema string if sl.AutoDetect { - schema, _, err = parseSchemaURL(documentNode) - if err != nil { + if schema, _, err = parseSchemaURL(documentNode); err != nil { return err } } @@ -71,7 +72,6 @@ func (sl *SchemaLoader) validateMetaschema(documentNode any) error { sl.Validate = false metaSchema, err := sl.Compile(NewReferenceLoader(schema)) - if err != nil { return err } @@ -84,7 +84,7 @@ func (sl *SchemaLoader) validateMetaschema(documentNode any) error { var res bytes.Buffer for _, err := range result.Errors() { res.WriteString(err.String()) - res.WriteString("\n") + res.WriteByte('\n') } return errors.New(res.String()) } @@ -99,7 +99,6 @@ func (sl *SchemaLoader) AddSchemas(loaders ...JSONLoader) error { for _, loader := range loaders { doc, err := loader.LoadJSON() - if err != nil { return err } @@ -122,15 +121,12 @@ func (sl *SchemaLoader) AddSchemas(loaders ...JSONLoader) error { // AddSchema adds a schema under the provided URL to the schema cache func (sl *SchemaLoader) AddSchema(url string, loader JSONLoader) error { - ref, err := gojsonreference.NewJsonReference(url) - if err != nil { return err } doc, err := loader.LoadJSON() - if err != nil { return err } @@ -146,16 +142,20 @@ func (sl *SchemaLoader) AddSchema(url string, loader JSONLoader) error { // Compile loads and compiles a schema func (sl *SchemaLoader) Compile(rootSchema JSONLoader) (*Schema, error) { - ref, err := rootSchema.JSONReference() - if err != nil { return nil, err } d := Schema{} d.Pool = sl.pool - d.Pool.jsonLoaderFactory = rootSchema.LoaderFactory() + // NewStringLoader and NewGoLoader hand back unrestricted factories, so the + // limits come from the compilation, not the root loader. The pool resolves + // every $ref through this factory, however deeply nested. + d.Pool.jsonLoaderFactory = rootSchema.LoaderFactory().withRemoteRefLimits(remoteRefLimits{ + allowNet: newAllowNetSet(sl.AllowNet), + ctx: sl.Context, + }) d.DocumentReference = ref d.ReferencePool = newSchemaReferencePool() d.validatePatterns = sl.ValidatePatterns @@ -170,14 +170,12 @@ func (sl *SchemaLoader) Compile(rootSchema JSONLoader) (*Schema, error) { doc = spd.Document } else { // Load JSON directly - doc, err = rootSchema.LoadJSON() - if err != nil { + if doc, err = rootSchema.LoadJSON(); err != nil { return nil, err } // References need only be parsed if loading JSON directly - // as pool.GetDocument already does this for us if loading by reference - err = sl.pool.parseReferences(doc, ref, true) - if err != nil { + // as pool.GetDocument already does this for us if loading by reference + if err = sl.pool.parseReferences(doc, ref, true); err != nil { return nil, err } } @@ -199,8 +197,7 @@ func (sl *SchemaLoader) Compile(rootSchema JSONLoader) (*Schema, error) { } } - err = d.parse(doc, draft) - if err != nil { + if err = d.parse(doc, draft); err != nil { return nil, err } diff --git a/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/utils.go b/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/utils.go index a8639d4d9a..ee06ba9099 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/utils.go +++ b/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/utils.go @@ -23,7 +23,6 @@ // // created 26-02-2013 -// nolint:unused // Package in development (2021). package gojsonschema import ( diff --git a/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/validation.go b/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/validation.go index e33a0f3d27..890785a495 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/validation.go +++ b/vendor/github.com/open-policy-agent/opa/internal/gojsonschema/validation.go @@ -419,8 +419,9 @@ func (v *SubSchema) validateCommon(currentSubSchema *SubSchema, value any, resul } } - // enum: - if len(currentSubSchema.enum) > 0 { + // enum: nil means the keyword is absent; non-nil (including empty) means + // the instance must deep-equal one of the listed values. + if currentSubSchema.enum != nil { vString, err := marshalWithoutNumber(value) if err != nil { result.addInternalError(new(InternalError), context, value, ErrorDetails{"error": err}) diff --git a/vendor/github.com/open-policy-agent/opa/internal/lcss/qsufsort.go b/vendor/github.com/open-policy-agent/opa/internal/lcss/qsufsort.go index 61c5196886..db83fec312 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/lcss/qsufsort.go +++ b/vendor/github.com/open-policy-agent/opa/internal/lcss/qsufsort.go @@ -24,7 +24,10 @@ package lcss -import "sort" +import ( + "slices" + "sort" +) // qsufsort constructs the suffix array for a given string. func qsufsort(data []byte) []int { @@ -55,7 +58,7 @@ func qsufsort(data []byte) []int { } pk := inv[s] + 1 // pk-1 is last position of unsorted group sufSortable.sa = sa[pi:pk] - sort.Sort(sufSortable) + sort.Sort(sufSortable) //nolint:forbidigo sufSortable.updateGroups(pi) pi = pk // next group } @@ -98,15 +101,15 @@ func initGroups(sa []int, data []byte) []int { inv := make([]int, len(data)) prevGroup := len(sa) - 1 groupByte := data[sa[prevGroup]] - for i := len(sa) - 1; i >= 0; i-- { - if b := data[sa[i]]; b < groupByte { + for i, s := range slices.Backward(sa) { + if b := data[s]; b < groupByte { if prevGroup == i+1 { sa[i+1] = -1 } groupByte = b prevGroup = i } - inv[sa[i]] = prevGroup + inv[s] = prevGroup if prevGroup == 0 { sa[0] = -1 } diff --git a/vendor/github.com/open-policy-agent/opa/internal/leb128/leb128.go b/vendor/github.com/open-policy-agent/opa/internal/leb128/leb128.go index 24ddc90951..03755bd506 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/leb128/leb128.go +++ b/vendor/github.com/open-policy-agent/opa/internal/leb128/leb128.go @@ -50,10 +50,7 @@ func MustReadVarUint64(r io.Reader) uint64 { // ReadVarUint32 tries to read a uint32 from r. func ReadVarUint32(r io.Reader) (uint32, error) { u64, err := ReadVarUint64(r) - if err != nil { - return 0, err - } - return uint32(u64), nil + return uint32(u64), err } // ReadVarUint64 tries to read a uint64 from r. @@ -78,10 +75,7 @@ func ReadVarUint64(r io.Reader) (uint64, error) { // ReadVarInt32 tries to read a int32 from r. func ReadVarInt32(r io.Reader) (int32, error) { i64, err := ReadVarInt64(r) - if err != nil { - return 0, err - } - return int32(i64), nil + return int32(i64), err } // ReadVarInt64 tries to read a int64 from r. diff --git a/vendor/github.com/open-policy-agent/opa/internal/methodlesstemplate/funcs.go b/vendor/github.com/open-policy-agent/opa/internal/methodlesstemplate/funcs.go index 4d733135fe..a313a97c85 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/methodlesstemplate/funcs.go +++ b/vendor/github.com/open-policy-agent/opa/internal/methodlesstemplate/funcs.go @@ -100,14 +100,6 @@ func addValueFuncs(out map[string]reflect.Value, in FuncMap) { } } -// addFuncs adds to values the functions in funcs. It does no checking of the input - -// call addValueFuncs first. -func addFuncs(out, in FuncMap) { - for name, fn := range in { - out[name] = fn - } -} - // goodFunc reports whether the function or method has the right result signature. func goodFunc(name string, typ reflect.Type) error { // We allow functions with 1 result or 2 results where the second is an error. diff --git a/vendor/github.com/open-policy-agent/opa/internal/methodlesstemplate/internal/fmtsort/sort.go b/vendor/github.com/open-policy-agent/opa/internal/methodlesstemplate/internal/fmtsort/sort.go index f51cdc7083..73479306c0 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/methodlesstemplate/internal/fmtsort/sort.go +++ b/vendor/github.com/open-policy-agent/opa/internal/methodlesstemplate/internal/fmtsort/sort.go @@ -11,7 +11,8 @@ package fmtsort import ( "cmp" "reflect" - "slices" + + "github.com/open-policy-agent/opa/v1/util" ) // Note: Throughout this package we avoid calling reflect.Value.Interface as @@ -59,10 +60,9 @@ func Sort(mapValue reflect.Value) SortedMap { for iter.Next() { sorted = append(sorted, KeyValue{iter.Key(), iter.Value()}) } - slices.SortStableFunc(sorted, func(a, b KeyValue) int { + return util.SortedStableFunc(sorted, func(a, b KeyValue) int { return compare(a.Key, b.Key) }) - return sorted } // compare compares two values of the same type. It returns -1, 0, 1 diff --git a/vendor/github.com/open-policy-agent/opa/internal/methodlesstemplate/template.go b/vendor/github.com/open-policy-agent/opa/internal/methodlesstemplate/template.go index 9ae5a6ca5b..5ed6225fcd 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/methodlesstemplate/template.go +++ b/vendor/github.com/open-policy-agent/opa/internal/methodlesstemplate/template.go @@ -175,9 +175,9 @@ func (t *Template) Delims(left, right string) *Template { func (t *Template) Funcs(funcMap FuncMap) *Template { t.init() t.muFuncs.Lock() - defer t.muFuncs.Unlock() addValueFuncs(t.execFuncs, funcMap) - addFuncs(t.parseFuncs, funcMap) + maps.Copy(t.parseFuncs, funcMap) + t.muFuncs.Unlock() return t } diff --git a/vendor/github.com/open-policy-agent/opa/internal/planner/planner.go b/vendor/github.com/open-policy-agent/opa/internal/planner/planner.go index a7d62542c2..558d71da9f 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/planner/planner.go +++ b/vendor/github.com/open-policy-agent/opa/internal/planner/planner.go @@ -6,10 +6,12 @@ package planner import ( + "cmp" "errors" "fmt" "io" - "sort" + "slices" + "strings" "github.com/open-policy-agent/opa/internal/debug" "github.com/open-policy-agent/opa/v1/ast" @@ -47,6 +49,12 @@ type Planner struct { lnext ir.Local // next variable to use loc *location.Location // location currently "being planned" debug debug.Debug // debug information produced during planning + + allRules map[*ast.Rule]bool // all rules parsed from input modules, used to track unplanned rules for additional reporting (e.g. coverage) + plannedRules map[*ast.Rule]bool + planning map[string]struct{} // ground path prefixes currently being planned + + unplannedRules bool // whether to populate policy.UnplannedRules } // debugf prepends the planner location. We're passing callstack depth 2 because @@ -81,6 +89,10 @@ func New() *Planner { funcs: newFuncstack(), mocks: newFunctionMocksStack(), debug: debug.Discard(), + + allRules: map[*ast.Rule]bool{}, + plannedRules: map[*ast.Rule]bool{}, + planning: map[string]struct{}{}, } } @@ -113,26 +125,51 @@ func (p *Planner) WithDebug(sink io.Writer) *Planner { return p } +// WithUnplannedRules controls whether the resulting policy includes the +// list of rules that were parsed but never planned (i.e. not reachable +// from any entrypoint). Disabled by default. +func (p *Planner) WithUnplannedRules(yes bool) *Planner { + p.unplannedRules = yes + return p +} + // Plan returns a IR plan for the policy query. func (p *Planner) Plan() (*ir.Policy, error) { - - if err := p.buildFunctrie(); err != nil { - return nil, err - } + p.buildFunctrie() if err := p.planQueries(); err != nil { return nil, err } - if err := p.planExterns(); err != nil { - return nil, err + p.planExterns() + + if p.unplannedRules { + p.buildUnplannedRules() } return p.policy, nil } -func (p *Planner) buildFunctrie() error { +// buildUnplannedRules populates policy.UnplannedRules with the rules that +// were parsed but never planned (i.e. not reachable from any entrypoint), +// for coverage reporting purposes. +func (p *Planner) buildUnplannedRules() { + for rule := range p.allRules { + if p.plannedRules[rule] { + continue + } + p.policy.UnplannedRules = append(p.policy.UnplannedRules, &ir.UnplannedRule{ + Path: rule.Ref().String(), + Location: p.newLocation(rule.Loc()), + }) + } + slices.SortFunc(p.policy.UnplannedRules, func(a, b *ir.UnplannedRule) int { + return strings.Compare(a.Path, b.Path) + }) +} + +func (p *Planner) buildFunctrie() { for _, module := range p.modules { // Create functrie node for empty packages so that extent queries return @@ -149,6 +186,8 @@ func (p *Planner) buildFunctrie() error { } for _, rule := range module.Rules { + p.allRules[rule] = true + r := rule.Ref().StringPrefix() val := p.rules.LookupOrInsert(r) @@ -157,19 +196,22 @@ func (p *Planner) buildFunctrie() error { val.children = nil } } - return nil } func (p *Planner) planRules(rules []*ast.Rule) (string, error) { + for _, rule := range rules { + p.plannedRules[rule] = true + } + // We sort rules, first by ref length, and then using the // Ref.Compare method to break ties. This yields a stable // sorting order for the slice of rules to be planned. - sort.Slice(rules, func(i, j int) bool { - li, lj := len(rules[i].Ref()), len(rules[j].Ref()) - if li != lj { - return li > lj + slices.SortFunc(rules, func(a, b *ast.Rule) int { + aRef, bRef := a.Ref(), b.Ref() + if c := cmp.Compare(len(aRef), len(bRef)); c != 0 { + return -c } - return rules[i].Ref().Compare(rules[j].Ref()) < 0 + return aRef.Compare(bRef) }) // We know the rules that are closer to the root (shorter static path) are ordered first. @@ -205,6 +247,23 @@ func (p *Planner) planRules(rules []*ast.Rule) (string, error) { return funcName, nil } + // One function is planned per ground path prefix, so rules whose refs only + // differ past a variable share a function. A reference from one of those + // rule bodies back into the same prefix is not recursion the compiler would + // reject, but the planner has no way to evaluate part of a function that is + // still being planned. The generation is left out of the key on purpose: a + // 'with' statement that shadows planned functions bumps it, and keying on + // it would let the same prefix re-enter planning forever. + if _, ok := p.planning[path]; ok { + err := fmt.Errorf("reference to %v is not supported: rules sharing that path prefix are planned as a single function", path) + if p.loc != nil { + return "", fmt.Errorf("%v: %w", p.loc, err) + } + return "", err + } + p.planning[path] = struct{}{} + defer delete(p.planning, path) + // Save current state of planner. // // TODO(tsandall): perhaps we would be better off using stacks here or @@ -533,7 +592,6 @@ func (p *Planner) planFuncParams(params []ir.Local, args ast.Args, idx int, iter } func (p *Planner) planQueries() error { - for _, qs := range p.queries { // Initialize the plan with a block that prepares the query result. @@ -616,7 +674,6 @@ func (p *Planner) planQueries() error { } func (p *Planner) planQuery(q ast.Body, index int, iter planiter) error { - if index >= len(q) { return iter() } @@ -898,8 +955,8 @@ func (p *Planner) planWith(e *ast.Expr, iter planiter) error { p.mocks.PopFrame() if shadowing { p.funcs.Pop() - for i := len(dataRefs) - 1; i >= 0; i-- { - p.rules.Pop(dataRefs[i]) + for _, dataRef := range slices.Backward(dataRefs) { + p.rules.Pop(dataRef) } } @@ -923,8 +980,8 @@ func (p *Planner) planWith(e *ast.Expr, iter planiter) error { p.mocks.PopFrame() if shadowing { p.funcs.Pop() - for i := len(dataRefs) - 1; i >= 0; i-- { - p.rules.Pop(dataRefs[i]) + for _, dataRef := range slices.Backward(dataRefs) { + p.rules.Pop(dataRef) } } return err @@ -2440,19 +2497,16 @@ func (p *Planner) planTermSliceRec(terms []*ast.Term, locals []ir.Operand, index }) } -func (p *Planner) planExterns() error { - +func (p *Planner) planExterns() { p.policy.Static.BuiltinFuncs = make([]*ir.BuiltinFunc, 0, len(p.externs)) for name, decl := range p.externs { p.policy.Static.BuiltinFuncs = append(p.policy.Static.BuiltinFuncs, &ir.BuiltinFunc{Name: name, Decl: decl.Decl}) } - sort.Slice(p.policy.Static.BuiltinFuncs, func(i, j int) bool { - return p.policy.Static.BuiltinFuncs[i].Name < p.policy.Static.BuiltinFuncs[j].Name + slices.SortFunc(p.policy.Static.BuiltinFuncs, func(a, b *ir.BuiltinFunc) int { + return strings.Compare(a.Name, b.Name) }) - - return nil } func (p *Planner) getStringConst(s string) int { @@ -2479,6 +2533,18 @@ func (p *Planner) getFileConst(s string) int { return index } +// newLocation builds a fresh *ir.Location from an ast.Location. It lives on +// Planner because it needs p.getFileConst to resolve the file constant index. +func (p *Planner) newLocation(loc *location.Location) *ir.Location { + str := loc.File + if str == "" { + str = `` + } + l := &ir.Location{} + l.SetLocation(p.getFileConst(str), loc.Row, loc.Col, str, loc.Text) + return l +} + func (p *Planner) appendStmt(s ir.Stmt) { p.appendStmtToBlock(s, p.curr) } @@ -2489,7 +2555,7 @@ func (p *Planner) appendStmtToBlock(s ir.Stmt, b *ir.Block) { if str == "" { str = `` } - s.SetLocation(p.getFileConst(str), p.loc.Row, p.loc.Col, str, string(p.loc.Text)) + s.SetLocation(p.getFileConst(str), p.loc.Row, p.loc.Col, str, p.loc.Text) } b.Stmts = append(b.Stmts, s) } diff --git a/vendor/github.com/open-policy-agent/opa/internal/planner/rules.go b/vendor/github.com/open-policy-agent/opa/internal/planner/rules.go index ed4da8571b..21245134e4 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/planner/rules.go +++ b/vendor/github.com/open-policy-agent/opa/internal/planner/rules.go @@ -2,7 +2,7 @@ package planner import ( "fmt" - "sort" + "slices" "github.com/open-policy-agent/opa/v1/ast" "github.com/open-policy-agent/opa/v1/util" @@ -242,10 +242,7 @@ func (t *ruletrie) Children() []ast.Value { sorted = append(sorted, key) } } - sort.Slice(sorted, func(i, j int) bool { - return sorted[i].Compare(sorted[j]) < 0 - }) - return sorted + return util.SortedFunc(sorted, ast.Value.Compare) } func (t *ruletrie) Get(k ast.Value) *ruletrie { @@ -326,8 +323,8 @@ func (s *functionMocksStack) PopFrame() { func (s *functionMocksStack) Lookup(f string) *ast.Term { current := s.stack.PeekGroup() - for i := len(current) - 1; i >= 0; i-- { - if t, ok := current[i][f]; ok { + for _, c := range slices.Backward(current) { + if t, ok := c[f]; ok { return t } } diff --git a/vendor/github.com/open-policy-agent/opa/internal/planner/varstack.go b/vendor/github.com/open-policy-agent/opa/internal/planner/varstack.go index 0df6bcd8b2..da70815b55 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/planner/varstack.go +++ b/vendor/github.com/open-policy-agent/opa/internal/planner/varstack.go @@ -5,6 +5,8 @@ package planner import ( + "slices" + "github.com/open-policy-agent/opa/v1/ast" "github.com/open-policy-agent/opa/v1/ir" ) @@ -34,8 +36,8 @@ func (vs varstack) GetOrEmpty(k ast.Var) ir.Local { } func (vs varstack) Get(k ast.Var) (ir.Local, bool) { - for i := len(vs) - 1; i >= 0; i-- { - if l, ok := vs[i][k]; ok { + for _, v := range slices.Backward(vs) { + if l, ok := v[k]; ok { return l, true } } diff --git a/vendor/github.com/open-policy-agent/opa/internal/providers/aws/crypto/ecc.go b/vendor/github.com/open-policy-agent/opa/internal/providers/aws/crypto/ecc.go index f93261a809..27db7645c2 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/providers/aws/crypto/ecc.go +++ b/vendor/github.com/open-policy-agent/opa/internal/providers/aws/crypto/ecc.go @@ -2,7 +2,6 @@ package crypto import ( "bytes" - "crypto/ecdh" "crypto/ecdsa" "crypto/elliptic" "crypto/hmac" @@ -31,34 +30,8 @@ func ECDSAKeyFromPoint(curve elliptic.Curve, d *big.Int) *ecdsa.PrivateKey { dBytes := make([]byte, (curve.Params().BitSize+7)/8) d.FillBytes(dBytes) - privKey := &ecdsa.PrivateKey{ - PublicKey: ecdsa.PublicKey{ - Curve: curve, - }, - D: d, - } - - var pubBytes []byte - switch curve { - case elliptic.P256(): - if ecdhPriv, err := ecdh.P256().NewPrivateKey(dBytes); err == nil { - pubBytes = ecdhPriv.PublicKey().Bytes() - } - case elliptic.P384(): - if ecdhPriv, err := ecdh.P384().NewPrivateKey(dBytes); err == nil { - pubBytes = ecdhPriv.PublicKey().Bytes() - } - case elliptic.P521(): - if ecdhPriv, err := ecdh.P521().NewPrivateKey(dBytes); err == nil { - pubBytes = ecdhPriv.PublicKey().Bytes() - } - } - - if len(pubBytes) > 0 { - byteLen := (curve.Params().BitSize + 7) / 8 - privKey.X = new(big.Int).SetBytes(pubBytes[1 : 1+byteLen]) - privKey.Y = new(big.Int).SetBytes(pubBytes[1+byteLen:]) - } else { + privKey, err := ecdsa.ParseRawPrivateKey(curve, dBytes) + if err != nil { panic(fmt.Sprintf("unsupported curve or invalid private key: %v", curve)) } @@ -67,8 +40,8 @@ func ECDSAKeyFromPoint(curve elliptic.Curve, d *big.Int) *ecdsa.PrivateKey { // mathIntToBytes writes val as a big-endian, fixed-length byte slice into out, // zero-padding on the left when val.Bytes() is shorter than out. This satisfies -// the uncompressed SEC 1 encoding (0x04 || X || Y) expected by crypto/ecdh's -// NewPublicKey: https://pkg.go.dev/crypto/ecdh#Curve.NewPublicKey +// the uncompressed SEC 1 encoding (0x04 || X || Y) expected by +// ecdsa.ParseUncompressedPublicKey: https://pkg.go.dev/crypto/ecdsa#ParseUncompressedPublicKey func mathIntToBytes(val *big.Int, out []byte) { valBytes := val.Bytes() copy(out[len(out)-len(valBytes):], valBytes) @@ -86,27 +59,12 @@ func ECDSAPublicKey(curve elliptic.Curve, x, y []byte) (*ecdsa.PublicKey, error) mathIntToBytes(xPoint, buf[1:1+byteLen]) mathIntToBytes(yPoint, buf[1+byteLen:]) - var err error - switch curve { - case elliptic.P256(): - _, err = ecdh.P256().NewPublicKey(buf) - case elliptic.P384(): - _, err = ecdh.P384().NewPublicKey(buf) - case elliptic.P521(): - _, err = ecdh.P521().NewPublicKey(buf) - default: - err = fmt.Errorf("unsupported curve for ECDSA: %v", curve) - } - + pub, err := ecdsa.ParseUncompressedPublicKey(curve, buf) if err != nil { return nil, fmt.Errorf("point(%v, %v) is not on the given curve", xPoint.String(), yPoint.String()) } - return &ecdsa.PublicKey{ - Curve: curve, - X: xPoint, - Y: yPoint, - }, nil + return pub, nil } // VerifySignature takes the provided public key, hash, and asn1 encoded signature and returns diff --git a/vendor/github.com/open-policy-agent/opa/internal/providers/aws/signing_v4.go b/vendor/github.com/open-policy-agent/opa/internal/providers/aws/signing_v4.go index c463ccbff8..cb3f57d77e 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/providers/aws/signing_v4.go +++ b/vendor/github.com/open-policy-agent/opa/internal/providers/aws/signing_v4.go @@ -151,23 +151,31 @@ func SignV4(headers map[string][]string, method string, theURL *url.URL, body [] // the "canonical request" is the normalized version of the AWS service access // that we're attempting to perform - canonicalReq := method + "\n" // HTTP method - canonicalReq += theURL.EscapedPath() + "\n" // URI-escaped path - canonicalReq += theURL.RawQuery + "\n" // RAW Query String + buf := bytes.NewBufferString(method) + buf.WriteByte('\n') + buf.WriteString(theURL.EscapedPath()) + buf.WriteByte('\n') + buf.WriteString(theURL.RawQuery) + buf.WriteByte('\n') // include the values for the signed headers orderedKeys := util.KeysSorted(headersToSign) for _, k := range orderedKeys { - // TODO: fix later - //nolint:perfsprint - canonicalReq += k + ":" + strings.Join(headersToSign[k], ",") + "\n" + buf.WriteString(k) + buf.WriteByte(':') + buf.WriteString(strings.Join(headersToSign[k], ",")) + buf.WriteByte('\n') } - canonicalReq += "\n" // linefeed to terminate headers + + buf.WriteByte('\n') // linefeed to terminate headers // include the list of the signed headers headerList := strings.Join(orderedKeys, ";") - canonicalReq += headerList + "\n" - canonicalReq += contentSha256 + buf.WriteString(headerList) + buf.WriteByte('\n') + buf.WriteString(contentSha256) + + canonicalReq := buf.String() // the "string to sign" is a time-bounded, scoped request token which // is linked to the "canonical request" by inclusion of its SHA-256 hash diff --git a/vendor/github.com/open-policy-agent/opa/internal/providers/aws/signing_v4a.go b/vendor/github.com/open-policy-agent/opa/internal/providers/aws/signing_v4a.go index db20eddc9d..0cdfb12664 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/providers/aws/signing_v4a.go +++ b/vendor/github.com/open-policy-agent/opa/internal/providers/aws/signing_v4a.go @@ -4,7 +4,6 @@ package aws import ( "bytes" "crypto" - "crypto/ecdh" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -16,7 +15,7 @@ import ( "math/big" "net/http" "net/url" - "sort" + "slices" "strconv" "strings" "sync" @@ -113,23 +112,10 @@ func deriveKeyFromAccessKeyPair(accessKey, secretKey string) (*ecdsa.PrivateKey, } d = d.Add(d, one) - priv := new(ecdsa.PrivateKey) - priv.PublicKey.Curve = p256 - priv.D = d - dBytes := make([]byte, 32) d.FillBytes(dBytes) - ecdhPriv, err := ecdh.P256().NewPrivateKey(dBytes) - if err != nil { - return nil, err - } - pubBytes := ecdhPriv.PublicKey().Bytes() - - priv.PublicKey.X = new(big.Int).SetBytes(pubBytes[1:33]) - priv.PublicKey.Y = new(big.Int).SetBytes(pubBytes[33:]) - - return priv, nil + return ecdsa.ParseRawPrivateKey(p256, dBytes) } // v4aCredentials is Context, ECDSA, and Optional Session Token that can be used @@ -211,7 +197,7 @@ func (s *httpSigner) Build() (signedRequest, error) { // Sort Each Query Key's Values for key := range query { - sort.Strings(query[key]) + slices.Sort(query[key]) } v4Internal.SanitizeHostForHeader(req) @@ -319,7 +305,7 @@ func (*httpSigner) buildCanonicalHeaders(host string, rule v4Internal.Rule, head headers = append(headers, lowerCaseKey) signed[lowerCaseKey] = v } - sort.Strings(headers) + slices.Sort(headers) signedHeaders = strings.Join(headers, ";") diff --git a/vendor/github.com/open-policy-agent/opa/internal/providers/aws/util.go b/vendor/github.com/open-policy-agent/opa/internal/providers/aws/util.go index d43339c961..b3ccdbfa0d 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/providers/aws/util.go +++ b/vendor/github.com/open-policy-agent/opa/internal/providers/aws/util.go @@ -22,7 +22,7 @@ func DoRequestWithClient(req *http.Request, client *http.Client, desc string, lo "url": req.URL.String(), "status": resp.Status, "headers": resp.Header, - }).Debug("Received response from " + desc + " service.") + }).Debug("Received response from %s service.", desc) body, err := io.ReadAll(resp.Body) if err != nil { diff --git a/vendor/github.com/open-policy-agent/opa/internal/providers/aws/v4/host.go b/vendor/github.com/open-policy-agent/opa/internal/providers/aws/v4/host.go index bf93659a43..19c689da45 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/providers/aws/v4/host.go +++ b/vendor/github.com/open-policy-agent/opa/internal/providers/aws/v4/host.go @@ -28,48 +28,37 @@ func getHost(r *http.Request) string { // If Host is an IPv6 literal with a port number, Hostname returns the // IPv6 literal without the square brackets. IPv6 literals may include // a zone identifier. -// -// Copied from the Go 1.8 standard library (net/url) func stripPort(hostport string) string { - colon := strings.IndexByte(hostport, ':') - if colon == -1 { + before, _, ok := strings.Cut(hostport, ":") + if !ok { return hostport } - if i := strings.IndexByte(hostport, ']'); i != -1 { - return strings.TrimPrefix(hostport[:i], "[") + if before, _, ok := strings.Cut(hostport, "]"); ok { + return strings.TrimPrefix(before, "[") } - return hostport[:colon] + return before } // Port returns the port part of u.Host, without the leading colon. // If u.Host doesn't contain a port, Port returns an empty string. -// -// Copied from the Go 1.8 standard library (net/url) func portOnly(hostport string) string { - colon := strings.IndexByte(hostport, ':') - if colon == -1 { + _, after, ok := strings.Cut(hostport, ":") + if !ok { return "" } - if i := strings.Index(hostport, "]:"); i != -1 { - return hostport[i+len("]:"):] + if _, after, ok := strings.Cut(hostport, "]:"); ok { + return after } if strings.Contains(hostport, "]") { return "" } - return hostport[colon+len(":"):] + return after } // Returns true if the specified URI is using the standard port // (i.e. port 80 for HTTP URIs or 443 for HTTPS URIs) func isDefaultPort(scheme, port string) bool { - if port == "" { - return true - } - - lowerCaseScheme := strings.ToLower(scheme) - if (lowerCaseScheme == "http" && port == "80") || (lowerCaseScheme == "https" && port == "443") { - return true - } - - return false + return port == "" || + (strings.EqualFold(scheme, "http") && port == "80") || + (strings.EqualFold(scheme, "https") && port == "443") } diff --git a/vendor/github.com/open-policy-agent/opa/internal/semver/semver.go b/vendor/github.com/open-policy-agent/opa/internal/semver/semver.go index d46f80aeb8..9c1196b71a 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/semver/semver.go +++ b/vendor/github.com/open-policy-agent/opa/internal/semver/semver.go @@ -45,13 +45,17 @@ type Version struct { func Parse(version string) (v Version, err error) { version = strings.TrimPrefix(version, "v") - version, v.Metadata = cut(version, '+') - if v.Metadata != "" && !reMetaIdentifier.MatchString(v.Metadata) { + var foundMetadata bool + + version, v.Metadata, foundMetadata = strings.Cut(version, "+") + if foundMetadata && !reMetaIdentifier.MatchString(v.Metadata) { return v, fmt.Errorf("invalid metadata identifier: %s", v.Metadata) } - version, v.PreRelease = cut(version, '-') - if v.PreRelease != "" && !reMetaIdentifier.MatchString(v.PreRelease) { + var foundPreRelease bool + + version, v.PreRelease, foundPreRelease = strings.Cut(version, "-") + if foundPreRelease && (!reMetaIdentifier.MatchString(v.PreRelease) || !validPreRelease(v.PreRelease)) { return v, fmt.Errorf("invalid pre-release identifier: %s", v.PreRelease) } @@ -59,23 +63,45 @@ func Parse(version string) (v Version, err error) { return v, fmt.Errorf("%s should contain major, minor, and patch versions", version) } - major, after := cut(version, '.') - if v.Major, err = strconv.ParseInt(major, 10, 64); err != nil { - return v, err + major, after := cutDot(version) + if v.Major, err = parseNumeric(major); err != nil { + return v, fmt.Errorf("invalid major version: %w", err) } - minor, after := cut(after, '.') - if v.Minor, err = strconv.ParseInt(minor, 10, 64); err != nil { - return v, err + minor, after := cutDot(after) + if v.Minor, err = parseNumeric(minor); err != nil { + return v, fmt.Errorf("invalid minor version: %w", err) } - if v.Patch, err = strconv.ParseInt(after, 10, 64); err != nil { - return v, err + if v.Patch, err = parseNumeric(after); err != nil { + return v, fmt.Errorf("invalid patch version: %w", err) } return v, nil } +// parseNumeric parses a major, minor or patch identifier, rejecting the empty +// string, a sign or a leading zero (all forbidden by SemVer 2.0.0) before +// converting to int64. +func parseNumeric(s string) (int64, error) { + if s == "" || s[0] == '+' || s[0] == '-' || (len(s) > 1 && s[0] == '0') { + return 0, fmt.Errorf("%q is not a valid numeric identifier", s) + } + return strconv.ParseInt(s, 10, 64) +} + +// validPreRelease reports whether every numeric pre-release identifier is free +// of leading zeroes, as required by SemVer 2.0.0. The identifier character set +// has already been checked by reMetaIdentifier. +func validPreRelease(pre string) bool { + for id := range strings.SplitSeq(pre, ".") { + if len(id) > 1 && id[0] == '0' && isAllDecimals(id) { + return false + } + } + return true +} + // MustParse is like Parse but panics if the version string is invalid instead of returning an error. func MustParse(version string) Version { v, err := Parse(version) @@ -164,8 +190,8 @@ func (v Version) Compare(other Version) int { return -1 } - a, afterA := cut(v.PreRelease, '.') - b, afterB := cut(other.PreRelease, '.') + a, afterA := cutDot(v.PreRelease) + b, afterB := cutDot(other.PreRelease) for { if a == "" && b != "" { @@ -211,8 +237,8 @@ func (v Version) Compare(other Version) int { return -1 } - a, afterA = cut(afterA, '.') - b, afterB = cut(afterB, '.') + a, afterA = cutDot(afterA) + b, afterB = cutDot(afterB) } } @@ -237,10 +263,13 @@ func length(v Version) int { return n } -// cut is a *slightly* faster version of strings.Cut only accepting -// single byte separators, and skipping the boolean return value. -func cut(s string, sep byte) (before, after string) { - if i := strings.IndexByte(s, sep); i >= 0 { +// cutDot is a *slightly* faster version of strings.Cut for the '.' separator, +// skipping the boolean return value. strings.Cut looks the separator up with +// strings.Index, which costs ~12% on BenchmarkCompare next to IndexByte. +// +//nolint:modernize // stringscut: measurably slower here, see above. +func cutDot(s string) (before, after string) { + if i := strings.IndexByte(s, '.'); i >= 0 { return s[:i], s[i+1:] } return s, "" diff --git a/vendor/github.com/open-policy-agent/opa/internal/uuid/uuid.go b/vendor/github.com/open-policy-agent/opa/internal/uuid/uuid.go index 63e1a5b071..bdf41d7b8b 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/uuid/uuid.go +++ b/vendor/github.com/open-policy-agent/opa/internal/uuid/uuid.go @@ -5,27 +5,36 @@ package uuid import ( + "encoding/hex" "fmt" "io" "strings" "github.com/google/uuid" + "github.com/open-policy-agent/opa/v1/util" ) -const ( - BILLION = 1000000000 -) +const BILLION = 1000000000 // New Create a version 4 random UUID func New(r io.Reader) (string, error) { - bs := make([]byte, 16) - n, err := io.ReadFull(r, bs) - if n != len(bs) || err != nil { + var arr [52]byte // arr, same buffer for both src (16) and dst (36) + src := arr[:16] // src, bytes to encode + dst := arr[16:16:52] // dst, to encode, len 0, cap 36 (for appending) + + n, err := io.ReadFull(r, src) + if n != 16 || err != nil { return "", err } - bs[8] = bs[8]&^0xc0 | 0x80 - bs[6] = bs[6]&^0xf0 | 0x40 - return fmt.Sprintf("%x-%x-%x-%x-%x", bs[0:4], bs[4:6], bs[6:8], bs[8:10], bs[10:]), nil + src[8] = src[8]&^0xc0 | 0x80 + src[6] = src[6]&^0xf0 | 0x40 + + dst = append(hex.AppendEncode(dst, src[:4]), '-') + dst = append(hex.AppendEncode(dst, src[4:6]), '-') + dst = append(hex.AppendEncode(dst, src[6:8]), '-') + dst = append(hex.AppendEncode(dst, src[8:10]), '-') + + return util.ByteSliceToString(hex.AppendEncode(dst, src[10:])), nil } // Parse will use the google/uuid library to parse the string into a uuid diff --git a/vendor/github.com/open-policy-agent/opa/internal/version/version.go b/vendor/github.com/open-policy-agent/opa/internal/version/version.go index 2655c9d716..8d1237bce3 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/version/version.go +++ b/vendor/github.com/open-policy-agent/opa/internal/version/version.go @@ -7,7 +7,6 @@ package version import ( "context" - "fmt" "runtime" "github.com/open-policy-agent/opa/v1/storage" @@ -19,7 +18,6 @@ var versionPath = storage.MustParsePath("/system/version") // Write the build version information into storage. This makes the // version information available to the REPL and the HTTP server. func Write(ctx context.Context, store storage.Store, txn storage.Transaction) error { - if err := storage.MakeDir(ctx, store, txn, versionPath); err != nil { return err } @@ -33,4 +31,4 @@ func Write(ctx context.Context, store storage.Store, txn storage.Transaction) er } // UserAgent defines the current OPA instances User-Agent default header value. -var UserAgent = fmt.Sprintf("Open-Policy-Agent/%s (%s, %s)", version.Version, runtime.GOOS, runtime.GOARCH) +var UserAgent = "Open-Policy-Agent/" + version.Version + " (" + runtime.GOOS + ", " + runtime.GOARCH + ")" diff --git a/vendor/github.com/open-policy-agent/opa/internal/wasm/encoding/reader.go b/vendor/github.com/open-policy-agent/opa/internal/wasm/encoding/reader.go index a2f23d9a64..559a61696d 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/wasm/encoding/reader.go +++ b/vendor/github.com/open-policy-agent/opa/internal/wasm/encoding/reader.go @@ -651,11 +651,8 @@ func readExport(r io.Reader, exp *module.Export) error { } exp.Descriptor.Index, err = leb128.ReadVarUint32(r) - if err != nil { - return err - } - return nil + return err } func readElementSegment(r io.Reader, seg *module.ElementSegment) error { diff --git a/vendor/github.com/open-policy-agent/opa/internal/wasm/sdk/opa/capabilities/capabilities_nowasm.go b/vendor/github.com/open-policy-agent/opa/internal/wasm/sdk/opa/capabilities/capabilities_nowasm.go index 6b17984bb8..93fd1c5e80 100644 --- a/vendor/github.com/open-policy-agent/opa/internal/wasm/sdk/opa/capabilities/capabilities_nowasm.go +++ b/vendor/github.com/open-policy-agent/opa/internal/wasm/sdk/opa/capabilities/capabilities_nowasm.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !opa_wasm && !generate -// +build !opa_wasm,!generate package capabilities diff --git a/vendor/github.com/open-policy-agent/opa/internal/yaml/yaml.go b/vendor/github.com/open-policy-agent/opa/internal/yaml/yaml.go new file mode 100644 index 0000000000..9f6e53a57f --- /dev/null +++ b/vendor/github.com/open-policy-agent/opa/internal/yaml/yaml.go @@ -0,0 +1,340 @@ +// Copyright 2026 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +// Package yaml provides YAML <-> JSON conversion for OPA, on top of +// go.yaml.in/yaml/v3. +// +// It replaces sigs.k8s.io/yaml, which is pinned to go.yaml.in/yaml/v2 and +// therefore resolves YAML 1.1 boolean spellings (on/off/yes/no) in positions +// where the YAML 1.2 core schema calls for strings. +package yaml + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "reflect" + "strconv" + + "go.yaml.in/yaml/v3" +) + +// Marshal serializes obj as YAML. obj is first round-tripped through +// encoding/json so that `json` struct tags and json.Marshaler +// implementations are honoured, matching the behaviour callers relied on +// from sigs.k8s.io/yaml. +func Marshal(obj any) ([]byte, error) { + bs, err := json.Marshal(obj) + if err != nil { + return nil, fmt.Errorf("error marshaling into JSON: %w", err) + } + var jsonObj any + if err := yaml.Unmarshal(bs, &jsonObj); err != nil { + return nil, err + } + return marshalYAML(jsonObj) +} + +// marshalYAML emits YAML at 2-space indentation. go-yaml v3 defaults to 4, +// where sigs.k8s.io/yaml (on go-yaml v2) emitted 2. +func marshalYAML(obj any) ([]byte, error) { + var buf bytes.Buffer + enc := yaml.NewEncoder(&buf) + enc.SetIndent(2) + if err := enc.Encode(obj); err != nil { + _ = enc.Close() + return nil, err + } + if err := enc.Close(); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// JSONOpt configures the encoding/json decoder used by Unmarshal. +type JSONOpt func(*json.Decoder) *json.Decoder + +// Unmarshal decodes a YAML document into obj, using encoding/json semantics +// (`json` struct tags, json.Unmarshaler) rather than go-yaml's. +func Unmarshal(bs []byte, obj any, opts ...JSONOpt) error { + js, err := YAMLToJSON(bs) + if err != nil { + return err + } + d := json.NewDecoder(bytes.NewReader(js)) + for _, opt := range opts { + d = opt(d) + } + if err := d.Decode(obj); err != nil { + return fmt.Errorf("error unmarshaling JSON: %w", err) + } + return nil +} + +// YAMLToJSON converts a single YAML document to JSON. Input holding more than +// one document keeps its historical meaning - only the first is converted - +// but the rest of the stream still has to parse. +func YAMLToJSON(bs []byte) ([]byte, error) { + node, err := firstDocument(bs) + if err != nil { + return nil, err + } + + var obj any + if node != nil { + normalize(node, map[*yaml.Node]struct{}{}) + if err := node.Decode(&obj); err != nil { + return nil, err + } + } + + obj, err = jsonable(obj) + if err != nil { + return nil, err + } + return json.Marshal(obj) +} + +// firstDocument returns the first document in bs, or nil if bs holds none. +// +// The remaining documents are parsed and discarded. go-yaml stops reading at +// the end of the first document, so without this a syntax error further into +// the input is never reported: `" a:\nb: 1"` closes the mapping at the dedent +// and silently drops `b: 1`, rather than failing the way the YAML spec calls +// for (issue 6854). +func firstDocument(bs []byte) (*yaml.Node, error) { + dec := yaml.NewDecoder(bytes.NewReader(bs)) + + var first *yaml.Node + for { + var node yaml.Node + if err := dec.Decode(&node); err != nil { + if errors.Is(err, io.EOF) { + return first, nil + } + return nil, err + } + if first == nil { + first = &node + } + } +} + +// normalize rewrites the node tree before it is decoded, so that documents +// go-yaml v2 accepted keep working under v3. +// +// Implicitly resolved !!timestamp scalars are re-tagged !!str. !!timestamp is +// a YAML 1.1 type that go-yaml v3 still resolves in the core schema; leaving +// it in place would silently rewrite `2023-01-01` to `2023-01-01T00:00:00Z` +// on the way to JSON. +// +// Repeated merge keys are folded into the sequence form, and duplicate +// mapping keys are collapsed to the last occurrence. go-yaml v3 rejects both +// outright; go-yaml v2 accepted them, and turning documents that load today +// into hard errors is a bigger change than this package is trying to make. +// +// Anchors make the node graph a DAG, so visited guards against re-walking a +// shared subtree. +func normalize(n *yaml.Node, visited map[*yaml.Node]struct{}) { + if n == nil { + return + } + if _, ok := visited[n]; ok { + return + } + visited[n] = struct{}{} + + switch n.Kind { + case yaml.ScalarNode: + if n.Tag == "!!timestamp" && n.Style == 0 { + n.Tag = "!!str" + } + case yaml.MappingNode: + n.Content = collapseMergeKeys(n.Content) + n.Content = dedupeKeys(n.Content) + } + + normalize(n.Alias, visited) + for _, c := range n.Content { + normalize(c, visited) + } +} + +// collapseMergeKeys rewrites a mapping that repeats `<<` into the spec's +// sequence form (`<<: [a, b]`), which go-yaml v3 accepts. go-yaml v2 applied +// repeated merge keys in document order, so preserve that order. +func collapseMergeKeys(content []*yaml.Node) []*yaml.Node { + first := -1 + var merged []*yaml.Node + + for i := 0; i+1 < len(content); i += 2 { + if content[i].Kind != yaml.ScalarNode || content[i].Tag != "!!merge" { + continue + } + if first < 0 { + first = i + } + if v := content[i+1]; v.Kind == yaml.SequenceNode { + merged = append(merged, v.Content...) + } else { + merged = append(merged, v) + } + } + + if first < 0 || len(merged) < 2 { + return content + } + + out := make([]*yaml.Node, 0, len(content)) + for i := 0; i+1 < len(content); i += 2 { + switch { + case i == first: + out = append(out, content[i], &yaml.Node{ + Kind: yaml.SequenceNode, + Tag: "!!seq", + Content: merged, + }) + case content[i].Kind == yaml.ScalarNode && content[i].Tag == "!!merge": + // dropped; folded into the sequence above + default: + out = append(out, content[i], content[i+1]) + } + } + return out +} + +// dedupeKeys drops all but the last occurrence of each key in a mapping's +// flattened key/value Content slice, preserving the position of the first +// occurrence the way a last-wins map assignment would. +// +// go-yaml v3 rejects duplicate keys outright, but go-yaml v2 accepted them, +// and turning documents that load today into hard errors is a bigger change +// than this package is trying to make. Keys are compared by the string they +// will occupy in the resulting JSON object, so `1` and `"1"` collide here the +// same way they would there. +func dedupeKeys(content []*yaml.Node) []*yaml.Node { + seen := make(map[string]int, len(content)/2) + dropped := false + + for i := 0; i+1 < len(content); i += 2 { + k := content[i] + // Merge keys are not real keys, and non-scalar keys have no JSON + // representation - both are handled elsewhere. + if k.Kind != yaml.ScalarNode || k.Tag == "!!merge" { + continue + } + var kv any + if err := k.Decode(&kv); err != nil { + continue + } + id, ok := keyString(kv) + if !ok { + continue + } + if prevVal, ok := seen[id]; ok { + // Keep the earlier key node's position, take the later value. + content[prevVal] = content[i+1] + content[i], content[i+1] = nil, nil + dropped = true + continue + } + seen[id] = i + 1 + } + + if !dropped { + return content + } + + out := content[:0] + for _, n := range content { + if n != nil { + out = append(out, n) + } + } + return out +} + +// JSONToYAML converts JSON to YAML, preserving nothing but the value. +func JSONToYAML(bs []byte) ([]byte, error) { + var obj any + // json.Number would be re-encoded as a quoted string by go-yaml, so decode + // numbers as float64 the way encoding/json does by default. + if err := json.Unmarshal(bs, &obj); err != nil { + return nil, err + } + return marshalYAML(obj) +} + +// jsonable rewrites the result of a go-yaml decode into something +// encoding/json can marshal: YAML permits mapping keys of any type, JSON +// only permits strings. +func jsonable(x any) (any, error) { + switch x := x.(type) { + case map[string]any: + for k, v := range x { + v, err := jsonable(v) + if err != nil { + return nil, err + } + x[k] = v + } + return x, nil + case map[any]any: + out := make(map[string]any, len(x)) + for k, v := range x { + ks, ok := keyString(k) + if !ok { + return nil, fmt.Errorf("unsupported map key of type: %s, key: %+#v, value: %+#v", reflect.TypeOf(k), k, v) + } + v, err := jsonable(v) + if err != nil { + return nil, err + } + out[ks] = v + } + return out, nil + case []any: + for i, v := range x { + v, err := jsonable(v) + if err != nil { + return nil, err + } + x[i] = v + } + return x, nil + default: + return x, nil + } +} + +func keyString(k any) (string, bool) { + switch k := k.(type) { + case string: + return k, true + case int: + return strconv.Itoa(k), true + case int64: + return strconv.FormatInt(k, 10), true + case uint64: + return strconv.FormatUint(k, 10), true + case float64: + // Match how go-yaml renders floats when marshaling. + switch s := strconv.FormatFloat(k, 'g', -1, 32); s { + case "+Inf": + return ".inf", true + case "-Inf": + return "-.inf", true + case "NaN": + return ".nan", true + default: + return s, true + } + case bool: + return strconv.FormatBool(k), true + default: + return "", false + } +} diff --git a/vendor/github.com/open-policy-agent/opa/loader/doc.go b/vendor/github.com/open-policy-agent/opa/loader/doc.go deleted file mode 100644 index 9f60920d95..0000000000 --- a/vendor/github.com/open-policy-agent/opa/loader/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2024 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. -// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. -// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. -package loader diff --git a/vendor/github.com/open-policy-agent/opa/loader/errors.go b/vendor/github.com/open-policy-agent/opa/loader/errors.go deleted file mode 100644 index 8dc70b8673..0000000000 --- a/vendor/github.com/open-policy-agent/opa/loader/errors.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2017 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package loader - -import ( - v1 "github.com/open-policy-agent/opa/v1/loader" -) - -// Errors is a wrapper for multiple loader errors. -type Errors = v1.Errors diff --git a/vendor/github.com/open-policy-agent/opa/loader/loader.go b/vendor/github.com/open-policy-agent/opa/loader/loader.go index a319f2c64d..8f7f1f6b32 100644 --- a/vendor/github.com/open-policy-agent/opa/loader/loader.go +++ b/vendor/github.com/open-policy-agent/opa/loader/loader.go @@ -1,7 +1,11 @@ -// Copyright 2017 The OPA Authors. All rights reserved. +// Copyright 2026 The OPA Authors. All rights reserved. // Use of this source code is governed by an Apache2 // license that can be found in the LICENSE file. +// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. +// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. +// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. +// // Package loader contains utilities for loading files into OPA. package loader @@ -15,6 +19,9 @@ import ( v1 "github.com/open-policy-agent/opa/v1/loader" ) +// Errors is a wrapper for multiple loader errors. +type Errors = v1.Errors + // Result represents the result of successfully loading zero or more files. type Result = v1.Result diff --git a/vendor/github.com/open-policy-agent/opa/rego/doc.go b/vendor/github.com/open-policy-agent/opa/rego/doc.go deleted file mode 100644 index febe75696c..0000000000 --- a/vendor/github.com/open-policy-agent/opa/rego/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2024 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. -// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. -// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. -package rego diff --git a/vendor/github.com/open-policy-agent/opa/rego/errors.go b/vendor/github.com/open-policy-agent/opa/rego/errors.go deleted file mode 100644 index bcbd2efedd..0000000000 --- a/vendor/github.com/open-policy-agent/opa/rego/errors.go +++ /dev/null @@ -1,17 +0,0 @@ -package rego - -import v1 "github.com/open-policy-agent/opa/v1/rego" - -// HaltError is an error type to return from a custom function implementation -// that will abort the evaluation process (analogous to topdown.Halt). -type HaltError = v1.HaltError - -// NewHaltError wraps an error such that the evaluation process will stop -// when it occurs. -func NewHaltError(err error) error { - return v1.NewHaltError(err) -} - -// ErrorDetails interface is satisfied by an error that provides further -// details. -type ErrorDetails = v1.ErrorDetails diff --git a/vendor/github.com/open-policy-agent/opa/rego/plugins.go b/vendor/github.com/open-policy-agent/opa/rego/plugins.go deleted file mode 100644 index 38ef84416f..0000000000 --- a/vendor/github.com/open-policy-agent/opa/rego/plugins.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2023 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package rego - -import ( - v1 "github.com/open-policy-agent/opa/v1/rego" -) - -type TargetPlugin = v1.TargetPlugin - -type TargetPluginEval = v1.TargetPluginEval - -func RegisterPlugin(name string, p TargetPlugin) { - v1.RegisterPlugin(name, p) -} diff --git a/vendor/github.com/open-policy-agent/opa/rego/rego.go b/vendor/github.com/open-policy-agent/opa/rego/rego.go index c9caf9f8cb..76cfc572b8 100644 --- a/vendor/github.com/open-policy-agent/opa/rego/rego.go +++ b/vendor/github.com/open-policy-agent/opa/rego/rego.go @@ -1,7 +1,11 @@ -// Copyright 2017 The OPA Authors. All rights reserved. +// Copyright 2026 The OPA Authors. All rights reserved. // Use of this source code is governed by an Apache2 // license that can be found in the LICENSE file. +// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. +// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. +// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. +// // Package rego exposes high level APIs for evaluating Rego policies. package rego @@ -23,6 +27,28 @@ import ( "github.com/open-policy-agent/opa/v1/tracing" ) +// HaltError is an error type to return from a custom function implementation +// that will abort the evaluation process (analogous to topdown.Halt). +type HaltError = v1.HaltError + +// NewHaltError wraps an error such that the evaluation process will stop +// when it occurs. +func NewHaltError(err error) error { + return v1.NewHaltError(err) +} + +// ErrorDetails interface is satisfied by an error that provides further +// details. +type ErrorDetails = v1.ErrorDetails + +type TargetPlugin = v1.TargetPlugin + +type TargetPluginEval = v1.TargetPluginEval + +func RegisterPlugin(name string, p TargetPlugin) { + v1.RegisterPlugin(name, p) +} + // CompileResult represents the result of compiling a Rego query, zero or more // Rego modules, and arbitrary contextual data into an executable. type CompileResult = v1.CompileResult @@ -636,3 +662,20 @@ func WithNoInline(paths []string) PrepareOption { func WithBuiltinFuncs(bis map[string]*topdown.Builtin) PrepareOption { return v1.WithBuiltinFuncs(bis) } + +// ResultSet represents a collection of output from Rego evaluation. An empty +// result set represents an undefined query. +type ResultSet = v1.ResultSet + +// Vars represents a collection of variable bindings. The keys are the variable +// names and the values are the binding values. +type Vars = v1.Vars + +// Result defines the output of Rego evaluation. +type Result = v1.Result + +// Location defines a position in a Rego query or module. +type Location = v1.Location + +// ExpressionValue defines the value of an expression in a Rego query. +type ExpressionValue = v1.ExpressionValue diff --git a/vendor/github.com/open-policy-agent/opa/rego/resultset.go b/vendor/github.com/open-policy-agent/opa/rego/resultset.go deleted file mode 100644 index 5c03360dfa..0000000000 --- a/vendor/github.com/open-policy-agent/opa/rego/resultset.go +++ /dev/null @@ -1,22 +0,0 @@ -package rego - -import ( - v1 "github.com/open-policy-agent/opa/v1/rego" -) - -// ResultSet represents a collection of output from Rego evaluation. An empty -// result set represents an undefined query. -type ResultSet = v1.ResultSet - -// Vars represents a collection of variable bindings. The keys are the variable -// names and the values are the binding values. -type Vars = v1.Vars - -// Result defines the output of Rego evaluation. -type Result = v1.Result - -// Location defines a position in a Rego query or module. -type Location = v1.Location - -// ExpressionValue defines the value of an expression in a Rego query. -type ExpressionValue = v1.ExpressionValue diff --git a/vendor/github.com/open-policy-agent/opa/storage/doc.go b/vendor/github.com/open-policy-agent/opa/storage/doc.go deleted file mode 100644 index c33db689ed..0000000000 --- a/vendor/github.com/open-policy-agent/opa/storage/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -// Package storage exposes the policy engine's storage layer. -// -// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. -// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. -// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. -package storage diff --git a/vendor/github.com/open-policy-agent/opa/storage/errors.go b/vendor/github.com/open-policy-agent/opa/storage/errors.go deleted file mode 100644 index 1403b3a988..0000000000 --- a/vendor/github.com/open-policy-agent/opa/storage/errors.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package storage - -import ( - v1 "github.com/open-policy-agent/opa/v1/storage" -) - -const ( - // InternalErr indicates an unknown, internal error has occurred. - InternalErr = v1.InternalErr - - // NotFoundErr indicates the path used in the storage operation does not - // locate a document. - NotFoundErr = v1.NotFoundErr - - // WriteConflictErr indicates a write on the path enocuntered a conflicting - // value inside the transaction. - WriteConflictErr = v1.WriteConflictErr - - // InvalidPatchErr indicates an invalid patch/write was issued. The patch - // was rejected. - InvalidPatchErr = v1.InvalidPatchErr - - // InvalidTransactionErr indicates an invalid operation was performed - // inside of the transaction. - InvalidTransactionErr = v1.InvalidTransactionErr - - // TriggersNotSupportedErr indicates the caller attempted to register a - // trigger against a store that does not support them. - TriggersNotSupportedErr = v1.TriggersNotSupportedErr - - // WritesNotSupportedErr indicate the caller attempted to perform a write - // against a store that does not support them. - WritesNotSupportedErr = v1.WritesNotSupportedErr - - // PolicyNotSupportedErr indicate the caller attempted to perform a policy - // management operation against a store that does not support them. - PolicyNotSupportedErr = v1.PolicyNotSupportedErr -) - -// Error is the error type returned by the storage layer. -type Error = v1.Error - -// IsNotFound returns true if this error is a NotFoundErr. -func IsNotFound(err error) bool { - return v1.IsNotFound(err) -} - -// IsWriteConflictError returns true if this error a WriteConflictErr. -func IsWriteConflictError(err error) bool { - return v1.IsWriteConflictError(err) -} - -// IsInvalidPatch returns true if this error is a InvalidPatchErr. -func IsInvalidPatch(err error) bool { - return v1.IsInvalidPatch(err) -} - -// IsInvalidTransaction returns true if this error is a InvalidTransactionErr. -func IsInvalidTransaction(err error) bool { - return v1.IsInvalidTransaction(err) -} - -// IsIndexingNotSupported is a stub for backwards-compatibility. -// -// Deprecated: We no longer return IndexingNotSupported errors, so it is -// unnecessary to check for them. -func IsIndexingNotSupported(err error) bool { - return v1.IsIndexingNotSupported(err) -} diff --git a/vendor/github.com/open-policy-agent/opa/storage/interface.go b/vendor/github.com/open-policy-agent/opa/storage/interface.go deleted file mode 100644 index a21b5575e9..0000000000 --- a/vendor/github.com/open-policy-agent/opa/storage/interface.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package storage - -import ( - v1 "github.com/open-policy-agent/opa/v1/storage" -) - -// Transaction defines the interface that identifies a consistent snapshot over -// the policy engine's storage layer. -type Transaction = v1.Transaction - -// Store defines the interface for the storage layer's backend. -type Store = v1.Store - -// MakeDirer defines the interface a Store could realize to override the -// generic MakeDir functionality in storage.MakeDir -type MakeDirer = v1.MakeDirer - -// NonEmptyer allows a store implemention to override NonEmpty()) -type NonEmptyer = v1.NonEmptyer - -// TransactionParams describes a new transaction. -type TransactionParams = v1.TransactionParams - -// Context is a simple container for key/value pairs. -type Context = v1.Context - -// NewContext returns a new context object. -func NewContext() *Context { - return v1.NewContext() -} - -// WriteParams specifies the TransactionParams for a write transaction. -var WriteParams = v1.WriteParams - -// PatchOp is the enumeration of supposed modifications. -type PatchOp = v1.PatchOp - -// Patch supports add, remove, and replace operations. -const ( - AddOp = v1.AddOp - RemoveOp = v1.RemoveOp - ReplaceOp = v1.ReplaceOp -) - -// WritesNotSupported provides a default implementation of the write -// interface which may be used if the backend does not support writes. -type WritesNotSupported = v1.WritesNotSupported - -// Policy defines the interface for policy module storage. -type Policy = v1.Policy - -// PolicyNotSupported provides a default implementation of the policy interface -// which may be used if the backend does not support policy storage. -type PolicyNotSupported = v1.PolicyNotSupported - -// PolicyEvent describes a change to a policy. -type PolicyEvent = v1.PolicyEvent - -// DataEvent describes a change to a base data document. -type DataEvent = v1.DataEvent - -// TriggerEvent describes the changes that caused the trigger to be invoked. -type TriggerEvent = v1.TriggerEvent - -// TriggerConfig contains the trigger registration configuration. -type TriggerConfig = v1.TriggerConfig - -// Trigger defines the interface that stores implement to register for change -// notifications when the store is changed. -type Trigger = v1.Trigger - -// TriggersNotSupported provides default implementations of the Trigger -// interface which may be used if the backend does not support triggers. -type TriggersNotSupported = v1.TriggersNotSupported - -// TriggerHandle defines the interface that can be used to unregister triggers that have -// been registered on a Store. -type TriggerHandle = v1.TriggerHandle - -// Iterator defines the interface that can be used to read files from a directory starting with -// files at the base of the directory, then sub-directories etc. -type Iterator = v1.Iterator - -// Update contains information about a file -type Update = v1.Update diff --git a/vendor/github.com/open-policy-agent/opa/storage/path.go b/vendor/github.com/open-policy-agent/opa/storage/path.go deleted file mode 100644 index 91d4f34f2b..0000000000 --- a/vendor/github.com/open-policy-agent/opa/storage/path.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package storage - -import ( - "github.com/open-policy-agent/opa/ast" - v1 "github.com/open-policy-agent/opa/v1/storage" -) - -// Path refers to a document in storage. -type Path = v1.Path - -// ParsePath returns a new path for the given str. -func ParsePath(str string) (path Path, ok bool) { - return v1.ParsePath(str) -} - -// ParsePathEscaped returns a new path for the given escaped str. -func ParsePathEscaped(str string) (path Path, ok bool) { - return v1.ParsePathEscaped(str) -} - -// NewPathForRef returns a new path for the given ref. -func NewPathForRef(ref ast.Ref) (path Path, err error) { - return v1.NewPathForRef(ref) -} - -// MustParsePath returns a new Path for s. If s cannot be parsed, this function -// will panic. This is mostly for test purposes. -func MustParsePath(s string) Path { - return v1.MustParsePath(s) -} diff --git a/vendor/github.com/open-policy-agent/opa/storage/storage.go b/vendor/github.com/open-policy-agent/opa/storage/storage.go index d1abc1046d..c71e63c4ab 100644 --- a/vendor/github.com/open-policy-agent/opa/storage/storage.go +++ b/vendor/github.com/open-policy-agent/opa/storage/storage.go @@ -1,15 +1,189 @@ -// Copyright 2016 The OPA Authors. All rights reserved. +// Copyright 2026 The OPA Authors. All rights reserved. // Use of this source code is governed by an Apache2 // license that can be found in the LICENSE file. +// Package storage exposes the policy engine's storage layer. +// +// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. +// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. +// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. package storage import ( "context" + "github.com/open-policy-agent/opa/ast" v1 "github.com/open-policy-agent/opa/v1/storage" ) +const ( + // InternalErr indicates an unknown, internal error has occurred. + InternalErr = v1.InternalErr + + // NotFoundErr indicates the path used in the storage operation does not + // locate a document. + NotFoundErr = v1.NotFoundErr + + // WriteConflictErr indicates a write on the path enocuntered a conflicting + // value inside the transaction. + WriteConflictErr = v1.WriteConflictErr + + // InvalidPatchErr indicates an invalid patch/write was issued. The patch + // was rejected. + InvalidPatchErr = v1.InvalidPatchErr + + // InvalidTransactionErr indicates an invalid operation was performed + // inside of the transaction. + InvalidTransactionErr = v1.InvalidTransactionErr + + // TriggersNotSupportedErr indicates the caller attempted to register a + // trigger against a store that does not support them. + TriggersNotSupportedErr = v1.TriggersNotSupportedErr + + // WritesNotSupportedErr indicate the caller attempted to perform a write + // against a store that does not support them. + WritesNotSupportedErr = v1.WritesNotSupportedErr + + // PolicyNotSupportedErr indicate the caller attempted to perform a policy + // management operation against a store that does not support them. + PolicyNotSupportedErr = v1.PolicyNotSupportedErr +) + +// Error is the error type returned by the storage layer. +type Error = v1.Error + +// IsNotFound returns true if this error is a NotFoundErr. +func IsNotFound(err error) bool { + return v1.IsNotFound(err) +} + +// IsWriteConflictError returns true if this error a WriteConflictErr. +func IsWriteConflictError(err error) bool { + return v1.IsWriteConflictError(err) +} + +// IsInvalidPatch returns true if this error is a InvalidPatchErr. +func IsInvalidPatch(err error) bool { + return v1.IsInvalidPatch(err) +} + +// IsInvalidTransaction returns true if this error is a InvalidTransactionErr. +func IsInvalidTransaction(err error) bool { + return v1.IsInvalidTransaction(err) +} + +// IsIndexingNotSupported is a stub for backwards-compatibility. +// +// Deprecated: We no longer return IndexingNotSupported errors, so it is +// unnecessary to check for them. +func IsIndexingNotSupported(err error) bool { + return v1.IsIndexingNotSupported(err) +} + +// Transaction defines the interface that identifies a consistent snapshot over +// the policy engine's storage layer. +type Transaction = v1.Transaction + +// Store defines the interface for the storage layer's backend. +type Store = v1.Store + +// MakeDirer defines the interface a Store could realize to override the +// generic MakeDir functionality in storage.MakeDir +type MakeDirer = v1.MakeDirer + +// NonEmptyer allows a store implemention to override NonEmpty()) +type NonEmptyer = v1.NonEmptyer + +// TransactionParams describes a new transaction. +type TransactionParams = v1.TransactionParams + +// Context is a simple container for key/value pairs. +type Context = v1.Context + +// NewContext returns a new context object. +func NewContext() *Context { + return v1.NewContext() +} + +// WriteParams specifies the TransactionParams for a write transaction. +var WriteParams = v1.WriteParams + +// PatchOp is the enumeration of supposed modifications. +type PatchOp = v1.PatchOp + +// Patch supports add, remove, and replace operations. +const ( + AddOp = v1.AddOp + RemoveOp = v1.RemoveOp + ReplaceOp = v1.ReplaceOp +) + +// WritesNotSupported provides a default implementation of the write +// interface which may be used if the backend does not support writes. +type WritesNotSupported = v1.WritesNotSupported + +// Policy defines the interface for policy module storage. +type Policy = v1.Policy + +// PolicyNotSupported provides a default implementation of the policy interface +// which may be used if the backend does not support policy storage. +type PolicyNotSupported = v1.PolicyNotSupported + +// PolicyEvent describes a change to a policy. +type PolicyEvent = v1.PolicyEvent + +// DataEvent describes a change to a base data document. +type DataEvent = v1.DataEvent + +// TriggerEvent describes the changes that caused the trigger to be invoked. +type TriggerEvent = v1.TriggerEvent + +// TriggerConfig contains the trigger registration configuration. +type TriggerConfig = v1.TriggerConfig + +// Trigger defines the interface that stores implement to register for change +// notifications when the store is changed. +type Trigger = v1.Trigger + +// TriggersNotSupported provides default implementations of the Trigger +// interface which may be used if the backend does not support triggers. +type TriggersNotSupported = v1.TriggersNotSupported + +// TriggerHandle defines the interface that can be used to unregister triggers that have +// been registered on a Store. +type TriggerHandle = v1.TriggerHandle + +// Iterator defines the interface that can be used to read files from a directory starting with +// files at the base of the directory, then sub-directories etc. +type Iterator = v1.Iterator + +// Update contains information about a file +type Update = v1.Update + +// Path refers to a document in storage. +type Path = v1.Path + +// ParsePath returns a new path for the given str. +func ParsePath(str string) (path Path, ok bool) { + return v1.ParsePath(str) +} + +// ParsePathEscaped returns a new path for the given escaped str. +func ParsePathEscaped(str string) (path Path, ok bool) { + return v1.ParsePathEscaped(str) +} + +// NewPathForRef returns a new path for the given ref. +func NewPathForRef(ref ast.Ref) (path Path, err error) { + return v1.NewPathForRef(ref) +} + +// MustParsePath returns a new Path for s. If s cannot be parsed, this function +// will panic. This is mostly for test purposes. +func MustParsePath(s string) Path { + return v1.MustParsePath(s) +} + // NewTransactionOrDie is a helper function to create a new transaction. If the // storage layer cannot create a new transaction, this function will panic. This // function should only be used for tests. diff --git a/vendor/github.com/open-policy-agent/opa/topdown/print/doc.go b/vendor/github.com/open-policy-agent/opa/topdown/print/doc.go deleted file mode 100644 index c2ee0eca7f..0000000000 --- a/vendor/github.com/open-policy-agent/opa/topdown/print/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2016 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. -// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. -// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. -package print diff --git a/vendor/github.com/open-policy-agent/opa/topdown/print/print.go b/vendor/github.com/open-policy-agent/opa/topdown/print/print.go index 66ffbb176f..9ea2ce2d0c 100644 --- a/vendor/github.com/open-policy-agent/opa/topdown/print/print.go +++ b/vendor/github.com/open-policy-agent/opa/topdown/print/print.go @@ -1,3 +1,10 @@ +// Copyright 2026 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. +// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. +// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. package print import ( diff --git a/vendor/github.com/open-policy-agent/opa/types/decode.go b/vendor/github.com/open-policy-agent/opa/types/decode.go deleted file mode 100644 index ae04b38ff4..0000000000 --- a/vendor/github.com/open-policy-agent/opa/types/decode.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2020 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package types - -import ( - v1 "github.com/open-policy-agent/opa/v1/types" -) - -// Unmarshal deserializes bs and returns the resulting type. -func Unmarshal(bs []byte) (result Type, err error) { - return v1.Unmarshal(bs) -} diff --git a/vendor/github.com/open-policy-agent/opa/types/doc.go b/vendor/github.com/open-policy-agent/opa/types/doc.go deleted file mode 100644 index bfa068e66b..0000000000 --- a/vendor/github.com/open-policy-agent/opa/types/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2024 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. -// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. -// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. -package types diff --git a/vendor/github.com/open-policy-agent/opa/types/types.go b/vendor/github.com/open-policy-agent/opa/types/types.go index 0dd428de7f..2969108e47 100644 --- a/vendor/github.com/open-policy-agent/opa/types/types.go +++ b/vendor/github.com/open-policy-agent/opa/types/types.go @@ -1,7 +1,11 @@ -// Copyright 2017 The OPA Authors. All rights reserved. +// Copyright 2026 The OPA Authors. All rights reserved. // Use of this source code is governed by an Apache2 // license that can be found in the LICENSE file. +// Deprecated: This package is intended for older projects transitioning from OPA v0.x and will remain for the lifetime of OPA v1.x, but its use is not recommended. +// For newer features and behaviours, such as defaulting to the Rego v1 syntax, use the corresponding components in the [github.com/open-policy-agent/opa/v1] package instead. +// See https://www.openpolicyagent.org/docs/latest/v0-compatibility/ for more information. +// // Package types declares data types for Rego values and helper functions to // operate on these types. package types @@ -10,6 +14,11 @@ import ( v1 "github.com/open-policy-agent/opa/v1/types" ) +// Unmarshal deserializes bs and returns the resulting type. +func Unmarshal(bs []byte) (result Type, err error) { + return v1.Unmarshal(bs) +} + // Sprint returns the string representation of the type. func Sprint(x Type) string { return v1.Sprint(x) diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/annotations.go b/vendor/github.com/open-policy-agent/opa/v1/ast/annotations.go index 2b6a83eeeb..0b25692602 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/annotations.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/annotations.go @@ -95,6 +95,14 @@ func (a *Annotations) String() string { return string(bs) } +func (a *Annotations) AppendText(buf []byte) ([]byte, error) { + bs, err := a.MarshalJSON() + if err == nil { + buf = append(buf, bs...) + } + return buf, err +} + // Loc returns the location of this annotation. func (a *Annotations) Loc() *Location { return a.Location @@ -107,24 +115,18 @@ func (a *Annotations) SetLoc(l *Location) { // EndLoc returns the location of this annotation's last comment line. func (a *Annotations) EndLoc() *Location { - if a.endLoc == nil { - return a.Location - } - return a.endLoc + return util.NilOr(a.endLoc, a.Location) } // Compare returns an integer indicating if a is less than, equal to, or greater // than other. func (a *Annotations) Compare(other *Annotations) int { - - if a == nil && other == nil { + if a == other { return 0 } - if a == nil { return -1 } - if other == nil { return 1 } @@ -141,19 +143,19 @@ func (a *Annotations) Compare(other *Annotations) int { return cmp } - if cmp := compareStringLists(a.Organizations, other.Organizations); cmp != 0 { + if cmp := slices.Compare(a.Organizations, other.Organizations); cmp != 0 { return cmp } - if cmp := compareRelatedResources(a.RelatedResources, other.RelatedResources); cmp != 0 { + if cmp := slices.CompareFunc(a.RelatedResources, other.RelatedResources, (*RelatedResourceAnnotation).Compare); cmp != 0 { return cmp } - if cmp := compareAuthors(a.Authors, other.Authors); cmp != 0 { + if cmp := slices.CompareFunc(a.Authors, other.Authors, (*AuthorAnnotation).Compare); cmp != 0 { return cmp } - if cmp := compareSchemas(a.Schemas, other.Schemas); cmp != 0 { + if cmp := slices.CompareFunc(a.Schemas, other.Schemas, (*SchemaAnnotation).Compare); cmp != 0 { return cmp } @@ -172,11 +174,7 @@ func (a *Annotations) Compare(other *Annotations) int { return cmp } - if cmp := util.Compare(a.Labels, other.Labels); cmp != 0 { - return cmp - } - - return 0 + return util.Compare(a.Labels, other.Labels) } // GetTargetPath returns the path of the node these Annotations are applied to (the target) @@ -217,12 +215,10 @@ func (ar *AnnotationsRef) GetPackage() *Package { } func (ar *AnnotationsRef) GetRule() *Rule { - switch n := ar.node.(type) { - case *Rule: - return n - default: - return nil + if r, ok := ar.node.(*Rule); ok { + return r } + return nil } func scopeCompare(s1, s2 string) int { @@ -251,188 +247,81 @@ func scopeOrder(s string) int { return 0 } -func compareAuthors(a, b []*AuthorAnnotation) int { - if len(a) > len(b) { - return 1 - } else if len(a) < len(b) { - return -1 - } - - for i := range a { - if cmp := a[i].Compare(b[i]); cmp != 0 { - return cmp - } - } - - return 0 -} - -func compareRelatedResources(a, b []*RelatedResourceAnnotation) int { - if len(a) > len(b) { - return 1 - } else if len(a) < len(b) { - return -1 - } - - for i := range a { - if cmp := a[i].Compare(b[i]); cmp != 0 { - return cmp - } - } - - return 0 -} - -func compareSchemas(a, b []*SchemaAnnotation) int { - maxLen := min(len(b), len(a)) - - for i := range maxLen { - if cmp := a[i].Compare(b[i]); cmp != 0 { - return cmp - } - } - - if len(a) > len(b) { - return 1 - } else if len(a) < len(b) { - return -1 - } - - return 0 -} - -func compareStringLists(a, b []string) int { - if len(a) > len(b) { - return 1 - } else if len(a) < len(b) { - return -1 - } - - for i := range a { - if cmp := strings.Compare(a[i], b[i]); cmp != 0 { - return cmp - } - } - - return 0 -} - // Copy returns a deep copy of s. func (a *Annotations) Copy(node Node) *Annotations { cpy := *a - - cpy.Organizations = make([]string, len(a.Organizations)) - copy(cpy.Organizations, a.Organizations) - - cpy.RelatedResources = make([]*RelatedResourceAnnotation, len(a.RelatedResources)) - for i := range a.RelatedResources { - cpy.RelatedResources[i] = a.RelatedResources[i].Copy() - } - - cpy.Authors = make([]*AuthorAnnotation, len(a.Authors)) - for i := range a.Authors { - cpy.Authors[i] = a.Authors[i].Copy() - } - - cpy.Schemas = make([]*SchemaAnnotation, len(a.Schemas)) - for i := range a.Schemas { - cpy.Schemas[i] = a.Schemas[i].Copy() - } - + cpy.Organizations = slices.Clone(a.Organizations) + cpy.RelatedResources = util.Map(a.RelatedResources, (*RelatedResourceAnnotation).Copy) + cpy.Authors = util.Map(a.Authors, (*AuthorAnnotation).Copy) + cpy.Schemas = util.Map(a.Schemas, (*SchemaAnnotation).Copy) cpy.Compile = a.Compile.Copy() if a.Custom != nil { cpy.Custom = deepcopy.Map(a.Custom) } - if a.Labels != nil { cpy.Labels = deepcopy.Map(a.Labels) } - cpy.node = node return &cpy } -// toObject constructs an AST Object from the annotation. -func (a *Annotations) toObject() (*Object, *Error) { - obj := NewObject() - +// toTerm constructs an AST Object from the annotation, and wraps it in a *Term. +func (a *Annotations) toTerm() (*Term, *Error) { if a == nil { - return &obj, nil + return ObjectTerm(), nil } + items := make([][2]*Term, 0, util.Count(util.Identity, + a.Entrypoint, + len(a.Scope) > 0, + len(a.Title) > 0, + len(a.Description) > 0, + len(a.Organizations) > 0, + len(a.RelatedResources) > 0, + len(a.Authors) > 0, + len(a.Schemas) > 0, + len(a.Custom) > 0, + len(a.Labels) > 0, + )) + if len(a.Scope) > 0 { - obj.Insert(InternedTerm("scope"), InternedTerm(a.Scope)) + items = append(items, [2]*Term{InternedTerm("scope"), InternedTerm(a.Scope)}) } if len(a.Title) > 0 { - obj.Insert(InternedTerm("title"), StringTerm(a.Title)) + items = append(items, [2]*Term{InternedTerm("title"), StringTerm(a.Title)}) } if a.Entrypoint { - obj.Insert(InternedTerm("entrypoint"), InternedTerm(true)) + items = append(items, [2]*Term{InternedTerm("entrypoint"), InternedTerm(true)}) } if len(a.Description) > 0 { - obj.Insert(InternedTerm("description"), StringTerm(a.Description)) + items = append(items, [2]*Term{InternedTerm("description"), StringTerm(a.Description)}) } if len(a.Organizations) > 0 { - orgs := make([]*Term, 0, len(a.Organizations)) - for _, org := range a.Organizations { - orgs = append(orgs, StringTerm(org)) - } - obj.Insert(InternedTerm("organizations"), ArrayTerm(orgs...)) + items = append(items, [2]*Term{InternedTerm("organizations"), ArrayTerm(util.Map(a.Organizations, StringTerm)...)}) } if len(a.RelatedResources) > 0 { - rrs := make([]*Term, 0, len(a.RelatedResources)) - for _, rr := range a.RelatedResources { - rrObj := NewObject(Item(InternedTerm("ref"), StringTerm(rr.Ref.String()))) - if len(rr.Description) > 0 { - rrObj.Insert(InternedTerm("description"), StringTerm(rr.Description)) - } - rrs = append(rrs, NewTerm(rrObj)) - } - obj.Insert(InternedTerm("related_resources"), ArrayTerm(rrs...)) + rrs := util.Map(a.RelatedResources, (*RelatedResourceAnnotation).toTerm) + items = append(items, [2]*Term{InternedTerm("related_resources"), ArrayTerm(rrs...)}) } if len(a.Authors) > 0 { - as := make([]*Term, 0, len(a.Authors)) - for _, author := range a.Authors { - aObj := NewObject() - if len(author.Name) > 0 { - aObj.Insert(InternedTerm("name"), StringTerm(author.Name)) - } - if len(author.Email) > 0 { - aObj.Insert(InternedTerm("email"), StringTerm(author.Email)) - } - as = append(as, NewTerm(aObj)) - } - obj.Insert(InternedTerm("authors"), ArrayTerm(as...)) + as := util.Map(a.Authors, (*AuthorAnnotation).toTerm) + items = append(items, [2]*Term{InternedTerm("authors"), ArrayTerm(as...)}) } if len(a.Schemas) > 0 { - ss := make([]*Term, 0, len(a.Schemas)) - for _, s := range a.Schemas { - sObj := NewObject() - if len(s.Path) > 0 { - sObj.Insert(InternedTerm("path"), NewTerm(s.Path.toArray())) - } - if len(s.Schema) > 0 { - sObj.Insert(InternedTerm("schema"), NewTerm(s.Schema.toArray())) - } - if s.Definition != nil { - def, err := InterfaceToValue(s.Definition) - if err != nil { - return nil, NewError(CompileErr, a.Location, "invalid definition in schema annotation: %s", err.Error()) - } - sObj.Insert(InternedTerm("definition"), NewTerm(def)) - } - ss = append(ss, NewTerm(sObj)) + ss, err := util.TryMap(a.Schemas, (*SchemaAnnotation).toTerm) + if err != nil { + return nil, NewError(CompileErr, a.Location, "invalid schema annotation %s", err.Error()) } - obj.Insert(InternedTerm("schemas"), ArrayTerm(ss...)) + items = append(items, [2]*Term{InternedTerm("schemas"), ArrayTerm(ss...)}) } if len(a.Custom) > 0 { @@ -440,7 +329,7 @@ func (a *Annotations) toObject() (*Object, *Error) { if err != nil { return nil, NewError(CompileErr, a.Location, "invalid custom annotation %s", err.Error()) } - obj.Insert(InternedTerm("custom"), NewTerm(c)) + items = append(items, [2]*Term{InternedTerm("custom"), NewTerm(c)}) } if len(a.Labels) > 0 { @@ -448,10 +337,10 @@ func (a *Annotations) toObject() (*Object, *Error) { if err != nil { return nil, NewError(CompileErr, a.Location, "invalid labels annotation %s", err.Error()) } - obj.Insert(InternedTerm("labels"), NewTerm(l)) + items = append(items, [2]*Term{InternedTerm("labels"), NewTerm(l)}) } - return &obj, nil + return ObjectTerm(items...), nil } func attachRuleAnnotations(mod *Module) { @@ -484,13 +373,11 @@ func attachRuleAnnotations(mod *Module) { func attachAnnotationsNodes(mod *Module) Errors { var errs Errors - // Find first non-annotation statement following each annotation and attach // the annotation to that statement. for _, a := range mod.Annotations { for _, stmt := range mod.stmts { - _, ok := stmt.(*Annotations) - if !ok { + if _, ok := stmt.(*Annotations); !ok { if stmt.Loc().Row > a.Location.Row { a.node = stmt break @@ -501,10 +388,9 @@ func attachAnnotationsNodes(mod *Module) Errors { if a.Scope == "" { switch a.node.(type) { case *Rule: + a.Scope = annotationScopeRule if a.Entrypoint { a.Scope = annotationScopeDocument - } else { - a.Scope = annotationScopeRule } case *Package: a.Scope = annotationScopePackage @@ -530,7 +416,6 @@ func attachAnnotationsNodes(mod *Module) Errors { } func validateAnnotationScopeAttachment(a *Annotations) *Error { - switch a.Scope { case annotationScopeRule, annotationScopeDocument: if _, ok := a.node.(*Rule); ok { @@ -568,12 +453,7 @@ func (a *AuthorAnnotation) Compare(other *AuthorAnnotation) int { if cmp := strings.Compare(a.Name, other.Name); cmp != 0 { return cmp } - - if cmp := strings.Compare(a.Email, other.Email); cmp != 0 { - return cmp - } - - return 0 + return strings.Compare(a.Email, other.Email) } func (a *AuthorAnnotation) String() string { @@ -585,6 +465,17 @@ func (a *AuthorAnnotation) String() string { return fmt.Sprintf("%s <%s>", a.Name, a.Email) } +func (a *AuthorAnnotation) toTerm() *Term { + items := make([][2]*Term, 0, 2) + if len(a.Name) > 0 { + items = append(items, [2]*Term{InternedTerm("name"), StringTerm(a.Name)}) + } + if len(a.Email) > 0 { + items = append(items, [2]*Term{InternedTerm("email"), StringTerm(a.Email)}) + } + return ObjectTerm(items...) +} + // Copy returns a deep copy of rr. func (rr *RelatedResourceAnnotation) Copy() *RelatedResourceAnnotation { cpy := *rr @@ -597,12 +488,7 @@ func (rr *RelatedResourceAnnotation) Compare(other *RelatedResourceAnnotation) i if cmp := strings.Compare(rr.Description, other.Description); cmp != 0 { return cmp } - - if cmp := strings.Compare(rr.Ref.String(), other.Ref.String()); cmp != 0 { - return cmp - } - - return 0 + return strings.Compare(rr.Ref.String(), other.Ref.String()) } func (rr *RelatedResourceAnnotation) String() string { @@ -610,6 +496,17 @@ func (rr *RelatedResourceAnnotation) String() string { return string(bs) } +func (rr *RelatedResourceAnnotation) toTerm() *Term { + items := make([][2]*Term, 0, 2) + if len(rr.Ref.String()) > 0 { + items = append(items, [2]*Term{InternedTerm("ref"), StringTerm(rr.Ref.String())}) + } + if len(rr.Description) > 0 { + items = append(items, [2]*Term{InternedTerm("description"), StringTerm(rr.Description)}) + } + return ObjectTerm(items...) +} + // Copy returns a deep copy of s. func (s *SchemaAnnotation) Copy() *SchemaAnnotation { cpy := *s @@ -622,20 +519,20 @@ func (s *SchemaAnnotation) Compare(other *SchemaAnnotation) int { if cmp := s.Path.Compare(other.Path); cmp != 0 { return cmp } - if cmp := s.Schema.Compare(other.Schema); cmp != 0 { return cmp } - if s.Definition != nil && other.Definition == nil { + switch { + case s.Definition == other.Definition: + return 0 + case s.Definition == nil: return -1 - } else if s.Definition == nil && other.Definition != nil { + case other.Definition == nil: return 1 - } else if s.Definition != nil && other.Definition != nil { - return util.Compare(*s.Definition, *other.Definition) } - return 0 + return util.Compare(*s.Definition, *other.Definition) } func (s *SchemaAnnotation) String() string { @@ -643,15 +540,31 @@ func (s *SchemaAnnotation) String() string { return string(bs) } +func (s *SchemaAnnotation) toTerm() (*Term, error) { + items := make([][2]*Term, 0, 3) + if len(s.Path.String()) > 0 { + items = append(items, [2]*Term{InternedTerm("path"), NewTerm(s.Path.toArray())}) + } + if len(s.Schema.String()) > 0 { + items = append(items, [2]*Term{InternedTerm("schema"), NewTerm(s.Schema.toArray())}) + } + if s.Definition != nil { + def, err := InterfaceToValue(s.Definition) + if err != nil { + return nil, err + } + items = append(items, [2]*Term{InternedTerm("definition"), NewTerm(def)}) + } + return ObjectTerm(items...), nil +} + // Copy returns a deep copy of s. func (c *CompileAnnotation) Copy() *CompileAnnotation { if c == nil { return nil } cpy := *c - for i := range c.Unknowns { - cpy.Unknowns[i] = c.Unknowns[i].Copy() - } + cpy.Unknowns = util.Map(c.Unknowns, Ref.Copy) return &cpy } @@ -659,12 +572,12 @@ func (c *CompileAnnotation) Copy() *CompileAnnotation { // than other. func (c *CompileAnnotation) Compare(other *CompileAnnotation) int { switch { - case c == nil && other == nil: + case c == other: return 0 - case c != nil && other == nil: - return 1 - case c == nil && other != nil: + case c == nil: return -1 + case other == nil: + return 1 } if cmp := slices.CompareFunc(c.Unknowns, other.Unknowns, RefCompare); cmp != 0 { @@ -776,24 +689,18 @@ func (as *AnnotationSet) GetPackageScope(pkg *Package) *Annotations { // The returned slice is sorted, first by the annotations' target path, then by their target location func (as *AnnotationSet) Flatten() FlatAnnotationsRefSet { // This preallocation often won't be optimal, but it's superior to starting with a nil slice. - refs := make([]*AnnotationsRef, 0, len(as.byPath.Children)+len(as.byRule)+len(as.byPackage)) - - refs = as.byPath.flatten(refs) - + size := len(as.byPath.Children) + len(as.byRule) + len(as.byPackage) + refs := as.byPath.flatten(make([]*AnnotationsRef, 0, size)) for _, a := range as.byPackage { refs = append(refs, NewAnnotationsRef(a)) } for _, as := range as.byRule { - for _, a := range as { - refs = append(refs, NewAnnotationsRef(a)) - } + refs = util.MapAppend(refs, as, NewAnnotationsRef) } // Sort by path, then annotation location, for stable output - slices.SortStableFunc(refs, (*AnnotationsRef).Compare) - - return refs + return util.SortedStableFunc(refs, (*AnnotationsRef).Compare) } // Chain returns the chain of annotations leading up to the given rule. @@ -804,10 +711,7 @@ func (as *AnnotationSet) Flatten() FlatAnnotationsRefSet { // 3. Entries for the 'subpackages' scope, if any; ordered from the closest package path to the fartest. E.g.: 'do.re.mi', 'do.re', 'do' // The returned slice is guaranteed to always contain at least one entry, corresponding to the given rule. func (as *AnnotationSet) Chain(rule *Rule) AnnotationsRefSet { - var refs []*AnnotationsRef - ruleAnnots := as.GetRuleScope(rule) - // Fall back to the rule's own attached annotations when the rule's source // module isn't tracked by this AnnotationSet. This happens for rules // supplied by an ExternalRuleSource that returns []*Rule directly: their @@ -819,10 +723,12 @@ func (as *AnnotationSet) Chain(rule *Rule) AnnotationsRefSet { ruleAnnots = rule.Annotations } + var refs []*AnnotationsRef if len(ruleAnnots) >= 1 { - for _, a := range ruleAnnots { - refs = append(refs, NewAnnotationsRef(a)) - } + // Sort by annotation location; chain must start with annotations declared closest to rule, then going outward + refs = util.SortedStableFunc(util.Map(ruleAnnots, NewAnnotationsRef), func(a, b *AnnotationsRef) int { + return -a.Annotations.Location.Compare(b.Annotations.Location) + }) } else { // Make sure there is always a leading entry representing the passed rule, even if it has no annotations refs = append(refs, &AnnotationsRef{ @@ -832,29 +738,19 @@ func (as *AnnotationSet) Chain(rule *Rule) AnnotationsRefSet { }) } - if len(refs) > 1 { - // Sort by annotation location; chain must start with annotations declared closest to rule, then going outward - slices.SortStableFunc(refs, func(a, b *AnnotationsRef) int { - return -a.Annotations.Location.Compare(b.Annotations.Location) - }) + if da := as.GetDocumentScope(rule.Ref().GroundPrefix()); da != nil { + refs = append(refs, NewAnnotationsRef(da)) } - docAnnots := as.GetDocumentScope(rule.Ref().GroundPrefix()) - if docAnnots != nil { - refs = append(refs, NewAnnotationsRef(docAnnots)) + if pa := as.GetPackageScope(rule.Module.Package); pa != nil { + refs = append(refs, NewAnnotationsRef(pa)) } - pkg := rule.Module.Package - pkgAnnots := as.GetPackageScope(pkg) - if pkgAnnots != nil { - refs = append(refs, NewAnnotationsRef(pkgAnnots)) - } - - subPkgAnnots := as.GetSubpackagesScope(pkg.Path) + subPkgAnnots := as.GetSubpackagesScope(rule.Module.Package.Path) // We need to reverse the order, as subPkgAnnots ordering will start at the root, // whereas we want to end at the root. - for i := len(subPkgAnnots) - 1; i >= 0; i-- { - refs = append(refs, NewAnnotationsRef(subPkgAnnots[i])) + for _, subPkgAnnot := range slices.Backward(subPkgAnnots) { + refs = append(refs, NewAnnotationsRef(subPkgAnnot)) } return refs @@ -880,8 +776,8 @@ func (as *AnnotationSet) MergedLabels(rule *Rule) (labels map[string]any, key st // we iterate in reverse to fold outer-to-inner. func mergeChainLabels(chain AnnotationsRefSet) map[string]any { var merged map[string]any - for i := len(chain) - 1; i >= 0; i-- { - a := chain[i].Annotations + for _, c := range slices.Backward(chain) { + a := c.Annotations if a == nil || len(a.Labels) == 0 { continue } @@ -899,8 +795,7 @@ func (ars FlatAnnotationsRefSet) Insert(ar *AnnotationsRef) FlatAnnotationsRefSe // insertion sort, first by path, then location for i, current := range ars { if ar.Compare(current) < 0 { - result = append(result, ar) - result = append(result, ars[i:]...) + result = append(append(result, ar), ars[i:]...) break } result = append(result, current) @@ -981,10 +876,8 @@ func (ar *AnnotationsRef) Compare(other *AnnotationsRef) int { if c := ar.Path.Compare(other.Path); c != 0 { return c } - if c := ar.Annotations.Location.Compare(other.Annotations.Location); c != 0 { return c } - return ar.Annotations.Compare(other.Annotations) } diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/builtins.go b/vendor/github.com/open-policy-agent/opa/v1/ast/builtins.go index 17ed06035d..8942e4f34c 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/builtins.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/builtins.go @@ -941,18 +941,20 @@ var ArrayReverse = &Builtin{ var conversions = category("conversions") var ToNumber = &Builtin{ - Name: "to_number", - Description: "Converts a string, bool, or number value to a number: Strings are converted to numbers using `strconv.Atoi`, Boolean `false` is converted to 0 and `true` is converted to 1.", + Name: "to_number", + Description: "Converts value of type string, null or boolean to number. Numeric strings converts to the " + + "corresponding number when possible. Null and boolean `false` converts to 0 and boolean `true` to 1. " + + "Numbers are returned without conversion.", Decl: types.NewFunction( types.Args( - types.Named("x", types.NewAny( + types.Named("value", types.NewAny( types.N, types.S, types.B, types.Nl, )).Description("value to convert"), ), - types.Named("num", types.N).Description("the numeric representation of `x`"), + types.Named("num", types.N).Description("the numeric representation of `value`"), ), Categories: conversions, CanSkipBctx: true, @@ -1002,7 +1004,7 @@ var RegexFindAllStringSubmatch = &Builtin{ var RegexTemplateMatch = &Builtin{ Name: "regex.template_match", - Description: "Matches a string against a pattern, where there pattern may be glob-like", + Description: "Matches a string against a pattern, where the pattern may be glob-like", Decl: types.NewFunction( types.Args( types.Named("template", types.S).Description("template expression containing `0..n` regular expressions"), @@ -3123,8 +3125,12 @@ var JSONSchemaVerify = &Builtin{ }, nil)). Description("`output` is of the form `[valid, error]`. If the schema is valid, then `valid` is `true`, and `error` is `null`. Otherwise, `valid` is `false` and `error` is a string describing the error."), ), - Categories: objectCat, - CanSkipBctx: true, + Categories: objectCat, + // `$ref`s are dereferenced at evaluation time, so the result depends on what those URLs serve. + Nondeterministic: true, + // Needs the BuiltinContext to read the allow_net capability, which + // restricts the hosts that remote `$ref`s may be fetched from. + CanSkipBctx: false, } // JSONMatchSchema returns empty array if the document matches the JSON schema, @@ -3155,8 +3161,10 @@ var JSONMatchSchema = &Builtin{ }, nil)). Description("`output` is of the form `[match, errors]`. If the document is valid given the schema, then `match` is `true`, and `errors` is an empty array. Otherwise, `match` is `false` and `errors` is an array of objects describing the error(s)."), ), - Categories: objectCat, - CanSkipBctx: false, + Categories: objectCat, + // `$ref`s are dereferenced at evaluation time, so the result depends on what those URLs serve. + Nondeterministic: true, + CanSkipBctx: false, } /** @@ -3676,12 +3684,8 @@ func (b *Builtin) IsNondeterministic() bool { func (b *Builtin) Expr(operands ...*Term) *Expr { ts := make([]*Term, len(operands)+1) ts[0] = NewTerm(b.Ref()) - for i := range operands { - ts[i+1] = operands[i] - } - return &Expr{ - Terms: ts, - } + copy(ts[1:], operands) + return &Expr{Terms: ts} } // Call creates a new term for the built-in with the given operands. diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/capabilities.go b/vendor/github.com/open-policy-agent/opa/v1/ast/capabilities.go index 0c37dfb1fe..610de4bc54 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/capabilities.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/capabilities.go @@ -5,23 +5,63 @@ package ast import ( - "bytes" _ "embed" "encoding/json" "fmt" "io" + "io/fs" "os" "slices" - "sort" "strings" "sync" "github.com/open-policy-agent/opa/internal/semver" "github.com/open-policy-agent/opa/internal/wasm/sdk/opa/capabilities" + "github.com/open-policy-agent/opa/v1/ast/internal/tokens" caps "github.com/open-policy-agent/opa/v1/capabilities" "github.com/open-policy-agent/opa/v1/util" ) +// In the compiler, we used this to check that we're OK working with ref heads. +// If this isn't present, we'll fail. This is to ensure that older versions of +// OPA can work with policies that we're compiling -- if they don't know ref +// heads, they wouldn't be able to parse them. +const ( + FeatureRefHeadStringPrefixes = "rule_head_ref_string_prefixes" + FeatureRefHeads = "rule_head_refs" + FeatureRegoV1 = "rego_v1" + FeatureRegoV1Import = "rego_v1_import" + FeatureKeywordsInRefs = "keywords_in_refs" + FeatureTemplateStrings = "template_strings" +) + +var ( + // Features carries the default features supported by this version of OPA. + // Use RegisterFeatures to add to them. + Features = []string{ + FeatureRegoV1, + FeatureKeywordsInRefs, + FeatureTemplateStrings, + } + v0v1compatFeatures = []string{ + FeatureRefHeadStringPrefixes, + FeatureRefHeads, + FeatureRegoV1Import, + FeatureRegoV1, // Included in v0 capabilities to allow v1 bundles in --v0-compatible mode + FeatureKeywordsInRefs, + } + // NOTE(tsandall): this file is generated by internal/cmd/genversionindex/main.go + // and run as part of go:generate. We generate the version index as part of the + // build process because it's relatively expensive to build (it takes ~500ms on + // my machine) and never changes. + // + //go:embed version_index.json + versionIndexBs []byte + // init only on demand, as JSON unmarshalling comes with some cost, and contributes + // noise to things like pprof stats + minVersionIndexOnce = sync.OnceValue(minVersionIndex) +) + // VersonIndex contains an index from built-in function name, language feature, // and future rego keyword to version number. During the build, this is used to // create an index of the minimum version required for the built-in/feature/kw. @@ -31,51 +71,13 @@ type VersionIndex struct { Keywords map[string]semver.Version `json:"keywords"` } -// NOTE(tsandall): this file is generated by internal/cmd/genversionindex/main.go -// and run as part of go:generate. We generate the version index as part of the -// build process because it's relatively expensive to build (it takes ~500ms on -// my machine) and never changes. -// -//go:embed version_index.json -var versionIndexBs []byte - -// init only on demand, as JSON unmarshalling comes with some cost, and contributes -// noise to things like pprof stats -var minVersionIndexOnce = sync.OnceValue(func() VersionIndex { - var vi VersionIndex - if err := json.Unmarshal(versionIndexBs, &vi); err != nil { - panic(err) - } - return vi -}) - -// In the compiler, we used this to check that we're OK working with ref heads. -// If this isn't present, we'll fail. This is to ensure that older versions of -// OPA can work with policies that we're compiling -- if they don't know ref -// heads, they wouldn't be able to parse them. -const FeatureRefHeadStringPrefixes = "rule_head_ref_string_prefixes" -const FeatureRefHeads = "rule_head_refs" -const FeatureRegoV1 = "rego_v1" -const FeatureRegoV1Import = "rego_v1_import" -const FeatureKeywordsInRefs = "keywords_in_refs" -const FeatureTemplateStrings = "template_strings" - -// Features carries the default features supported by this version of OPA. -// Use RegisterFeatures to add to them. -var Features = []string{ - FeatureRegoV1, - FeatureKeywordsInRefs, - FeatureTemplateStrings, -} - // RegisterFeatures lets applications wrapping OPA register features, to be // included in `ast.CapabilitiesForThisVersion()`. func RegisterFeatures(fs ...string) { for i := range fs { - if slices.Contains(Features, fs[i]) { - continue + if !slices.Contains(Features, fs[i]) { + Features = append(Features, fs[i]) } - Features = append(Features, fs[i]) } } @@ -113,6 +115,9 @@ type CapabilitiesOptions struct { } func newCapabilitiesOptions(opts []CapabilitiesOption) CapabilitiesOptions { + if len(opts) == 0 { + return CapabilitiesOptions{} + } co := CapabilitiesOptions{} for _, opt := range opts { opt(&co) @@ -146,59 +151,35 @@ func CapabilitiesExperimentalKeywords(yes bool) CapabilitiesOption { // CapabilitiesForThisVersion returns the capabilities of this version of OPA. func CapabilitiesForThisVersion(opts ...CapabilitiesOption) *Capabilities { co := newCapabilitiesOptions(opts) - - f := &Capabilities{} + f := &Capabilities{Builtins: util.SortedFunc(slices.Clone(Builtins), cmpBuiltinName)} for _, vers := range capabilities.ABIVersions() { f.WasmABIVersions = append(f.WasmABIVersions, WasmABIVersion{Version: vers[0], Minor: vers[1]}) } - f.Builtins = make([]*Builtin, len(Builtins)) - copy(f.Builtins, Builtins) - - slices.SortFunc(f.Builtins, func(a, b *Builtin) int { - return strings.Compare(a.Name, b.Name) - }) - switch co.regoVersion { case RegoV0, RegoV0CompatV1: - for kw := range allFutureKeywords { - if _, internal := experimentalFutureKeywords[kw]; internal && !co.experimentalKeywords { - continue - } - f.FutureKeywords = append(f.FutureKeywords, kw) - } - - f.Features = []string{ - FeatureRefHeadStringPrefixes, - FeatureRefHeads, - FeatureRegoV1Import, - FeatureRegoV1, // Included in v0 capabilities to allow v1 bundles in --v0-compatible mode - FeatureKeywordsInRefs, - } + f.FutureKeywords = co.filterFutureKeywords(allFutureKeywords) + f.Features = util.Sorted(slices.Clone(v0v1compatFeatures)) default: - for kw := range futureKeywords { - if _, internal := experimentalFutureKeywords[kw]; internal && !co.experimentalKeywords { - continue - } - f.FutureKeywords = append(f.FutureKeywords, kw) - } - - f.Features = make([]string, len(Features)) - copy(f.Features, Features) + f.FutureKeywords = co.filterFutureKeywords(futureKeywords) + f.Features = util.Sorted(slices.Clone(Features)) } - sort.Strings(f.FutureKeywords) - sort.Strings(f.Features) - return f } +func (co *CapabilitiesOptions) filterFutureKeywords(src map[string]tokens.Token) []string { + if co.experimentalKeywords { + return util.KeysSorted(src) + } + return slices.DeleteFunc(util.KeysSorted(src), isExperimental) +} + // LoadCapabilitiesJSON loads a JSON serialized capabilities structure from the reader r. func LoadCapabilitiesJSON(r io.Reader) (*Capabilities, error) { - d := util.NewJSONDecoder(r) var c Capabilities - return &c, d.Decode(&c) + return &c, util.NewJSONDecoder(r).Decode(&c) } // LoadCapabilitiesVersion loads a JSON serialized capabilities structure from the specific version. @@ -208,18 +189,16 @@ func LoadCapabilitiesVersion(version string) (*Capabilities, error) { return nil, err } - for _, cv := range cvs { - if cv == version { - cont, err := caps.FS.ReadFile(cv + ".json") - if err != nil { - return nil, err - } - - return LoadCapabilitiesJSON(bytes.NewReader(cont)) + if slices.Contains(cvs, version) { + fd, err := caps.FS.Open(version + ".json") + if err != nil { + return nil, err } - + defer fd.Close() + return LoadCapabilitiesJSON(fd) } - return nil, fmt.Errorf("no capabilities version found %v", version) + + return nil, fmt.Errorf("no capabilities version found %s", version) } // LoadCapabilitiesFile loads a JSON serialized capabilities structure from a file. @@ -234,19 +213,11 @@ func LoadCapabilitiesFile(file string) (*Capabilities, error) { // LoadCapabilitiesVersions loads all capabilities versions func LoadCapabilitiesVersions() ([]string, error) { - ents, err := caps.FS.ReadDir(".") + entries, err := caps.FS.ReadDir(".") if err != nil { return nil, err } - - capabilitiesVersions := make([]string, 0, len(ents)) - for _, ent := range ents { - capabilitiesVersions = append(capabilitiesVersions, strings.Replace(ent.Name(), ".json", "", 1)) - } - - slices.SortStableFunc(capabilitiesVersions, semver.Compare) - - return capabilitiesVersions, nil + return util.SortedStableFunc(util.Map(entries, removeJsonSuffix), semver.Compare), nil } // MinimumCompatibleVersion returns the minimum compatible OPA version based on @@ -306,14 +277,30 @@ func (c *Capabilities) ContainsFutureKeyword(kw string) bool { // addBuiltinSorted inserts a built-in into c in sorted order. An existing built-in with the same name // will be overwritten. func (c *Capabilities) addBuiltinSorted(bi *Builtin) { - i := sort.Search(len(c.Builtins), func(x int) bool { - return c.Builtins[x].Name >= bi.Name - }) - if i < len(c.Builtins) && bi.Name == c.Builtins[i].Name { - c.Builtins[i] = bi - return + i, found := slices.BinarySearchFunc(c.Builtins, bi, cmpBuiltinName) + if !found { + c.Builtins = append(c.Builtins, nil) + copy(c.Builtins[i+1:], c.Builtins[i:]) } - c.Builtins = append(c.Builtins, nil) - copy(c.Builtins[i+1:], c.Builtins[i:]) c.Builtins[i] = bi } + +func minVersionIndex() (vi VersionIndex) { + if err := json.Unmarshal(versionIndexBs, &vi); err != nil { + panic(err) + } + return vi +} + +func cmpBuiltinName(a, b *Builtin) int { + return strings.Compare(a.Name, b.Name) +} + +func removeJsonSuffix(ent fs.DirEntry) string { + return strings.Replace(ent.Name(), ".json", "", 1) +} + +func isExperimental(kw string) bool { + _, experimental := experimentalFutureKeywords[kw] + return experimental +} diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/check.go b/vendor/github.com/open-policy-agent/opa/v1/ast/check.go index ed3d14a15f..05c7f889aa 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/check.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/check.go @@ -16,6 +16,10 @@ import ( type varRewriter func(Ref) Ref +// dependentsResolver returns the refs of the rules that (transitively) depend on +// the document(s) at ref. +type dependentsResolver func(Ref) []Ref + // typeChecker implements type checking on queries and rules. Errors are // accumulated on the typeChecker so that a single run can report multiple // issues. @@ -29,6 +33,8 @@ type typeChecker struct { input types.Type allowUndefinedFuncs bool schemaTypes map[string]types.Type + dependentsResolver dependentsResolver + withTrees map[string]*typeTreeNode } // newTypeChecker returns a new typeChecker object that has no errors. @@ -38,7 +44,14 @@ func newTypeChecker() *typeChecker { func (tc *typeChecker) newEnv(exist *TypeEnv) *TypeEnv { if exist != nil { - return exist.wrap() + env := exist.wrap() + // The wrapped environment would otherwise inherit exist's checker + // factory, which may have been built with a different configuration + // than tc -- the compiler seeds Compiler.TypeEnv from a bare checker, + // for one. Comprehension bodies are typed lazily through this factory, + // so it has to reflect the checker that is running now. + env.newChecker = tc.copyForEnv + return env } env := newTypeEnv(tc.copy) if tc.input != nil { @@ -47,6 +60,14 @@ func (tc *typeChecker) newEnv(exist *TypeEnv) *TypeEnv { return env } +// copyForEnv returns a checker for typing the closures an environment is asked +// about. It drops the required-capabilities accumulator: environments outlive +// the compilation that produced them, and the builtins in those closures are +// already recorded by checkClosures. +func (tc *typeChecker) copyForEnv() *typeChecker { + return tc.copy().WithRequiredCapabilities(nil) +} + func (tc *typeChecker) copy() *typeChecker { return newTypeChecker(). WithVarRewriter(tc.varRewriter). @@ -56,6 +77,7 @@ func (tc *typeChecker) copy() *typeChecker { WithInputType(tc.input). WithAllowUndefinedFunctionCalls(tc.allowUndefinedFuncs). WithBuiltins(tc.builtins). + WithDependentsResolver(tc.dependentsResolver). WithRequiredCapabilities(tc.required) } @@ -89,6 +111,13 @@ func (tc *typeChecker) WithVarRewriter(f varRewriter) *typeChecker { return tc } +// WithDependentsResolver sets the function used to look up the rules that depend +// on the document(s) replaced by a with modifier. +func (tc *typeChecker) WithDependentsResolver(f dependentsResolver) *typeChecker { + tc.dependentsResolver = f + return tc +} + func (tc *typeChecker) WithInputType(tpe types.Type) *typeChecker { tc.input = tpe return tc @@ -124,17 +153,27 @@ func (tc *typeChecker) CheckBody(env *TypeEnv, body Body) (*TypeEnv, Errors) { for _, bexpr := range body { WalkExprs(bexpr, func(expr *Expr) bool { - closureErrs := tc.checkClosures(env, expr) + exprEnv, exprVis, exprGV := env, vis, gv + + if len(expr.With) > 0 { + if withEnv := tc.withEnv(env, expr); withEnv != env { + exprEnv = withEnv + exprVis = newRefChecker(withEnv, tc.varRewriter) + exprGV = NewGenericVisitor(exprVis.Visit) + } + } + + closureErrs := tc.checkClosures(exprEnv, expr) errors = append(errors, closureErrs...) // reset errors from previous iteration - vis.errs = nil - gv.Walk(expr) - errors = append(errors, vis.errs...) + exprVis.errs = nil + exprGV.Walk(expr) + errors = append(errors, exprVis.errs...) - if err := tc.checkExpr(env, expr); err != nil { + if err := tc.checkExpr(exprEnv, expr); err != nil { hasClosureErrors := len(closureErrs) > 0 - hasRefErrors := len(vis.errs) > 0 + hasRefErrors := len(exprVis.errs) > 0 // Suppress this error if a more actionable one has occurred. In // this case, if an error occurred in a ref or closure contained in // this expression, and the error is due to a nil type, then it's @@ -222,7 +261,6 @@ func (tc *typeChecker) getSchemaType(schemaAnnot *SchemaAnnotation, rule *Rule) } func (tc *typeChecker) checkRule(env *TypeEnv, as *AnnotationSet, rule *Rule) { - env = env.wrap() schemaAnnots := getRuleAnnotation(as, rule) @@ -270,10 +308,8 @@ func (tc *typeChecker) checkRule(env *TypeEnv, as *AnnotationSet, rule *Rule) { for _, arg := range rule.Head.Args { // If args are not referred to in body, infer as any. WalkTerms(arg, func(t *Term) bool { - if _, ok := t.Value.(Var); ok { - if cpy.GetByValue(t.Value) == nil { - cpy.tree.PutOne(t.Value, types.A) - } + if _, ok := t.Value.(Var); ok && cpy.GetByValue(t.Value) == nil { + cpy.vars.PutOne(t.Value, types.A) } return false }) @@ -285,7 +321,16 @@ func (tc *typeChecker) checkRule(env *TypeEnv, as *AnnotationSet, rule *Rule) { args[i] = cpy.GetByValue(rule.Head.Args[i].Value) } - tpe = types.NewFunction(args, cpy.GetByValue(rule.Head.Value.Value)) + result := cpy.GetByValue(rule.Head.Value.Value) + if result == nil && tc.allowUndefinedFuncs { + // The value is only unknown because it came out of a call to an + // undefined function. Recording a function type without a result + // would make callers look like they pass one argument too many, so + // fall back to any. + result = types.A + } + + tpe = types.NewFunction(args, result) } else { switch rule.Head.RuleKind() { case SingleValue: @@ -335,23 +380,17 @@ func nestedObject(env *TypeEnv, path Ref, tpe types.Type) (types.Type, error) { return tpe, nil } - k := path[0] typeV, err := nestedObject(env, path[1:], tpe) - if err != nil { + if err != nil || typeV == nil { return nil, err } - if typeV == nil { - return nil, nil - } - var dynamicProperty *types.DynamicProperty - typeK := env.GetByValue(k.Value) + typeK := env.GetByValue(path[0].Value) if typeK == nil { return nil, nil } - dynamicProperty = types.NewDynamicProperty(typeK, typeV) - return types.NewObject(nil, dynamicProperty), nil + return types.NewObject(nil, types.NewDynamicProperty(typeK, typeV)), nil } func (tc *typeChecker) checkExpr(env *TypeEnv, expr *Expr) *Error { @@ -373,13 +412,75 @@ func (tc *typeChecker) checkExpr(env *TypeEnv, expr *Expr) *Error { } } - if operator == "eq" { + switch operator { + case Equality.Name: return checkExprEq(env, expr) + case Member.Name, MemberWithKey.Name: + if err := checkExprMember(env, expr, operator == MemberWithKey.Name); err != nil { + return err + } } return tc.checkExprBuiltin(env, expr) } +// checkExprMember type checks the `in` operator, whose operands are declared as +// any: what may be found in a collection depends on the collection's own type, +// which a function declaration can't express. +func checkExprMember(env *TypeEnv, expr *Expr, withKey bool) *Error { + arity := Member.Decl.Arity() + if withKey { + arity = MemberWithKey.Decl.Arity() + } + + args := expr.Operands() + if len(args) < arity { + return nil // too few arguments; reported by checkExprBuiltin + } + + collection := env.GetByValue(args[arity-1].Value) + + // `in` yields false rather than erroring for operands it can't enumerate. + values := types.Values(collection) + if values == nil { + return nil + } + + if withKey { + if err := checkExprMemberOperand(env, expr, args[0], types.Keys(collection)); err != nil { + return err + } + } + + return checkExprMemberOperand(env, expr, args[arity-2], values) +} + +// checkExprMemberOperand checks that term can occur in the collection being +// searched, inferring the type of untyped terms (e.g. `some x in xs`) as it goes. +func checkExprMemberOperand(env *TypeEnv, expr *Expr, term *Term, tpe types.Type) *Error { + if tpe == nil || types.Nil(tpe) { + return nil + } + + have := env.GetByValue(term.Value) + + // unifies rejects already-typed terms; unify1 infers types for untyped vars + // and checks the resolved parts of partially typed composites. + if (!types.Nil(have) && !unifies(have, tpe)) || !unify1(env, term, tpe, false) { + return &Error{ + Code: TypeErr, + Location: expr.Location, + Message: "match error", + Details: &UnificationErrDetail{ + Left: have, + Right: tpe, + }, + } + } + + return nil +} + func (tc *typeChecker) checkExprBuiltin(env *TypeEnv, expr *Expr) *Error { // NOTE(tsandall): undefined functions will have been caught earlier in the // compiler. We check for undefined functions before the safety check so @@ -455,14 +556,16 @@ func (tc *typeChecker) checkExprBuiltin(env *TypeEnv, expr *Expr) *Error { } func checkExprEq(env *TypeEnv, expr *Expr) *Error { + ops := expr.Operands() + num := len(ops) - pre := getArgTypes(env, expr.Operands()) - - if len(pre) < Equality.Decl.Arity() { + if num < Equality.Decl.Arity() { + pre := getArgTypes(env, ops) return newArgError(expr.Location, expr.Operator(), "too few arguments", pre, Equality.Decl.FuncArgs()) } - if Equality.Decl.Arity() < len(pre) { + if Equality.Decl.Arity() < num { + pre := getArgTypes(env, ops) return newArgError(expr.Location, expr.Operator(), "too many arguments", pre, Equality.Decl.FuncArgs()) } @@ -481,6 +584,99 @@ func checkExprEq(env *TypeEnv, expr *Expr) *Error { return nil } +// withEnv returns the TypeEnv to check expr against, where the documents its +// with modifiers replace can also have the type of their replacement value, and +// the rules depending on those documents are widened to any. +func (tc *typeChecker) withEnv(env *TypeEnv, expr *Expr) *TypeEnv { + cpy := env + targets := make([]Ref, 0, len(expr.With)) + targetTypes := make([]types.Type, 0, len(expr.With)) + + for _, w := range expr.With { + target, ok := w.Target.Value.(Ref) + if !ok { + continue + } + + targetType := env.GetByRef(target) + + // Keeping the declaration of a replaced function allows its arity to be + // checked against the replacement. + _, isFunc := targetType.(*types.Function) + + if tree := tc.relaxedDependents(target, isFunc); tree != nil { + layer := cpy.wrapWith() + // Shared with every other expression replacing this target. + layer.tree = tree + cpy = layer + } + + if !isFunc { + // A non-ground target replaces an unknown part of the document, so + // nothing more specific than any can be said about its prefix. + tpe := types.A + if target.IsGround() && targetType != nil { + // Or returns nil if only one of the two is a function. + if valueType := env.GetByValue(w.Value.Value); valueType != nil { + if or := types.Or(targetType, valueType); or != nil { + tpe = or + } + } + } + targets = append(targets, target.GroundPrefix()) + targetTypes = append(targetTypes, tpe) + } + } + + if len(targets) == 0 { + return cpy + } + + // Wrapped last, so that a replaced document keeps the type of its + // replacement value even if another modifier replaces one of its dependencies. + cpy = cpy.wrapWith() + for i := range targets { + cpy.tree.Put(targets[i], targetTypes[i]) + } + + return cpy +} + +// relaxedDependents returns a cached type tree where the rules affected by +// replacing the document(s) at target are typed as any, or nil if there are none. +func (tc *typeChecker) relaxedDependents(target Ref, isFunc bool) *typeTreeNode { + if tc.dependentsResolver == nil { + return nil + } + + key := target.String() + if isFunc { + key = "f:" + key + } + + if tree, ok := tc.withTrees[key]; ok { + return tree + } + + var tree *typeTreeNode + for _, ref := range tc.dependentsResolver(target) { + if isFunc && ref.Equal(target) { + continue + } + if tree == nil { + tree = newTypeTree() + } + tree.Put(ref, types.A) + } + + if tc.withTrees == nil { + tc.withTrees = map[string]*typeTreeNode{} + } + tc.withTrees[key] = tree + + return tree +} + func (tc *typeChecker) checkExprWith(env *TypeEnv, expr *Expr, i int) *Error { if i == len(expr.With) { return nil @@ -655,12 +851,12 @@ func unify1(env *TypeEnv, term *Term, tpe types.Type, union bool) bool { return unifies(env.GetByValue(v), tpe) case Var: if !union { - if exist := env.GetByValue(v); exist != nil { + if exist := env.GetByValue(term.Value); exist != nil { return unifies(exist, tpe) } - env.tree.PutOne(term.Value, tpe) + env.vars.PutOne(term.Value, tpe) } else { - env.tree.PutOne(term.Value, types.Or(env.GetByValue(v), tpe)) + env.vars.PutOne(term.Value, types.Or(env.GetByValue(term.Value), tpe)) } return true default: @@ -781,7 +977,6 @@ func (rc *refChecker) checkApply(curr *TypeEnv, ref Ref) *Error { } func (rc *refChecker) checkRef(curr *TypeEnv, node *typeTreeNode, ref Ref, idx int) *Error { - if idx == len(ref) { return nil } @@ -807,7 +1002,7 @@ func (rc *refChecker) checkRef(curr *TypeEnv, node *typeTreeNode, ref Ref, idx i return newRefErrInvalid(ref[0].Location, rc.varRewriter(ref), idx, exist, tpe, getOneOfForNode(node)) } } else { - rc.env.tree.PutOne(head.Value, tpe) + rc.env.vars.PutOne(head.Value, tpe) } } @@ -848,6 +1043,13 @@ func (rc *refChecker) checkRefLeaf(tpe types.Type, ref Ref, idx int) *Error { head := ref[idx] + if isEmptyCollectionType(tpe) { + // The collection has no members at all, so nothing can be selected from + // it. Report that like any other missing key rather than as a value that + // can't be dereferenced at all. + return newRefErrInvalid(ref[0].Location, rc.varRewriter(ref), idx, nil, nil, nil) + } + keys := types.Keys(tpe) if keys == nil { return newRefErrUnsupported(ref[0].Location, rc.varRewriter(ref), idx-1, tpe) @@ -861,7 +1063,7 @@ func (rc *refChecker) checkRefLeaf(tpe types.Type, ref Ref, idx int) *Error { return newRefErrInvalid(ref[0].Location, rc.varRewriter(ref), idx, exist, keys, getOneOfForType(tpe)) } } else { - rc.env.tree.PutOne(head.Value, types.Keys(tpe)) + rc.env.vars.PutOne(head.Value, types.Keys(tpe)) } case Ref: @@ -887,6 +1089,27 @@ func (rc *refChecker) checkRefLeaf(tpe types.Type, ref Ref, idx int) *Error { return rc.checkRefLeaf(types.Values(tpe), ref, idx+1) } +// isEmptyCollectionType returns true if tpe is the type of a collection that +// can hold nothing: an object with neither static nor dynamic properties, an +// array with no items, or a set with no element type. +func isEmptyCollectionType(tpe types.Type) bool { + if named, ok := tpe.(*types.NamedType); ok { + tpe = named.Type + } + if rec, ok := tpe.(*types.Recursive); ok { + tpe = rec.Unwrap() + } + switch tpe := tpe.(type) { + case *types.Object: + return len(tpe.StaticProperties()) == 0 && tpe.DynamicProperties() == nil + case *types.Array: + return tpe.Len() == 0 && tpe.Dynamic() == nil + case *types.Set: + return tpe.Of() == nil + } + return false +} + // unifies checks whether two types are compatible with each other. func unifies(a, b types.Type) bool { @@ -950,14 +1173,20 @@ func unifies(a, b types.Type) bool { if !ok { return false } + // A set type without an element type is the empty set, which is a + // member of every set type. + if a.Of() == nil || b.Of() == nil { + return true + } return unifies(types.Values(a), types.Values(b)) case *types.Function: // NOTE(sr): variadic functions can only be internal ones, and we've forbidden // their replacement via `with`; so we disregard variadic here if types.Arity(a) == types.Arity(b) { - b := b.(*types.Function) - for i := range a.FuncArgs().Args { - if !unifies(a.FuncArgs().Arg(i), b.FuncArgs().Arg(i)) { + aArgs := a.FuncArgs() + bArgs := b.(*types.Function).FuncArgs() + for i := range aArgs.Args { + if !unifies(aArgs.Arg(i), bArgs.Arg(i)) { return false } } @@ -1056,10 +1285,42 @@ type ArgErrDetail struct { // Lines returns the string representation of the detail. func (d *ArgErrDetail) Lines() []string { - lines := make([]string, 2) - lines[0] = "have: " + formatArgs(d.Have) - lines[1] = "want: " + d.Want.String() - return lines + have := "have: " + formatArgs(d.Have) + want := "want: " + d.Want.String() + + if !tooWideForTypeErr(have, want) { + return []string{have, want} + } + + // Positions that only exist on one side, as is the case for arity errors, + // have nothing to be compared against, and are collapsed to their outermost + // type constructor. + haveArgs := util.Map(d.Have, elideType) + wantArgs := util.Map(d.Want.Args, elideType) + + for i := range min(len(haveArgs), len(wantArgs)) { + haveArgs[i], wantArgs[i] = diffArg(d.Have[i], d.Want.Args[i]) + } + + if d.Want.Variadic != nil { + wantArgs = append(wantArgs, elideType(d.Want.Variadic)+"...") + } + + return []string{ + "have: (" + strings.Join(haveArgs, ", ") + ")", + "want: (" + strings.Join(wantArgs, ", ") + ")", + } +} + +// diffArg renders an actual and an expected argument type side by side. The two +// are only diffed if they are actually in conflict: an argument that the +// function would have accepted is not what the error is about, and expanding it +// is what makes these messages unreadable in the first place. +func diffArg(have, want types.Type) (string, string) { + if have != nil && want != nil && unifies(unwrapNamedType(have), unwrapNamedType(want)) { + return elideType(have), elideType(want) + } + return sprintDiff(have, want) } func (d *ArgErrDetail) nilType() bool { @@ -1079,10 +1340,15 @@ func (a *UnificationErrDetail) nilType() bool { // Lines returns the string representation of the detail. func (a *UnificationErrDetail) Lines() []string { - lines := make([]string, 2) - lines[0] = fmt.Sprint("left : ", types.Sprint(a.Left)) - lines[1] = fmt.Sprint("right : ", types.Sprint(a.Right)) - return lines + leftLine := "left : " + types.Sprint(a.Left) + rightLine := "right : " + types.Sprint(a.Right) + + if !tooWideForTypeErr(leftLine, rightLine) { + return []string{leftLine, rightLine} + } + + left, right := sprintDiff(a.Left, a.Right) + return []string{"left : " + left, "right : " + right} } // RefErrUnsupportedDetail describes an undefined reference error where the @@ -1095,12 +1361,11 @@ type RefErrUnsupportedDetail struct { // Lines returns the string representation of the detail. func (r *RefErrUnsupportedDetail) Lines() []string { - lines := []string{ + return []string{ r.Ref.String(), strings.Repeat("^", len(r.Ref[:r.Pos+1].String())), fmt.Sprintf("have: %v", r.Have), } - return lines } // RefErrInvalidDetail describes an undefined reference error where the referenced @@ -1127,6 +1392,10 @@ func (r *RefErrInvalidDetail) Lines() []string { } if len(r.OneOf) > 0 { lines = append(lines, fmt.Sprintf("%swant (one of): %v", pad, r.OneOf)) + } else if r.Want == nil { + // Neither candidate keys nor a key type: the referenced value has no + // selectable keys at all (e.g. an empty object). + lines = append(lines, pad+"want (one of): []") } else { lines = append(lines, fmt.Sprintf("%swant (type): %v", pad, r.Want)) } @@ -1190,20 +1459,16 @@ func newArgError(loc *Location, builtinName Ref, msg string, have []types.Type, return err } -func getOneOfForNode(node *typeTreeNode) (result []Value) { - node.Children().Iter(func(k Value, _ *typeTreeNode) bool { - result = append(result, k) - return false - }) - - slices.SortFunc(result, Value.Compare) - return result +func getOneOfForNode(node *typeTreeNode) []Value { + return util.SortedFunc(node.Children().Keys(), Value.Compare) } func getOneOfForType(tpe types.Type) (result []Value) { switch tpe := tpe.(type) { case *types.Object: - for _, k := range tpe.Keys() { + keys := tpe.Keys() + result = slices.Grow(result, len(keys)) + for _, k := range keys { v, err := InterfaceToValue(k) if err != nil { panic(err) @@ -1215,15 +1480,14 @@ func getOneOfForType(tpe types.Type) (result []Value) { return getOneOfForType(tpe.Unwrap()) case types.Any: + result = slices.Grow(result, len(tpe)) for _, object := range tpe { objRes := getOneOfForType(object) result = append(result, objRes...) } } - result = removeDuplicate(result) - slices.SortFunc(result, Value.Compare) - return result + return util.SortedFunc(removeDuplicate(result), Value.Compare) } func removeDuplicate(list []Value) []Value { @@ -1273,16 +1537,11 @@ func override(ref Ref, t types.Type, o types.Type, rule *Rule) (types.Type, *Err } obj, ok := t.(*types.Object) if !ok { - newType, err := getObjectType(ref, o, rule, dynamicAnyAny) - if err != nil { - return nil, err - } - return newType, nil + return getObjectType(ref, o, rule, dynamicAnyAny) } found := false if ok { - staticProps := obj.StaticProperties() - for _, prop := range staticProps { + for _, prop := range obj.StaticProperties() { valueCopy := prop.Value key, err := InterfaceToValue(prop.Key) if err != nil { @@ -1316,7 +1575,7 @@ func override(ref Ref, t types.Type, o types.Type, rule *Rule) (types.Type, *Err } func getKeys(ref Ref, rule *Rule) ([]any, *Error) { - keys := []any{} + keys := make([]any, 0, len(ref)) for _, refElem := range ref { key, err := JSON(refElem.Value) if err != nil { @@ -1329,8 +1588,7 @@ func getKeys(ref Ref, rule *Rule) ([]any, *Error) { func getObjectTypeRec(keys []any, o types.Type, d *types.DynamicProperty) *types.Object { if len(keys) == 1 { - staticProps := []*types.StaticProperty{types.NewStaticProperty(keys[0], o)} - return types.NewObject(staticProps, d) + return types.NewObject([]*types.StaticProperty{types.NewStaticProperty(keys[0], o)}, d) } staticProps := []*types.StaticProperty{types.NewStaticProperty(keys[0], getObjectTypeRec(keys[1:], o, d))} @@ -1346,7 +1604,6 @@ func getObjectType(ref Ref, o types.Type, rule *Rule, d *types.DynamicProperty) } func getRuleAnnotation(as *AnnotationSet, rule *Rule) (result []*SchemaAnnotation) { - for _, x := range as.GetSubpackagesScope(rule.Module.Package.Path) { result = append(result, x.Schemas...) } @@ -1367,15 +1624,12 @@ func getRuleAnnotation(as *AnnotationSet, rule *Rule) (result []*SchemaAnnotatio } func processAnnotation(ss *SchemaSet, annot *SchemaAnnotation, rule *Rule, allowNet []string) (types.Type, *Error) { - var schema any - if annot.Schema != nil { if ss == nil { return nil, nil } - schema = ss.Get(annot.Schema) - if schema == nil { + if schema = ss.Get(annot.Schema); schema == nil { return nil, NewError(TypeErr, rule.Location, "undefined schema: %v", annot.Schema) } } else if annot.Definition != nil { diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/check_elide.go b/vendor/github.com/open-policy-agent/opa/v1/ast/check_elide.go new file mode 100644 index 0000000000..9f8ae05377 --- /dev/null +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/check_elide.go @@ -0,0 +1,399 @@ +// Copyright 2025 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package ast + +import ( + "fmt" + "strings" + "unicode/utf8" + + "github.com/open-policy-agent/opa/v1/types" + "github.com/open-policy-agent/opa/v1/util" +) + +const ( + typeElision = "..." + + // maxTypeErrLineWidth is the width above which a type error detail line is + // re-rendered with nested type information elided. Details that already fit + // are left alone: eliding them would drop information without buying any + // readability. + maxTypeErrLineWidth = 80 + + // maxElidedTypeWidth is the width above which a type that is not itself the + // subject of the mismatch is collapsed to its outermost constructor. + maxElidedTypeWidth = 32 + + // maxTypeDiffDepth bounds the parallel walk so that deeply nested types + // cannot produce an unreadable message, or, for cyclic types, no message. + maxTypeDiffDepth = 8 +) + +// sprintDiff returns the string representations of a and b, keeping only the +// structure that is needed to see how the two differ. Sub-types that are equal +// on both sides are collapsed to their outermost type constructor, and object +// properties and any-members that are equal on both sides are dropped +// altogether. An ellipsis marks everything that was left out. +func sprintDiff(a, b types.Type) (string, string) { + return diffTypes(a, b, 0) +} + +// sprintElided returns the string representation of t with any type information +// nested more than depth levels below t replaced by an ellipsis. A depth of zero +// keeps the outermost type constructor only; a negative depth renders t in full, +// exactly as types.Sprint does. +func sprintElided(t types.Type, depth int) string { + if depth < 0 { + return types.Sprint(t) + } + + switch t := t.(type) { + case nil: + return types.Sprint(t) + case *types.NamedType: + // A name is not a level of nesting, so depth is passed through. + return t.Name + ": " + sprintElided(t.Type, depth) + case *types.Set: + if t.Of() == nil { + // The empty set has no element type to elide. + return t.String() + } + if depth == 0 { + return "set[" + typeElision + "]" + } + return "set[" + sprintElided(t.Of(), depth-1) + "]" + case *types.Array: + static := make([]string, 0, t.Len()) + if depth == 0 && t.Len() > 0 { + static = append(static, typeElision) + } else { + for i := range t.Len() { + static = append(static, sprintElided(t.Select(i), depth-1)) + } + } + var dynamic string + if dyn := t.Dynamic(); dyn != nil { + if depth == 0 { + dynamic = typeElision + } else { + dynamic = sprintElided(dyn, depth-1) + } + } + return sprintArray(static, dynamic) + case *types.Object: + props := t.StaticProperties() + static := make([]string, 0, len(props)) + if depth == 0 && len(props) > 0 { + static = append(static, typeElision) + } else { + for _, p := range props { + static = append(static, sprintProperty(p.Key, sprintElided(p.Value, depth-1))) + } + } + var dynamic string + if dyn := t.DynamicProperties(); dyn != nil { + if depth == 0 { + dynamic = typeElision + } else { + dynamic = sprintElided(dyn.Key, depth-1) + ": " + sprintElided(dyn.Value, depth-1) + } + } + return sprintObject(static, dynamic) + case types.Any: + if len(t) == 0 { + return t.String() + } + if depth == 0 { + return sprintAny([]string{typeElision}) + } + of := make([]string, len(t)) + for i := range t { + of[i] = sprintElided(t[i], depth-1) + } + return sprintAny(of) + case *types.Function: + args := t.FuncArgs() + params := make([]string, 0, len(args.Args)+1) + if depth == 0 { + if len(args.Args) > 0 || args.Variadic != nil { + params = append(params, typeElision) + } + if t.Result() == nil { + return sprintFunction(params, types.Sprint(nil)) + } + return sprintFunction(params, typeElision) + } + for i := range args.Args { + params = append(params, sprintElided(args.Args[i], depth-1)) + } + if args.Variadic != nil { + params = append(params, sprintElided(args.Variadic, depth-1)+"...") + } + return sprintFunction(params, sprintElided(t.Result(), depth-1)) + default: + // Scalars, and recursive types, which are rendered by name only. + return t.String() + } +} + +// elideType collapses t to its outermost type constructor, unless it is short +// enough that spelling it out costs nothing. +func elideType(t types.Type) string { + if full := types.Sprint(t); utf8.RuneCountInString(full) <= maxElidedTypeWidth { + return full + } + return sprintElided(t, 0) +} + +func unwrapNamedType(t types.Type) types.Type { + if n, ok := t.(*types.NamedType); ok { + return n.Type + } + return t +} + +// typesEqual reports whether a and b would render identically. Comparing the +// rendered form, rather than the type structure, keeps this safe for recursive +// types, which render as a name rather than as their unrolled definition. +func typesEqual(a, b types.Type) bool { + return types.Sprint(a) == types.Sprint(b) +} + +func diffTypes(a, b types.Type, depth int) (string, string) { + // A name is not a level of nesting, so depth is passed through. + if n, ok := a.(*types.NamedType); ok { + left, right := diffTypes(n.Type, b, depth) + return n.Name + ": " + left, right + } + if n, ok := b.(*types.NamedType); ok { + left, right := diffTypes(a, n.Type, depth) + return left, n.Name + ": " + right + } + + if depth >= maxTypeDiffDepth || a == nil || b == nil || typesEqual(a, b) { + return elideType(a), elideType(b) + } + + switch a := a.(type) { + case *types.Set: + if b, ok := b.(*types.Set); ok && a.Of() != nil && b.Of() != nil { + left, right := diffTypes(a.Of(), b.Of(), depth+1) + return "set[" + left + "]", "set[" + right + "]" + } + case *types.Array: + if b, ok := b.(*types.Array); ok { + return diffArrays(a, b, depth) + } + case *types.Object: + if b, ok := b.(*types.Object); ok { + return diffObjects(a, b, depth) + } + case types.Any: + if b, ok := b.(types.Any); ok { + return diffAnys(a, b) + } + case *types.Function: + if b, ok := b.(*types.Function); ok && a.Arity() == b.Arity() { + return diffFunctions(a, b, depth) + } + } + + // The outermost type constructors already differ, which is all the reader + // needs to see. + return elideType(a), elideType(b) +} + +func diffArrays(a, b *types.Array, depth int) (string, string) { + if a.Len() != b.Len() || (a.Dynamic() == nil) != (b.Dynamic() == nil) { + // Element types are still shown so that it is clear which of the two is + // the longer, or which one has a dynamic tail. + return sprintElided(a, 1), sprintElided(b, 1) + } + + // Array elements are positional, so equal ones are collapsed rather than + // dropped, keeping the two renderings aligned. + left := make([]string, 0, a.Len()) + right := make([]string, 0, b.Len()) + for i := range a.Len() { + l, r := diffTypes(a.Select(i), b.Select(i), depth+1) + left = append(left, l) + right = append(right, r) + } + + var dynLeft, dynRight string + if a.Dynamic() != nil { + dynLeft, dynRight = diffTypes(a.Dynamic(), b.Dynamic(), depth+1) + } + + return sprintArray(left, dynLeft), sprintArray(right, dynRight) +} + +func diffObjects(a, b *types.Object, depth int) (string, string) { + aProps, bProps := a.StaticProperties(), b.StaticProperties() + + var left, right []string + var elided bool + + // Static properties are sorted by key on both sides, so they can be walked in + // parallel. Those that are equal say nothing about why the two types were + // reported together, and are dropped. + i, j := 0, 0 + for i < len(aProps) || j < len(bProps) { + switch { + case j == len(bProps): + left = append(left, sprintProperty(aProps[i].Key, elideType(aProps[i].Value))) + i++ + case i == len(aProps): + right = append(right, sprintProperty(bProps[j].Key, elideType(bProps[j].Value))) + j++ + default: + switch util.Compare(aProps[i].Key, bProps[j].Key) { + case -1: + left = append(left, sprintProperty(aProps[i].Key, elideType(aProps[i].Value))) + i++ + case 1: + right = append(right, sprintProperty(bProps[j].Key, elideType(bProps[j].Value))) + j++ + default: + if typesEqual(aProps[i].Value, bProps[j].Value) { + elided = true + } else { + l, r := diffTypes(aProps[i].Value, bProps[j].Value, depth+1) + left = append(left, sprintProperty(aProps[i].Key, l)) + right = append(right, sprintProperty(bProps[j].Key, r)) + } + i++ + j++ + } + } + } + + aDyn, bDyn := a.DynamicProperties(), b.DynamicProperties() + var dynLeft, dynRight string + switch { + case aDyn != nil && bDyn != nil: + keyLeft, keyRight := diffTypes(aDyn.Key, bDyn.Key, depth+1) + valLeft, valRight := diffTypes(aDyn.Value, bDyn.Value, depth+1) + dynLeft, dynRight = keyLeft+": "+valLeft, keyRight+": "+valRight + case aDyn != nil: + dynLeft = elideType(aDyn.Key) + ": " + elideType(aDyn.Value) + case bDyn != nil: + dynRight = elideType(bDyn.Key) + ": " + elideType(bDyn.Value) + } + + return sprintObject(withElision(left, elided), dynLeft), sprintObject(withElision(right, elided), dynRight) +} + +func diffAnys(a, b types.Any) (string, string) { + // The members of an Any are unordered as far as the reader is concerned, so + // those that appear on both sides are dropped rather than collapsed. + left := make([]string, 0, len(a)) + right := make([]string, 0, len(b)) + var elided bool + + for _, tpe := range a { + if containsType(b, tpe) { + elided = true + } else { + left = append(left, elideType(tpe)) + } + } + for _, tpe := range b { + if !containsType(a, tpe) { + right = append(right, elideType(tpe)) + } + } + + return sprintAny(withElision(left, elided)), sprintAny(withElision(right, elided)) +} + +func diffFunctions(a, b *types.Function, depth int) (string, string) { + aArgs, bArgs := a.FuncArgs(), b.FuncArgs() + + left := make([]string, 0, len(aArgs.Args)+1) + right := make([]string, 0, len(bArgs.Args)+1) + for i := range aArgs.Args { + l, r := diffTypes(aArgs.Args[i], bArgs.Args[i], depth+1) + left = append(left, l) + right = append(right, r) + } + switch { + case aArgs.Variadic != nil && bArgs.Variadic != nil: + l, r := diffTypes(aArgs.Variadic, bArgs.Variadic, depth+1) + left = append(left, l+"...") + right = append(right, r+"...") + case aArgs.Variadic != nil: + left = append(left, elideType(aArgs.Variadic)+"...") + case bArgs.Variadic != nil: + right = append(right, elideType(bArgs.Variadic)+"...") + } + + resLeft, resRight := diffTypes(a.Result(), b.Result(), depth+1) + return sprintFunction(left, resLeft), sprintFunction(right, resRight) +} + +func containsType(haystack types.Any, needle types.Type) bool { + for _, tpe := range haystack { + if typesEqual(tpe, needle) { + return true + } + } + return false +} + +func withElision(parts []string, elided bool) []string { + if !elided { + return parts + } + return append(parts, typeElision) +} + +func sprintProperty(key any, value string) string { + return fmt.Sprintf("%v: %v", key, value) +} + +func sprintArray(static []string, dynamic string) string { + return sprintComposite("array", static, dynamic) +} + +func sprintObject(static []string, dynamic string) string { + return sprintComposite("object", static, dynamic) +} + +func sprintComposite(prefix string, static []string, dynamic string) string { + sb := strings.Builder{} + sb.WriteString(prefix) + if len(static) > 0 { + sb.WriteString("<") + sb.WriteString(strings.Join(static, ", ")) + sb.WriteString(">") + } + if dynamic != "" { + sb.WriteString("[") + sb.WriteString(dynamic) + sb.WriteString("]") + } + return sb.String() +} + +func sprintAny(of []string) string { + if len(of) == 0 { + return "any" + } + return "any<" + strings.Join(of, ", ") + ">" +} + +func sprintFunction(args []string, result string) string { + return "(" + strings.Join(args, ", ") + ") => " + result +} + +func tooWideForTypeErr(lines ...string) bool { + for _, line := range lines { + if utf8.RuneCountInString(line) > maxTypeErrLineWidth { + return true + } + } + return false +} diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/compare.go b/vendor/github.com/open-policy-agent/opa/v1/ast/compare.go index ef1ba033fd..d3dde15743 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/compare.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/compare.go @@ -8,7 +8,10 @@ import ( "cmp" "fmt" "math/big" + "slices" "strings" + + "github.com/open-policy-agent/opa/v1/util" ) // Compare returns an integer indicating whether two AST values are less than, @@ -104,10 +107,9 @@ func Compare(a, b any) int { case Var: return VarCompare(a, b.(Var)) case Ref: - return termSliceCompare(a, b.(Ref)) + return slices.CompareFunc(a, b.(Ref), TermValueCompare) case *Array: - b := b.(*Array) - return termSliceCompare(a.elems, b.elems) + return slices.CompareFunc(a.elems, b.(*Array).elems, TermValueCompare) case *lazyObj: return Compare(a.force(), b) case *object: @@ -127,7 +129,7 @@ func Compare(a, b any) int { b := b.(*SetComprehension) return a.Compare(b) case Call: - return termSliceCompare(a, b.(Call)) + return slices.CompareFunc(a, b.(Call), TermValueCompare) case *Expr: return a.Compare(b.(*Expr)) case *SomeDecl: @@ -147,7 +149,7 @@ func Compare(a, b any) int { case *Rule: return a.Compare(b.(*Rule)) case Args: - return termSliceCompare(a, b.(Args)) + return slices.CompareFunc(a, b.(Args), TermValueCompare) case *Import: return a.Compare(b.(*Import)) case *Package: @@ -244,84 +246,6 @@ func sortOrder(x any) int { panic(fmt.Sprintf("illegal value: %T", x)) } -func importsCompare(a, b []*Import) int { - minLen := min(len(b), len(a)) - for i := range minLen { - if cmp := a[i].Compare(b[i]); cmp != 0 { - return cmp - } - } - if len(a) < len(b) { - return -1 - } - if len(b) < len(a) { - return 1 - } - return 0 -} - -func annotationsCompare(a, b []*Annotations) int { - minLen := min(len(b), len(a)) - for i := range minLen { - if cmp := a[i].Compare(b[i]); cmp != 0 { - return cmp - } - } - if len(a) < len(b) { - return -1 - } - if len(b) < len(a) { - return 1 - } - return 0 -} - -func rulesCompare(a, b []*Rule) int { - minLen := min(len(b), len(a)) - for i := range minLen { - if cmp := a[i].Compare(b[i]); cmp != 0 { - return cmp - } - } - if len(a) < len(b) { - return -1 - } - if len(b) < len(a) { - return 1 - } - return 0 -} - -func termSliceCompare(a, b []*Term) int { - minLen := min(len(b), len(a)) - for i := range minLen { - if cmp := a[i].Value.Compare(b[i].Value); cmp != 0 { - return cmp - } - } - if len(a) < len(b) { - return -1 - } else if len(b) < len(a) { - return 1 - } - return 0 -} - -func withSliceCompare(a, b []*With) int { - minLen := min(len(b), len(a)) - for i := range minLen { - if cmp := a[i].Compare(b[i]); cmp != 0 { - return cmp - } - } - if len(a) < len(b) { - return -1 - } else if len(b) < len(a) { - return 1 - } - return 0 -} - func VarCompare(a, b Var) int { if a == b { return 0 @@ -333,6 +257,9 @@ func VarCompare(a, b Var) int { } func TermValueCompare(a, b *Term) int { + if a == b { + return 0 + } return a.Value.Compare(b.Value) } @@ -346,6 +273,8 @@ func ValueEqual(a, b Value) bool { return v.Equal(b) case *Array: return v.Equal(b) + case *object: + return v.Equal(b) case *Not: return v.Equal(b) case *TemplateString: @@ -356,46 +285,51 @@ func ValueEqual(a, b Value) bool { } func RefCompare(a, b Ref) int { - return termSliceCompare(a, b) + return slices.CompareFunc(a, b, TermValueCompare) } func RefEqual(a, b Ref) bool { - return termSliceEqual(a, b) + return slices.EqualFunc(a, b, (*Term).Equal) } func NumberCompare(x, y Number) int { xs, ys := string(x), string(y) - - var xIsF, yIsF bool - - // Treat "1" and "1.0", "1.00", etc as "1" - if strings.Contains(xs, ".") { - if tx := strings.TrimRight(xs, ".0"); tx != xs { - // Still a float after trimming? - xIsF = strings.Contains(tx, ".") - xs = tx - } - } - if strings.Contains(ys, ".") { - if ty := strings.TrimRight(ys, ".0"); ty != ys { - yIsF = strings.Contains(ty, ".") - ys = ty - } - } if xs == ys { return 0 } var xi, yi int64 - var xf, yf float64 var xiOK, yiOK, xfOK, yfOK bool - if xi, xiOK = x.Int64(); xiOK { - if yi, yiOK = y.Int64(); yiOK { + if xi, xiOK = util.Atoi64(xs); xiOK { + if yi, yiOK = util.Atoi64(ys); yiOK { return cmp.Compare(xi, yi) } } + var xf, yf float64 + var xIsF, yIsF bool + + // Treat "1" and "1.0", "1.00", etc as "1" for the purpose of deciding + // whether each side is a non-integral value. + // + // The trimmed forms must not be assigned back over xs and ys. TrimRight + // takes a cutset rather than a suffix, so ".0" strips every trailing '.' + // and '0' character: "0.0" trims to the empty string and "-0.0" to "-". + // Those are then handed to big.Float.SetString below, which fails, and the + // failure path is a panic. + if strings.IndexByte(xs, '.') != -1 { + if tx := strings.TrimRight(xs, ".0"); tx != xs { + // Still a float after trimming? + xIsF = strings.IndexByte(tx, '.') != -1 + } + } + if strings.IndexByte(ys, '.') != -1 { + if ty := strings.TrimRight(ys, ".0"); ty != ys { + yIsF = strings.IndexByte(ty, '.') != -1 + } + } + if xIsF && yIsF { if xf, xfOK = x.Float64(); xfOK { if yf, yfOK = y.Float64(); yfOK { @@ -408,7 +342,7 @@ func NumberCompare(x, y Number) int { } var a *big.Rat - fa, ok := new(big.Float).SetString(string(x)) + fa, ok := new(big.Float).SetString(xs) if !ok { panic("illegal value") } @@ -418,14 +352,14 @@ func NumberCompare(x, y Number) int { } } if a == nil { - a, ok = new(big.Rat).SetString(string(x)) + a, ok = new(big.Rat).SetString(xs) if !ok { panic("illegal value") } } var b *big.Rat - fb, ok := new(big.Float).SetString(string(y)) + fb, ok := new(big.Float).SetString(ys) if !ok { panic("illegal value") } @@ -435,7 +369,7 @@ func NumberCompare(x, y Number) int { } } if b == nil { - b, ok = new(big.Rat).SetString(string(y)) + b, ok = new(big.Rat).SetString(ys) if !ok { panic("illegal value") } diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/compile.go b/vendor/github.com/open-policy-agent/opa/v1/ast/compile.go index c642c508a8..dae5320889 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/compile.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/compile.go @@ -11,7 +11,6 @@ import ( "io" "maps" "slices" - "sort" "strings" "sync" @@ -23,14 +22,31 @@ import ( "github.com/open-policy-agent/opa/v1/util" ) -// CompileErrorLimitDefault is the default number errors a compiler will allow before -// exiting. -const CompileErrorLimitDefault = 10 +const ( + // CompileErrorLimitDefault is the default number + // of errors a compiler will allow before exiting. + CompileErrorLimitDefault = 10 + LocalVarPrefix = "__local" + + errAssignInNegated = "cannot assign vars inside negated expression" + errAssignInAndOperand = "cannot assign vars inside implicit and operand" + errAssignInOrOperand = "cannot assign vars inside implicit or operand" +) var ( - errLimitReached = newErrorString(CompileErr, nil, "error limit reached") - - doubleEq = Equal.Ref() + // SafetyCheckVisitorParams defines the AST visitor parameters to use for collecting + // variables during the safety check. This has to be exported because it's relied on + // by the copy propagation implementation in topdown. + SafetyCheckVisitorParams = VarVisitorParams{SkipRefCallHead: true, SkipClosures: true} + // TODO(tsandall): Improve this so that users can either supply this list explicitly + // or the information is maintained on the built-in function declaration. What we really + // need to know is whether the built-in function allows callers to push down output + // values or not. It's unlikely that anything outside of OPA does this today so this + // solution is fine for now. + comprehensionIndexDenylist = map[string]int{WalkBuiltin.Name: len(WalkBuiltin.Decl.FuncArgs().Args)} + errLimitReached = newErrorString(CompileErr, nil, "error limit reached") + emptyPackage = &Package{Path: Ref{VarTerm("")}} + futureKeywordsPrefix = Ref{FutureRootDocument, InternedTerm("keywords")} ) // Compiler contains the state of a compilation process. @@ -163,6 +179,8 @@ type Compiler struct { defaultRegoVersion RegoVersion skipStages map[StageID]struct{} // stages to skip during compilation plan *executionPlan // computed execution plan (cached) + unusedImports []*Import // imports found unused during ref resolution, reported by CheckUnusedImports + unrecoverableErr bool // at least one recorded error prevents the remaining stages from running } func (c *Compiler) DefaultRegoVersion() RegoVersion { @@ -181,6 +199,7 @@ type StageID string // at least lets you know what your attention is needed when you depend on the stages. const ( StageResolveRefs StageID = "ResolveRefs" + StageCheckUnusedImports StageID = "CheckUnusedImports" StageInitLocalVarGen StageID = "InitLocalVarGen" StageRewriteRuleHeadRefs StageID = "RewriteRuleHeadRefs" StageCheckKeywordOverrides StageID = "CheckKeywordOverrides" @@ -223,6 +242,7 @@ const ( func AllStages() []StageID { return []StageID{ StageResolveRefs, + StageCheckUnusedImports, StageInitLocalVarGen, StageRewriteRuleHeadRefs, StageCheckKeywordOverrides, @@ -262,7 +282,7 @@ func AllStages() []StageID { // CompilerEvalMode allows toggling certain stages that are only // needed for certain modes, Concretely, only "topdown" mode will // have the compiler build comprehension and rule indices. -type CompilerEvalMode int +type CompilerEvalMode uint8 const ( // EvalModeTopdown (default) instructs the compiler to build rule @@ -378,7 +398,7 @@ type QueryCompiler interface { // WithStageAfter registers a stage to run during query compilation after // the named stage. // - // Caution: Use [ast.QueryCompiler.WithStageAfterID] instead. It provides + // Caution: Use [QueryCompiler.WithStageAfterID] instead. It provides // more (Golang) compile-time safety WithStageAfter(after string, stage QueryCompilerStageDefinition) QueryCompiler @@ -441,6 +461,7 @@ func NewCompiler() *Compiler { // load additional modules. If any stages run before resolution, they // need to be re-run after resolution. {StageResolveRefs, "compile_stage_resolve_refs", c.resolveAllRefs}, + {StageCheckUnusedImports, "compile_stage_check_unused_imports", c.checkUnusedImports}, // The local variable generator must be initialized after references are // resolved and the dynamic module loader has run but before subsequent // stages that need to generate variables. @@ -468,7 +489,7 @@ func NewCompiler() *Compiler { {StageCheckSafetyRuleHeads, "compile_stage_check_safety_rule_heads", c.checkSafetyRuleHeads}, {StageCheckSafetyRuleBodies, "compile_stage_check_safety_rule_bodies", c.checkSafetyRuleBodies}, {StageRewriteEquals, "compile_stage_rewrite_equals", c.rewriteEquals}, - {StageRewriteDynamicTerms, "compile_stage_rewrite_dynamic_terms", c.rewriteDynamicTerms}, + {StageRewriteDynamicTerms, "compile_stage_rewrite_dynamic_terms", c.rewriteDynamicTerms}, // stages before CheckTypes must not rewrite hoisted terms, see recordSubjectNoCopy {StageRewriteTestRulesForTracing, "compile_stage_rewrite_test_rules_for_tracing", c.rewriteTestRuleEqualities}, // must run after RewriteDynamicTerms {StageCheckRecursion, "compile_stage_check_recursion", c.checkRecursion}, {StageCheckTypes, "compile_stage_check_types", c.checkTypes}, // must be run after CheckRecursion @@ -517,7 +538,7 @@ func (c *Compiler) WithPathConflictsCheckRoots(rootPaths []string) *Compiler { // WithStageAfter registers a stage to run during compilation after // the named stage. // -// Caution: Consider using [ast.QueryCompiler.WithStageAfterID] instead. It provides +// Caution: Consider using [Compiler.WithStageAfterID] instead. It provides // more (Golang) compile-time safety func (c *Compiler) WithStageAfter(after string, stage CompilerStageDefinition) *Compiler { c.after[after] = append(c.after[after], stage) @@ -672,7 +693,7 @@ func (c *Compiler) Compile(modules map[string]*Module) { c.init() c.Modules = make(map[string]*Module, len(modules)) - c.sorted = make([]string, 0, len(modules)) + c.sorted = util.KeysSorted(modules) if c.keepModules { c.parsedModules = make(map[string]*Module, len(modules)) @@ -682,14 +703,11 @@ func (c *Compiler) Compile(modules map[string]*Module) { for k, v := range modules { c.Modules[k] = v.Copy() - c.sorted = append(c.sorted, k) if c.parsedModules != nil { c.parsedModules[k] = v } } - sort.Strings(c.sorted) - c.compile() } @@ -840,23 +858,15 @@ func (c *Compiler) GetRulesWithPrefix(ref Ref) (rules []*Rule) { // GetRules("data.a.b.c.q") => [rule2] // GetRules("data.a.b.c") => [rule1, rule2] // GetRules("data.a.b.d") => nil -func (c *Compiler) GetRules(ref Ref) (rules []*Rule) { +func (c *Compiler) GetRules(ref Ref) []*Rule { + virt := c.GetRulesForVirtualDocument(ref) + pref := c.GetRulesWithPrefix(ref) - set := map[*Rule]struct{}{} + set := make(map[*Rule]struct{}, len(virt)+len(pref)) + insertRules(set, virt) + insertRules(set, pref) - for _, rule := range c.GetRulesForVirtualDocument(ref) { - set[rule] = struct{}{} - } - - for _, rule := range c.GetRulesWithPrefix(ref) { - set[rule] = struct{}{} - } - - for rule := range set { - rules = append(rules, rule) - } - - return rules + return util.Keys(set) } // GetRulesDynamic returns a slice of rules that could be referred to by a ref. @@ -928,7 +938,7 @@ func (c *Compiler) GetRulesDynamicWithOpts(ref Ref, opts RulesOptions) []*Rule { if child := node.Child(ref[i].Value); child != nil { if len(child.Values) > 0 { // Add any rules at this position - insertRules(set, child.Values) + insertRulesIntersecting(set, child.Values, ref, i+1) } // There might still be "sub-rules" contributing key-value "overrides" for e.g. partial object rules, continue walking walk(child, i+1) @@ -943,18 +953,14 @@ func (c *Compiler) GetRulesDynamicWithOpts(ref Ref, opts RulesOptions) []*Rule { if child.Hide && !opts.IncludeHiddenModules { continue } - insertRules(set, child.Values) + insertRulesIntersecting(set, child.Values, ref, i+1) walk(child, i+1) } } } walk(node, 0) - rules := make([]*Rule, 0, len(set)) - for rule := range set { - rules = append(rules, rule) - } - return rules + return util.Keys(set) } // Utility: add all rule values to the set. @@ -964,6 +970,31 @@ func insertRules(set map[*Rule]struct{}, rules []*Rule) { } } +// insertRulesIntersecting adds the rules whose refs could still intersect ref +// beyond position i. Rules with general refs are all stored at the ground +// prefix of their ref, so a rule like data.a.p[x].foo.bar sits at data.a.p +// alongside data.a.p[x].foo.baz. Without comparing the remaining parts, a ref +// to one of them would appear to refer to both. +func insertRulesIntersecting(set map[*Rule]struct{}, rules []*Rule, ref Ref, i int) { + for _, rule := range rules { + if refsMayIntersect(rule.Ref(), ref, i) { + set[rule] = struct{}{} + } + } +} + +// refsMayIntersect compares a and b from position i onwards, treating parts +// that aren't statically known as matching anything. +func refsMayIntersect(a, b Ref, i int) bool { + for ; i < len(a) && i < len(b); i++ { + x, y := a[i].Value, b[i].Value + if IsConstant(x) && IsConstant(y) && x.Compare(y) != 0 { + return false + } + } + return true +} + // RuleIndex returns a RuleIndex built for the rule set referred to by path. // The path must refer to the rule set exactly, i.e., given a rule set at path // data.a.b.c.p, refs data.a.b.c.p.x and data.a.b.c would not return a @@ -985,11 +1016,7 @@ func (c *Compiler) PassesTypeCheck(body Body) bool { // PassesTypeCheckRules determines whether the given rules passes type checking func (c *Compiler) PassesTypeCheckRules(rules []*Rule) Errors { - elems := make([]util.T, 0, len(rules)) - - for _, rule := range rules { - elems = append(elems, rule) - } + elems := util.ToSliceOf[util.T](rules) // Load the global input schema if one was provided. if c.schemaSet != nil { @@ -1109,9 +1136,7 @@ func (c *Compiler) buildExecutionPlan() *executionPlan { // getOrBuildPlan ensures we have a valid execution plan. func (c *Compiler) getOrBuildPlan() *executionPlan { - if c.plan == nil { - c.plan = c.buildExecutionPlan() - } + c.plan = util.Or(c.plan, c.buildExecutionPlan) return c.plan } @@ -1161,12 +1186,13 @@ func (c *Compiler) buildRuleIndices() { // b.c[x].e := 1 { x := input.x } // b.c.d := 2 // b.c.d2.e[x] := 3 { x := input.x } - for _, child := range node.Children { - child.DepthFirst(func(c *TreeNode) bool { - rules = append(rules, c.Values...) - return false - }) - } + // Cleared rather than truncated: rules aliases node.Values, which the + // walk hands back along with everything below it. + rules = nil + node.DepthFirst(func(c *TreeNode) bool { + rules = append(rules, c.Values...) + return false + }) } index := newBaseDocEqIndex(c.isVirtual) @@ -1196,8 +1222,6 @@ func (c *Compiler) buildComprehensionIndices() { varVisitorPool.Put(vis) } -var futureKeywordsPrefix = Ref{FutureRootDocument, InternedTerm("keywords")} - // buildRequiredCapabilities updates the required capabilities on the compiler // to include any keyword and feature dependencies present in the modules. The // built-in function dependencies will have already been added by the type @@ -1223,7 +1247,6 @@ func (c *Compiler) buildRequiredCapabilities() { if c.moduleIsRegoV1(c.Modules[name]) { for kw := range futureKeywords { // Don't output experimental keywords for wildcard imports - // TODO: Remove on and/or release if _, internal := experimentalFutureKeywords[kw]; internal { continue } @@ -1231,7 +1254,6 @@ func (c *Compiler) buildRequiredCapabilities() { } } else { for kw := range allFutureKeywords { - // TODO: Remove on and/or release if _, internal := experimentalFutureKeywords[kw]; internal { continue } @@ -1314,18 +1336,19 @@ func (c *Compiler) checkRecursion() { func (c *Compiler) checkSelfPath(loc *Location, eq func(a, b util.T) bool, a, b util.T) { tr := NewGraphTraversal(c.Graph) if p := util.DFSPath(tr, eq, a, b); len(p) > 0 { + rw := rewriteVarsInRef(c.RewrittenVars) n := make([]string, 0, len(p)) for _, x := range p { - n = append(n, astNodeToString(x)) + n = append(n, astNodeToString(rw, x)) } - if !c.err(NewError(RecursionErr, loc, "rule %v is recursive: %v", astNodeToString(a), strings.Join(n, " -> "))) { + if !c.err(NewError(RecursionErr, loc, "rule %v is recursive: %v", astNodeToString(rw, a), strings.Join(n, " -> "))) { return } } } -func astNodeToString(x any) string { - return x.(*Rule).Ref().String() +func astNodeToString(rw varRewriter, x any) string { + return rw(x.(*Rule).Ref().CopyNonGround()).String() // varRewriter operates in-place } // checkRuleConflicts ensures that rules definitions are not in conflict. @@ -1494,8 +1517,7 @@ func (c *Compiler) checkRuleConflicts() { func (c *Compiler) checkUndefinedFuncs() { for _, name := range c.sorted { - m := c.Modules[name] - c.err(checkUndefinedFuncs(c.TypeEnv, m, c.GetArity, c.RewrittenVars)...) + c.err(checkUndefinedFuncs(c.TypeEnv, c.Modules[name], c.GetArity, c.RewrittenVars)...) } } @@ -1534,10 +1556,10 @@ func checkUndefinedFuncs(env *TypeEnv, x any, arity func(Ref) int, rwVars map[Va } func arityMismatchError(env *TypeEnv, f Ref, expr *Expr, exp, act int) *Error { - if want, ok := env.Get(f).(*types.Function); ok { // generate richer error for built-in functions + if want, ok := env.GetByRef(f).(*types.Function); ok { // generate richer error for built-in functions have := make([]types.Type, len(expr.Operands())) for i, op := range expr.Operands() { - have[i] = env.Get(op) + have[i] = env.GetByValue(op.Value) } return newArgError(expr.Loc(), f, "arity mismatch", have, want.NamedFuncArgs()) } @@ -1554,47 +1576,45 @@ func (c *Compiler) checkSafetyRuleBodies() { vis := varVisitorPool.Get() for _, name := range c.sorted { - m := c.Modules[name] - WalkRules(m, func(r *Rule) bool { - vis = vis.Clear() - // vis.vars == safe - vis.vars.Update(ReservedVars) - if len(r.Head.Args) > 0 { - vis.WalkArgs(r.Head.Args) - } - r.Body = c.checkBodySafety(vis.vars, r.Body) - return false - }) + scopes := ruleScopes{module: c.Modules[name]} + for _, rule := range c.Modules[name].Rules { + WalkRules(rule, func(r *Rule) bool { + vis = vis.Clear() + // vis.vars == safe + vis.vars.Update(ReservedVars) + if len(r.Head.Args) > 0 { + vis.WalkArgs(r.Head.Args) + } + r.Body = c.checkBodySafety(vis.vars, r.Body, r, &scopes) + return false + }) + } } varVisitorPool.Put(vis) } -func (c *Compiler) checkBodySafety(safe VarSet, b Body) Body { +func (c *Compiler) checkBodySafety(safe VarSet, b Body, r *Rule, scopes *ruleScopes) Body { reordered, unsafe := reorderBodyForSafety(c.builtins, c.GetArity, safe, b) - if errs := safetyErrorSlice(unsafe, c.RewrittenVars); len(errs) > 0 { + if len(unsafe) == 0 { + return reordered + } + if errs := safetyErrorSlice(unsafe, c.RewrittenVars, scopes.scope(r)); len(errs) > 0 { c.err(errs...) return b } return reordered } -// SafetyCheckVisitorParams defines the AST visitor parameters to use for collecting -// variables during the safety check. This has to be exported because it's relied on -// by the copy propagation implementation in topdown. -// TODO: deprecate? -var SafetyCheckVisitorParams = VarVisitorParams{ - SkipRefCallHead: true, - SkipClosures: true, -} - // checkSafetyRuleHeads ensures that variables appearing in the head of a // rule also appear in the body. func (c *Compiler) checkSafetyRuleHeads() { vis := varVisitorPool.Get() for _, name := range c.sorted { - WalkRules(c.Modules[name], func(r *Rule) bool { + m := c.Modules[name] + scopes := ruleScopes{module: m} + WalkRules(m, func(r *Rule) bool { if headMayHaveVars(r.Head) { vis = vis.Clear().WithParams(SafetyCheckVisitorParams) vis.WalkBody(r.Body) @@ -1607,6 +1627,7 @@ func (c *Compiler) checkSafetyRuleHeads() { vars := r.Head.Vars() if vars.DiffCount(vis.vars) > 0 { unsafe := vars.Diff(vis.vars) + scope := scopes.scope(r) for v := range unsafe { // vars is keyed by the original name, so the location must be // read before v is replaced with the rewritten one -- otherwise @@ -1616,7 +1637,7 @@ func (c *Compiler) checkSafetyRuleHeads() { v = w } if !v.IsGenerated() { - if !c.err(NewError(UnsafeVarErr, loc, "var %v is unsafe", v)) { + if !c.err(NewError(UnsafeVarErr, loc, "var %v is unsafe%v", v, scope)) { return true } } @@ -1631,10 +1652,9 @@ func (c *Compiler) checkSafetyRuleHeads() { } func compileSchema(goSchema any, allowNet []string) (*gojsonschema.Schema, error) { - gojsonschema.SetAllowNet(allowNet) - var refLoader gojsonschema.JSONLoader sl := gojsonschema.NewSchemaLoader() + sl.AllowNet = allowNet if goSchema != nil { refLoader = gojsonschema.NewGoLoader(goSchema) @@ -1906,29 +1926,59 @@ func (c *Compiler) checkTypes() { WithInputType(c.inputType). WithBuiltins(c.builtins). WithRequiredCapabilities(c.Required). - WithVarRewriter(rewriteVarsInRef(c.RewrittenVars)). + WithVarRewriter(rewriteRefErrVars(c.localvargen.subjects, c.RewrittenVars)). + WithDependentsResolver(c.dependentRuleRefs). WithAllowUndefinedFunctionCalls(c.allowUndefinedFuncCalls) var as *AnnotationSet if c.useTypeCheckAnnotations { as = c.annotationSet } env, errs := checker.CheckTypes(c.TypeEnv, sorted, as) - for _, err := range errs { - c.err(err) - } + c.errRecoverable(errs...) c.TypeEnv = env } +// dependentRuleRefs returns the refs of the rules that ref could refer to, +// together with the refs of the rules that transitively depend on them. +func (c *Compiler) dependentRuleRefs(ref Ref) []Ref { + if c.Graph == nil { + return nil + } + + rules := c.GetRulesDynamicWithOpts(ref, RulesOptions{IncludeHiddenModules: true}) + if len(rules) == 0 { + return nil + } + + refs := make([]Ref, 0, len(rules)) + visited := make(map[*Rule]struct{}, len(rules)) + + var visit func(*Rule) + visit = func(rule *Rule) { + if _, ok := visited[rule]; ok { + return + } + visited[rule] = struct{}{} + refs = append(refs, rule.Ref().GroundPrefix()) + for dependent := range c.Graph.Dependents(rule) { + visit(dependent.(*Rule)) + } + } + + for _, rule := range rules { + visit(rule) + } + + return refs +} + func (c *Compiler) checkUnsafeBuiltins() { if len(c.unsafeBuiltinsMap) == 0 { return } for _, name := range c.sorted { - errs := checkUnsafeBuiltins(c.unsafeBuiltinsMap, c.Modules[name]) - for _, err := range errs { - c.err(err) - } + c.errRecoverable(checkUnsafeBuiltins(c.unsafeBuiltinsMap, c.Modules[name])...) } } @@ -1946,10 +1996,7 @@ func (c *Compiler) checkDeprecatedBuiltins() { for _, name := range c.sorted { if c.strict || c.Modules[name].regoV1Compatible() { - errs := checkDeprecatedBuiltins(c.deprecatedBuiltinsMap, c.Modules[name]) - for _, err := range errs { - c.err(err) - } + c.errRecoverable(checkDeprecatedBuiltins(c.deprecatedBuiltinsMap, c.Modules[name])...) } } } @@ -1957,25 +2004,40 @@ func (c *Compiler) checkDeprecatedBuiltins() { func (c *Compiler) compile() { plan := c.getOrBuildPlan() + defer c.sortErrors() + if c.metrics != nil { for _, s := range plan.stages { c.metrics.Timer(s.metricName).Start() s.f() c.metrics.Timer(s.metricName).Stop() - if c.Failed() { + if c.unrecoverableErr { return } } } else { for _, s := range plan.stages { s.f() - if c.Failed() { + if c.unrecoverableErr { return } } } } +// sortErrors orders errors by location so reports read top-to-bottom. The error +// limit marker has no location of its own and is swapped to the end to keep it +// last, wherever it was recorded. +func (c *Compiler) sortErrors() { + errs := c.Errors + if i := slices.Index(errs, errLimitReached); i >= 0 { + errs[i], errs[len(errs)-1] = errs[len(errs)-1], errs[i] + errs = errs[:len(errs)-1] + } + + errs.Sort() +} + func (c *Compiler) init() { if c.initialized { @@ -2052,7 +2114,19 @@ func (c *Compiler) init() { c.initialized = true } +// err records an error that stops compilation after the current stage. func (c *Compiler) err(errs ...*Error) bool { // returns if we should continue + return c.recordErrs(false, errs...) +} + +// errRecoverable records an error that lets the remaining stages run, so that one +// compilation can report every violation it finds. Only for checks that leave the +// modules in a state later stages can't produce bogus follow-up errors from. +func (c *Compiler) errRecoverable(errs ...*Error) bool { + return c.recordErrs(true, errs...) +} + +func (c *Compiler) recordErrs(recoverable bool, errs ...*Error) bool { if len(errs) == 0 { return true } @@ -2062,6 +2136,7 @@ func (c *Compiler) err(errs ...*Error) bool { // returns if we should continue if c.maxErrs <= 0 { c.Errors = append(c.Errors, errs...) + c.unrecoverableErr = c.unrecoverableErr || !recoverable return true } @@ -2080,6 +2155,8 @@ func (c *Compiler) err(errs ...*Error) bool { // returns if we should continue c.errCount += uint32(numToTake) c.Errors = append(c.Errors, errs[:numToTake]...) + // Nothing left to collect once the limit is hit, so stop there too. + c.unrecoverableErr = c.unrecoverableErr || !recoverable || isLimitReachedInThisCall if isLimitReachedInThisCall { c.Errors = append(c.Errors, errLimitReached) } @@ -2087,44 +2164,37 @@ func (c *Compiler) err(errs ...*Error) bool { // returns if we should continue return !isLimitReachedInThisCall // Return false if the limit was reached, true otherwise. } +func (c *Compiler) getExport(pkg Ref) []Ref { + var refs []Ref + for _, name := range c.sorted { + if RefEqual(pkg, c.Modules[name].Package.Path) { + refs = slices.Grow(refs, len(c.Modules[name].Rules)) + for _, rule := range c.Modules[name].Rules { + refs = append(refs, rule.Head.Ref().GroundPrefix()) + } + } + } + return refs +} + +// getExports groups every module's exported rule refs by package path in one +// pass. Use this instead of getExport per package, which is quadratic. func (c *Compiler) getExports() *util.HasherMap[Ref, []Ref] { rules := util.NewHasherMap[Ref, []Ref](RefEqual) for _, name := range c.sorted { - for _, rule := range c.Modules[name].Rules { - hashMapAdd(rules, c.Modules[name].Package.Path, rule.Head.Ref().GroundPrefix()) + mod := c.Modules[name] + refs, _ := rules.Get(mod.Package.Path) + refs = slices.Grow(refs, len(mod.Rules)) + for _, rule := range mod.Rules { + refs = append(refs, rule.Head.Ref().GroundPrefix()) } + rules.Put(mod.Package.Path, refs) } return rules } -func refSliceEqual(a, b []Ref) bool { - if len(a) != len(b) { - return false - } - for i := range a { - if !a[i].Equal(b[i]) { - return false - } - } - return true -} - -func hashMapAdd(rules *util.HasherMap[Ref, []Ref], pkg, rule Ref) { - prev, ok := rules.Get(pkg) - if !ok { - rules.Put(pkg, []Ref{rule}) - return - } - for _, p := range prev { - if p.Equal(rule) { - return - } - } - rules.Put(pkg, append(prev, rule)) -} - func (c *Compiler) GetAnnotationSet() *AnnotationSet { return c.annotationSet } @@ -2138,7 +2208,7 @@ func (c *Compiler) checkImports() { for _, name := range c.sorted { for _, imp := range c.Modules[name].Imports { if !supportsRegoV1Import && RegoV1CompatibleRef.Equal(imp.Path.Value) { - if !c.err(NewError(CompileErr, imp.Loc(), "rego.v1 import is not supported")) { + if !c.errRecoverable(NewError(CompileErr, imp.Loc(), "rego.v1 import is not supported")) { continue } } @@ -2149,13 +2219,25 @@ func (c *Compiler) checkImports() { } } - c.err(checkDuplicateImports(modules)...) + c.errRecoverable(checkDuplicateImports(modules)...) +} + +// checkUnusedImports reports the imports resolveAllRefs found unused. Strict mode +// only, and a stage of its own so these don't cut compilation short. +func (c *Compiler) checkUnusedImports() { + for _, imp := range c.unusedImports { + if !c.errRecoverable(NewError(CompileErr, imp.Location, "%s unused", imp.String())) { + break + } + } + + c.unusedImports = nil } func (c *Compiler) checkKeywordOverrides() { for _, name := range c.sorted { if c.strict || c.moduleIsRegoV1Compatible(c.Modules[name]) { - if !c.err(checkRootDocumentOverrides(c.Modules[name])...) { + if !c.errRecoverable(checkRootDocumentOverrides(c.Modules[name])...) { continue } } @@ -2208,15 +2290,12 @@ func (c *Compiler) moduleIsRegoV1Compatible(mod *Module) bool { // // The reference "c.d.e" would be resolved to "data.a.b.c.d.e". func (c *Compiler) resolveAllRefs() { - rules := c.getExports() + exports := c.getExports() + c.unusedImports = nil for _, name := range c.sorted { mod := c.Modules[name] - var ruleExports []Ref - if x, ok := rules.Get(mod.Package.Path); ok { - ruleExports = x - } - + ruleExports, _ := exports.Get(mod.Package.Path) globals := getGlobals(mod.Package, ruleExports, mod.Imports) WalkRules(mod, func(rule *Rule) bool { @@ -2227,7 +2306,7 @@ func (c *Compiler) resolveAllRefs() { return false }) - if c.strict { // check for unused imports + if c.strict { // collect unused imports, reported by the CheckUnusedImports stage for _, imp := range mod.Imports { path := imp.Path.Value.(Ref) if FutureRootDocument.Equal(path[0]) || RegoRootDocument.Equal(path[0]) { @@ -2236,9 +2315,7 @@ func (c *Compiler) resolveAllRefs() { for v, u := range globals { if v == imp.Name() && !u.used { - if !c.err(NewError(CompileErr, imp.Location, "%s unused", imp.String())) { - return - } + c.unusedImports = append(c.unusedImports, imp) } } } @@ -2246,7 +2323,6 @@ func (c *Compiler) resolveAllRefs() { } if c.moduleLoader != nil { - parsed, err := c.moduleLoader(c.Modules) if err != nil { c.err(newErrorString(CompileErr, nil, err.Error())) @@ -2265,7 +2341,7 @@ func (c *Compiler) resolveAllRefs() { } } - sort.Strings(c.sorted) + slices.Sort(c.sorted) c.resolveAllRefs() } } @@ -2285,10 +2361,23 @@ func (c *Compiler) initLocalVarGen() { func (c *Compiler) rewriteComprehensionTerms() { f := newEqualityFactory(c.localvargen) for _, name := range c.sorted { + // Transform rebuilds what it walks, so finding nothing is not free. + if !ContainsComprehensions(c.Modules[name]) { + continue + } _, _ = rewriteComprehensionTerms(f, c.Modules[name]) // ignore error } } +func containsWith(x any) bool { + found := false + WalkWiths(x, func(*With) bool { + found = true + return found + }) + return found +} + func (c *Compiler) rewriteExprTerms() { for _, name := range c.sorted { WalkRules(c.Modules[name], func(rule *Rule) bool { @@ -2360,6 +2449,8 @@ func (c *Compiler) rewriteRuleHeadRefs() { } } +// checkVoidCalls errors are not recoverable: the type checker has no type for an +// expression using a void result, and reports a bogus "undefined function" for it. func (c *Compiler) checkVoidCalls() { for _, name := range c.sorted { c.err(checkVoidCalls(c.TypeEnv, c.Modules[name])...) @@ -2450,6 +2541,12 @@ func (c *Compiler) rewriteTemplateStrings() { for _, name := range c.sorted { mod := c.Modules[name] WalkRules(mod, func(r *Rule) bool { + // The output vars computed below are read only to resolve a template + // string, and most rules have none to resolve. + if !containsTemplateString(r) { + return false + } + tsr = tsr.Clear() safe := r.Head.Args.Vars() @@ -2479,6 +2576,17 @@ func (c *Compiler) rewriteTemplateStrings() { } } +func containsTemplateString(x any) bool { + found := false + WalkTerms(x, func(t *Term) bool { + if _, ok := t.Value.(*TemplateString); ok { + found = true + } + return found + }) + return found +} + func rewriteTemplateStrings(tsr *templateStringRewriter, globals VarSet, x any) (bool, VarSet, Errors) { var errs Errors var modified bool @@ -2492,7 +2600,7 @@ func rewriteTemplateStrings(tsr *templateStringRewriter, globals VarSet, x any) safe = globals.Copy() } - vis := &GenericVisitor{func(x any) bool { + vis := NewGenericVisitor(func(x any) bool { var modrec bool var errsrec Errors switch x := x.(type) { @@ -2549,7 +2657,7 @@ func rewriteTemplateStrings(tsr *templateStringRewriter, globals VarSet, x any) } errs = append(errs, errsrec...) return false - }} + }) vis.Walk(x) return modified, safe, errs @@ -2675,11 +2783,12 @@ func (c *Compiler) rewritePrintCalls() { } bodyVis := func(b Body) bool { - modrec, errs := rewritePrintCalls(c.localvargen, c.GetArity, vis.vars, b) + modrec, errs := rewritePrintCalls(c.localvargen, c.GetArity, vis.vars, c.RewrittenVars, b) if modrec { modified = true } - if !c.err(errs...) { + if len(errs) > 0 { + c.err(errs...) return true } return false @@ -2704,7 +2813,7 @@ func checkVoidCalls(env *TypeEnv, x any) Errors { var errs Errors WalkTerms(x, func(x *Term) bool { if call, ok := x.Value.(Call); ok { - if tpe, ok := env.Get(call[0]).(*types.Function); ok && tpe.Result() == nil { + if tpe, ok := env.GetByValue(call[0].Value).(*types.Function); ok && tpe.Result() == nil { errs = append(errs, NewError(TypeErr, x.Loc(), "%v used as value", call)) } } @@ -2725,15 +2834,15 @@ func checkVoidCalls(env *TypeEnv, x any) Errors { // The expression would be rewritten to: // // print({__local0__ | __local0__ = "the value of x is:"}, {__local1__ | __local1__ = input.x}) -func rewritePrintCalls(gen *localVarGenerator, getArity func(Ref) int, globals VarSet, body Body) (bool, Errors) { +func rewritePrintCalls(gen *localVarGenerator, getArity func(Ref) int, globals VarSet, rewritten map[Var]Var, body Body) (bool, Errors) { var errs Errors var modified bool - // Visit comprehension bodies recursively to ensure print statements inside - // those bodies only close over variables that are safe. + // Visit nested bodies recursively to ensure print statements inside those + // bodies only close over variables that are safe. for i := range body { - if ContainsClosures(body[i]) { + if containsNestedBody(body[i]) { safe := outputVarsForBody(body[:i], getArity, globals, nil) safe.Update(globals) WalkClosures(body[i], func(x any) bool { @@ -2741,28 +2850,28 @@ func rewritePrintCalls(gen *localVarGenerator, getArity func(Ref) int, globals V var errsrec Errors switch x := x.(type) { case *SetComprehension: - modrec, errsrec = rewritePrintCalls(gen, getArity, safe, x.Body) + modrec, errsrec = rewritePrintCalls(gen, getArity, safe, rewritten, x.Body) case *ArrayComprehension: - modrec, errsrec = rewritePrintCalls(gen, getArity, safe, x.Body) + modrec, errsrec = rewritePrintCalls(gen, getArity, safe, rewritten, x.Body) case *ObjectComprehension: - modrec, errsrec = rewritePrintCalls(gen, getArity, safe, x.Body) + modrec, errsrec = rewritePrintCalls(gen, getArity, safe, rewritten, x.Body) case *Every: safe.Update(x.KeyValueVars()) - modrec, errsrec = rewritePrintCalls(gen, getArity, safe, x.Body) + modrec, errsrec = rewritePrintCalls(gen, getArity, safe, rewritten, x.Body) case *Not: - modrec, errsrec = rewritePrintCalls(gen, getArity, safe, x.Body) + modrec, errsrec = rewritePrintCalls(gen, getArity, safe, rewritten, x.Body) case *LogicalAnd: var modR bool var errsR Errors - modrec, errsrec = rewritePrintCalls(gen, getArity, safe, x.Lhs) - modR, errsR = rewritePrintCalls(gen, getArity, safe, x.Rhs) + modrec, errsrec = rewritePrintCalls(gen, getArity, safe, rewritten, x.Lhs) + modR, errsR = rewritePrintCalls(gen, getArity, safe, rewritten, x.Rhs) modrec = modrec || modR errsrec = append(errsrec, errsR...) case *LogicalOr: var modR bool var errsR Errors - modrec, errsrec = rewritePrintCalls(gen, getArity, safe, x.Lhs) - modR, errsR = rewritePrintCalls(gen, getArity, safe, x.Rhs) + modrec, errsrec = rewritePrintCalls(gen, getArity, safe, rewritten, x.Lhs) + modR, errsR = rewritePrintCalls(gen, getArity, safe, rewritten, x.Rhs) modrec = modrec || modR errsrec = append(errsrec, errsR...) } @@ -2814,6 +2923,9 @@ func rewritePrintCalls(gen *localVarGenerator, getArity func(Ref) int, globals V if vars.DiffCount(safe) > 0 { unsafe := vars.Diff(safe) for _, v := range unsafe.Sorted() { + if w, ok := rewritten[v]; ok { + v = w + } errs = append(errs, NewError(CompileErr, args[j].Loc(), "var %v is undeclared", v)) } } @@ -2840,9 +2952,21 @@ func rewritePrintCalls(gen *localVarGenerator, getArity func(Ref) int, globals V return modified, nil } +// containsNestedBody returns true if x contains any node that carries a nested +// body which rewritePrintCalls needs to descend into. This is a superset of +// ContainsClosures, which ignores not/and/or expressions. +func containsNestedBody(x any) bool { + found := false + WalkClosures(x, func(any) bool { + found = true + return found + }) + return found +} + func erasePrintCalls(node any) bool { var modified bool - NewGenericVisitor(func(x any) bool { + vis := NewGenericVisitor(func(x any) bool { var modrec bool switch x := x.(type) { case *Rule: @@ -2872,7 +2996,8 @@ func erasePrintCalls(node any) bool { modified = true } return false - }).Walk(node) + }) + vis.Walk(node) return modified } @@ -2916,10 +3041,8 @@ func containsPrintCall(x any) bool { return found } -var printRef = Print.Ref() - func isPrintCall(x *Expr) bool { - return x.IsCall() && x.Operator().Equal(printRef) + return x.IsCall() && x.Operator().Equal(Interned.Refs.Print) } // rewriteRefsInHead will rewrite rules so that the head does not contain any @@ -3093,16 +3216,15 @@ func (c *Compiler) rewriteRegoMetadataCalls() { var metadataRuleVar Var if ruleCalled { // Create and inject metadata for rule - var metadataRuleTerm *Term a := getPrimaryRuleAnnotations(c.annotationSet, rule) if a != nil { - annotObj, err := a.toObject() + annotObj, err := a.toTerm() if err != nil { return !c.err(err) } - metadataRuleTerm = NewTerm(*annotObj) + metadataRuleTerm = annotObj } else { // If rule has no annotations, assign an empty object metadataRuleTerm = ObjectTerm() @@ -3131,17 +3253,14 @@ func (c *Compiler) rewriteRegoMetadataCalls() { func getPrimaryRuleAnnotations(as *AnnotationSet, rule *Rule) *Annotations { annots := as.GetRuleScope(rule) - if len(annots) == 0 { return nil } - // Sort by annotation location; chain must start with annotations declared closest to rule, then going outward - slices.SortStableFunc(annots, func(a, b *Annotations) int { - return -a.Location.Compare(b.Location) + // chain must start with annotations declared closest to rule, then going outward + return slices.MinFunc(annots, func(a, b *Annotations) int { + return a.Location.Compare(b.Location) }) - - return annots[0] } func rewriteRegoMetadataCalls(metadataChainVar *Var, metadataRuleVar *Var, body Body, rewrittenVars *map[Var]Var) Errors { @@ -3200,30 +3319,26 @@ func rewriteRegoMetadataCalls(metadataChainVar *Var, metadataRuleVar *Var, body return errs } -var regoMetadataChainRef = RegoMetadataChain.Ref() -var regoMetadataRuleRef = RegoMetadataRule.Ref() - func isRegoMetadataChainCall(x *Expr) bool { - return x.IsCall() && x.Operator().Equal(regoMetadataChainRef) + return x.IsCall() && Interned.Refs.RegoMetadataChain.Equal(x.Operator()) } func isRegoMetadataRuleCall(x *Expr) bool { - return x.IsCall() && x.Operator().Equal(regoMetadataRuleRef) + return x.IsCall() && Interned.Refs.RegoMetadataRule.Equal(x.Operator()) } func createMetadataChain(chain []*AnnotationsRef) (*Term, *Error) { - metaArray := NewArray() for _, link := range chain { // Dropping leading 'data' element of path p := link.Path[1:].toArray() obj := NewObject(Item(InternedTerm("path"), NewTerm(p))) if link.Annotations != nil { - annotObj, err := link.Annotations.toObject() + annotObj, err := link.Annotations.toTerm() if err != nil { return nil, err } - obj.Insert(InternedTerm("annotations"), NewTerm(*annotObj)) + obj.Insert(InternedTerm("annotations"), annotObj) } metaArray = metaArray.Append(NewTerm(obj)) } @@ -3234,7 +3349,7 @@ func createMetadataChain(chain []*AnnotationsRef) (*Term, *Error) { func (c *Compiler) rewriteLocalVars() { var assignment bool - args := NewVarVisitor() + args := varVisitorPool.Get() argsStack := newLocalDeclaredVars() for _, name := range c.sorted { @@ -3260,6 +3375,9 @@ func (c *Compiler) rewriteLocalVars() { if !c.err(errs...) { return true } + if !c.errRecoverable(stack.unused...) { + return true + } if stack.assignment { assignment = true } @@ -3275,7 +3393,13 @@ func (c *Compiler) rewriteLocalVars() { // Report an error for each unused function argument for arg := range unusedArgs { if !arg.IsWildcard() { - if !c.err(NewError(CompileErr, rule.Head.Location, "unused argument %v. (hint: use _ (wildcard variable) instead)", arg)) { + err := NewError( + CompileErr, + rule.Head.Location, + "unused argument %v. (hint: use _ (wildcard variable) instead)", + arg, + ) + if !c.errRecoverable(err) { return true } } @@ -3289,6 +3413,8 @@ func (c *Compiler) rewriteLocalVars() { if assignment { c.Required.addBuiltinSorted(Assign) } + + varVisitorPool.Put(args) } func (c *Compiler) rewriteLocalVarsInRule(rule *Rule, unusedArgs VarSet, argsStack *localDeclaredVars, gen *localVarGenerator) (*localDeclaredVars, Errors) { @@ -3313,11 +3439,14 @@ func (c *Compiler) rewriteLocalVarsInRule(rule *Rule, unusedArgs VarSet, argsSta strict: c.strict, } - NewGenericVisitor(nestedXform.Visit).Walk(rule.Head) + nxfVis := NewGenericVisitor(nestedXform.Visit) + nxfVis.Walk(rule.Head) c.err(nestedXform.errs...) // NB(sr): This is a bit bogus -- Why not return them? + c.errRecoverable(nestedXform.unused...) // Rewrite assignments in body. - vis := NewVarVisitor() + vis := varVisitorPool.Get() + defer varVisitorPool.Put(vis) for _, t := range rule.Head.Ref()[1:] { if !IsScalar(t.Value) { @@ -3354,31 +3483,32 @@ func (c *Compiler) rewriteLocalVarsInRule(rule *Rule, unusedArgs VarSet, argsSta // references (stay unsafe-var errors), call operators (SkipRefCallHead), // and `with` targets/values (possible function mocks). if len(c.builtins) > 0 { - bodyVis := NewVarVisitor().WithParams(VarVisitorParams{ + bodyVis := varVisitorPool.Get().WithParams(VarVisitorParams{ SkipRefCallHead: true, SkipClosures: true, }) + defer varVisitorPool.Put(bodyVis) + bodyVis.Walk(rule.Body) bodyVars := bodyVis.Vars() - declaredInBody := declaredVars(rule.Body) + declaredInBody := declaredBodyVars(rule.Body) - withVars := NewVarSet() - NewGenericVisitor(func(x any) bool { - if w, ok := x.(*With); ok { - WalkVars(w, func(v Var) bool { - withVars.Add(v) - return false - }) - } + withVis := varVisitorPool.Get().WithParams(VarVisitorParams{SkipRefCallHead: true}) + defer varVisitorPool.Put(withVis) + + f := func(w *With) bool { + withVis.Walk(w.Target.Value) + withVis.Walk(w.Value.Value) return false - }).Walk(rule) + } + WalkWiths(rule, f) for _, v := range bodyVars.Sorted() { if _, ok := c.builtins[v.String()]; !ok { continue } - if declaredInBody.Contains(v) || withVars.Contains(v) { + if declaredInBody.Contains(v) || withVis.Vars().Contains(v) { continue } if _, ok := stack.Declared(v); ok { @@ -3447,35 +3577,42 @@ func headMayHaveVars(head *Head) bool { type rewriteNestedHeadVarLocalTransform struct { gen *localVarGenerator errs Errors + unused Errors // strict-mode "unused var" diagnostics, see localDeclaredVars.unused RewrittenVars map[Var]Var strict bool } func (xform *rewriteNestedHeadVarLocalTransform) Visit(x any) bool { if term, ok := x.(*Term); ok { + if IsScalar(term.Value) { + return false + } + stop := false stack := newLocalDeclaredVars() switch x := term.Value.(type) { case *object: - vis := NewGenericVisitor(xform.Visit) - cpy, _ := x.Map(func(k, v *Term) (*Term, *Term, error) { - kcpy := k.Copy() - vis.Walk(kcpy) - vcpy := v.Copy() - vis.Walk(vcpy) - return kcpy, vcpy, nil - }) - term.Value = cpy + if !x.IsGround() { + vis := NewGenericVisitor(xform.Visit) + term.Value, _ = x.Map(func(k, v *Term) (*Term, *Term, error) { + kcpy := k.Copy() + vis.Walk(kcpy) + vcpy := v.Copy() + vis.Walk(vcpy) + return kcpy, vcpy, nil + }) + } stop = true case *set: - vis := NewGenericVisitor(xform.Visit) - cpy, _ := x.Map(func(v *Term) (*Term, error) { - vcpy := v.Copy() - vis.Walk(vcpy) - return vcpy, nil - }) - term.Value = cpy + if !x.IsGround() { + vis := NewGenericVisitor(xform.Visit) + term.Value, _ = x.Map(func(v *Term) (*Term, error) { + vcpy := v.Copy() + vis.Walk(vcpy) + return vcpy, nil + }) + } stop = true case *ArrayComprehension: xform.errs = rewriteDeclaredVarsInArrayComprehension(xform.gen, stack, x, xform.errs, xform.strict) @@ -3492,6 +3629,7 @@ func (xform *rewriteNestedHeadVarLocalTransform) Visit(x any) bool { } maps.Copy(xform.RewrittenVars, stack.rewritten) + xform.unused = append(xform.unused, stack.unused...) return stop } @@ -3513,14 +3651,9 @@ func (xform rewriteHeadVarLocalTransform) Transform(x any) (any, error) { } func (c *Compiler) rewriteLocalArgVars(gen *localVarGenerator, stack *localDeclaredVars, rule *Rule) { - vis := &ruleArgLocalRewriter{ - stack: stack, - gen: gen, - } - - for i := range rule.Head.Args { - Walk(vis, rule.Head.Args[i]) - } + vis := &ruleArgLocalRewriter{stack: stack, gen: gen} + vis.gv = &GenericVisitor{f: vis.visit} + vis.gv.Walk(rule.Head.Args) c.err(vis.errs...) } @@ -3528,16 +3661,15 @@ func (c *Compiler) rewriteLocalArgVars(gen *localVarGenerator, stack *localDecla type ruleArgLocalRewriter struct { stack *localDeclaredVars gen *localVarGenerator + gv *GenericVisitor errs []*Error } -func (vis *ruleArgLocalRewriter) Visit(x any) Visitor { - - t, ok := x.(*Term) +func (vis *ruleArgLocalRewriter) visit(a any) bool { + t, ok := a.(*Term) if !ok { - return vis + return false } - switch v := t.Value.(type) { case Var: gv, ok := vis.stack.Declared(v) @@ -3548,37 +3680,39 @@ func (vis *ruleArgLocalRewriter) Visit(x any) Visitor { vis.stack.Insert(v, gv, argVar) } t.Value = gv - return nil + return true case *object: if cpy, err := v.Map(func(k, v *Term) (*Term, *Term, error) { vcpy := v.Copy() - Walk(vis, vcpy) + vis.gv.Walk(vcpy) return k, vcpy, nil }); err != nil { vis.errs = append(vis.errs, newErrorString(CompileErr, t.Location, err.Error())) } else { t.Value = cpy } - return nil + return true case Null, Boolean, Number, String, *ArrayComprehension, *SetComprehension, *ObjectComprehension, Set, *TemplateString: // Scalars are no-ops. Comprehensions and template-strings are handled above. Sets must not // contain variables. - return nil + return true case Call: vis.errs = append(vis.errs, NewError(CompileErr, t.Location, "rule arguments cannot contain calls")) - return nil - default: - // Recurse on refs and arrays. Any embedded - // variables can be rewritten. - return vis + return true } + // Recurse on refs and arrays. Any embedded variables can be rewritten. + return false } func (c *Compiler) rewriteWithModifiers() { f := newEqualityFactory(c.localvargen) for _, name := range c.sorted { mod := c.Modules[name] - t := NewGenericTransformer(func(x any) (any, error) { + // As above: a module with no with modifier would be rebuilt unchanged. + if !containsWith(mod) { + continue + } + t := GenericTransformer{f: func(x any) (any, error) { body, ok := x.(Body) if !ok { return x, nil @@ -3589,7 +3723,7 @@ func (c *Compiler) rewriteWithModifiers() { } return body, nil - }) + }} _, _ = Transform(t, mod) // ignore error } } @@ -3625,6 +3759,7 @@ type queryCompiler struct { qctx *QueryContext typeEnv *TypeEnv rewritten map[Var]Var + refSubjects map[Var]Value after map[string][]QueryCompilerStageDefinition unsafeBuiltins map[string]struct{} comprehensionIndices map[*Term]*ComprehensionIndex @@ -3724,7 +3859,7 @@ func (qc *queryCompiler) Compile(query Body) (Body, error) { {StageRewriteWithValues, "query_compile_stage_rewrite_with_values", qc.rewriteWithModifiers}, {StageCheckUndefinedFuncs, "query_compile_stage_check_undefined_funcs", qc.checkUndefinedFuncs}, {StageCheckSafety, "query_compile_stage_check_safety", qc.checkSafety}, - {StageRewriteDynamicTerms, "query_compile_stage_rewrite_dynamic_terms", qc.rewriteDynamicTerms}, + {StageRewriteDynamicTerms, "query_compile_stage_rewrite_dynamic_terms", qc.rewriteDynamicTerms}, // see recordSubjectNoCopy {StageCheckTypes, "query_compile_stage_check_types", qc.checkTypes}, {StageCheckUnsafeBuiltins, "query_compile_stage_check_unsafe_builtins", qc.checkUnsafeBuiltins}, {StageCheckDeprecatedBuiltins, "query_compile_stage_check_deprecated_builtins", qc.checkDeprecatedBuiltins}, @@ -3757,8 +3892,7 @@ func (qc *queryCompiler) TypeEnv() *TypeEnv { } func (qc *queryCompiler) applyErrorLimit(err error) error { - var errs Errors - if errors.As(err, &errs) { + if errs, ok := errors.AsType[Errors](err); ok { if qc.compiler.maxErrs > 0 && len(errs) > qc.compiler.maxErrs { err = append(errs[:qc.compiler.maxErrs], errLimitReached) } @@ -3776,7 +3910,6 @@ func (qc *queryCompiler) checkKeywordOverrides(_ *QueryContext, body Body) (Body } func (qc *queryCompiler) resolveRefs(qctx *QueryContext, body Body) (Body, error) { - var globals map[Var]*usedRef if qctx != nil { @@ -3784,21 +3917,16 @@ func (qc *queryCompiler) resolveRefs(qctx *QueryContext, body Body) (Body, error // Query compiler ought to generate a package if one was not provided and one or more imports were provided. // The generated package name could even be an empty string to avoid conflicts (it doesn't have to be valid syntactically) if pkg == nil && len(qctx.Imports) > 0 { - pkg = &Package{Path: RefTerm(VarTerm("")).Value.(Ref)} + pkg = emptyPackage } if pkg != nil { - var ruleExports []Ref - rules := qc.compiler.getExports() - if exist, ok := rules.Get(pkg.Path); ok { - ruleExports = exist - } - + ruleExports := qc.compiler.getExport(pkg.Path) globals = getGlobals(qctx.Package, ruleExports, qctx.Imports) qctx.Imports = nil } } - ignore := &declaredVarStack{declaredVars(body)} + ignore := &declaredVarStack{declaredBodyVars(body)} return resolveRefsInBody(globals, ignore, body), nil } @@ -3813,21 +3941,26 @@ func (*queryCompiler) rewriteComprehensionTerms(_ *QueryContext, body Body) (Bod return node.(Body), nil } -func (*queryCompiler) rewriteDynamicTerms(_ *QueryContext, body Body) (Body, error) { +func (qc *queryCompiler) rewriteDynamicTerms(_ *QueryContext, body Body) (Body, error) { gen := newLocalVarGenerator("q", body) f := newEqualityFactory(gen) - return rewriteDynamics(f, body), nil + body = rewriteDynamics(f, body) + qc.refSubjects = mergeRefSubjects(qc.refSubjects, gen.subjects) + return body, nil } -func (*queryCompiler) rewriteExprTerms(_ *QueryContext, body Body) (Body, error) { +func (qc *queryCompiler) rewriteExprTerms(_ *QueryContext, body Body) (Body, error) { gen := newLocalVarGenerator("q", body) - return rewriteExprTermsInBody(gen, body), nil + body = rewriteExprTermsInBody(gen, body) + qc.refSubjects = gen.subjects + return body, nil } func (qc *queryCompiler) rewriteLocalVars(_ *QueryContext, body Body) (Body, error) { gen := newLocalVarGenerator("q", body) stack := newLocalDeclaredVars() body, _, err := rewriteLocalVars(gen, stack, nil, body, qc.compiler.strict) + err = append(err, stack.unused...) if len(err) != 0 { return nil, err } @@ -3855,7 +3988,7 @@ func (qc *queryCompiler) rewritePrintCalls(_ *QueryContext, body Body) (Body, er return cpy, nil } gen := newLocalVarGenerator("q", body) - if _, errs := rewritePrintCalls(gen, qc.compiler.GetArity, ReservedVars, body); len(errs) > 0 { + if _, errs := rewritePrintCalls(gen, qc.compiler.GetArity, ReservedVars, qc.RewrittenVars(), body); len(errs) > 0 { return nil, errs } return body, nil @@ -3878,7 +4011,7 @@ func (qc *queryCompiler) checkUndefinedFuncs(_ *QueryContext, body Body) (Body, func (qc *queryCompiler) checkSafety(_ *QueryContext, body Body) (Body, error) { safe := ReservedVars.Copy() reordered, unsafe := reorderBodyForSafety(qc.compiler.builtins, qc.compiler.GetArity, safe, body) - if errs := safetyErrorSlice(unsafe, qc.RewrittenVars()); len(errs) > 0 { + if errs := safetyErrorSlice(unsafe, qc.RewrittenVars(), ""); len(errs) > 0 { return nil, errs } return reordered, nil @@ -3889,7 +4022,8 @@ func (qc *queryCompiler) checkTypes(_ *QueryContext, body Body) (Body, error) { checker := newTypeChecker(). WithSchemaSet(qc.compiler.schemaSet). WithInputType(qc.compiler.inputType). - WithVarRewriter(rewriteVarsInRef(qc.rewritten, qc.compiler.RewrittenVars)) + WithDependentsResolver(qc.compiler.dependentRuleRefs). + WithVarRewriter(rewriteRefErrVars(qc.refSubjects, qc.rewritten, qc.compiler.RewrittenVars)) qc.typeEnv, errs = checker.CheckBody(qc.compiler.TypeEnv, body) if len(errs) > 0 { return nil, errs @@ -3956,16 +4090,21 @@ func (ci *ComprehensionIndex) String() string { return fmt.Sprintf("", NewArray(ci.Keys...)) } -func buildComprehensionIndices(dbg debug.Debug, arity func(Ref) int, candidates VarSet, rwVars map[Var]Var, node Body, result map[*Term]*ComprehensionIndex) uint64 { - var n uint64 +func buildComprehensionIndices( + dbg debug.Debug, + arity func(Ref) int, + candidates VarSet, + rwVars map[Var]Var, + node Body, + result map[*Term]*ComprehensionIndex, +) (n uint64) { cpy := candidates.Copy() vis := varVisitorPool.Get() - defer varVisitorPool.Put(vis) WalkBodies(node, func(b Body) bool { for _, expr := range b { - index := getComprehensionIndex(dbg, arity, cpy, rwVars, expr) + index := getComprehensionIndex(dbg, arity, cpy, rwVars, expr, vis) if index != nil { result[index.Term] = index n++ @@ -3973,15 +4112,24 @@ func buildComprehensionIndices(dbg debug.Debug, arity func(Ref) int, candidates // Any variables appearing in the expressions leading up to the comprehension // are fair-game to be used as index keys. vis = vis.Clear().WithParams(VarVisitorParams{SkipClosures: true, SkipRefCallHead: true}) + old := vis.vars + vis.vars = cpy vis.Walk(expr) - cpy.Update(vis.Vars()) + vis.vars = old } return false }) return n } -func getComprehensionIndex(dbg debug.Debug, arity func(Ref) int, candidates VarSet, rwVars map[Var]Var, expr *Expr) *ComprehensionIndex { +func getComprehensionIndex( + dbg debug.Debug, + arity func(Ref) int, + candidates VarSet, + rwVars map[Var]Var, + expr *Expr, + vis *VarVisitor, +) *ComprehensionIndex { // Ignore everything except = expressions. Extract // the comprehension term from the expression. if !expr.IsEquality() || expr.Negated || len(expr.With) > 0 { @@ -4034,10 +4182,14 @@ func getComprehensionIndex(dbg debug.Debug, arity func(Ref) int, candidates VarS body = x.Body } - outputs := outputVarsForBody(body, arity, ReservedVars, nil) - unsafe := body.Vars(SafetyCheckVisitorParams).Diff(outputs).Diff(ReservedVars) + vis = vis.Clear().WithParams(SafetyCheckVisitorParams) + outputs := outputVarsForBody(body, arity, ReservedVars, vis) - if len(unsafe) > 0 { + vis.Clear().WithParams(SafetyCheckVisitorParams).Walk(body) + unsafe := vis.Vars().Diff(outputs) + + if unsafe.DiffCount(ReservedVars) > 0 { + unsafe = unsafe.Diff(ReservedVars) dbg.Printf("%s: comprehension index: unsafe vars: %v", expr.Location, unsafe) return nil } @@ -4072,12 +4224,7 @@ func getComprehensionIndex(dbg debug.Debug, arity func(Ref) int, candidates VarS return nil } - result := make([]*Term, 0, len(indexVars)) - for v := range indexVars { - result = append(result, NewTerm(v)) - } - slices.SortFunc(result, TermValueCompare) - + result := util.SortedFunc(util.MapKeys(indexVars, ToTerm), TermValueCompare) debugRes := make([]*Term, len(result)) for i, r := range result { if o, ok := rwVars[r.Value.(Var)]; ok { @@ -4096,15 +4243,6 @@ type comprehensionIndexRegressionCheckVisitor struct { worse bool } -// TODO(tsandall): Improve this so that users can either supply this list explicitly -// or the information is maintained on the built-in function declaration. What we really -// need to know is whether the built-in function allows callers to push down output -// values or not. It's unlikely that anything outside of OPA does this today so this -// solution is fine for now. -var comprehensionIndexBlacklist = map[string]int{ - WalkBuiltin.Name: len(WalkBuiltin.Decl.FuncArgs().Args), -} - func newComprehensionIndexRegressionCheckVisitor(candidates VarSet) *comprehensionIndexRegressionCheckVisitor { return &comprehensionIndexRegressionCheckVisitor{ candidates: candidates, @@ -4121,7 +4259,7 @@ func (vis *comprehensionIndexRegressionCheckVisitor) visit(x any) bool { switch x := x.(type) { case *Expr: operands := x.Operands() - if pos := comprehensionIndexBlacklist[x.Operator().String()]; pos > 0 && pos < len(operands) { + if pos := comprehensionIndexDenylist[x.Operator().String()]; pos > 0 && pos < len(operands) { vis.assertEmptyIntersection(operands[pos].Vars()) } case Ref: @@ -4278,13 +4416,13 @@ func (n *ModuleTreeNode) DepthFirst(f func(*ModuleTreeNode) bool) { // TreeNode represents a node in the rule tree. The rule tree is keyed by // rule path. type TreeNode struct { + Values []*Rule + Sorted []Value Key Value External *ExternalIndex - Values []*Rule - Children map[Value]*TreeNode - Sorted []Value - Hide bool Index RuleIndex + Children map[Value]*TreeNode + Hide bool } func (n *TreeNode) String() string { @@ -4450,18 +4588,13 @@ type legacyExternalResolver struct { inner ValueResolver } -func (r legacyExternalResolver) Resolve(ref Ref) (Value, error) { +func (r legacyExternalResolver) Resolve(ref Ref) (v Value, err error) { if !ref.HasPrefix(InputRootRef) { - return nil, UnknownValueErr{} + err = UnknownValueErr{} + } else if v, err = r.inner.Resolve(ref); err == nil && v == nil { + err = UnknownValueErr{} } - v, err := r.inner.Resolve(ref) - if err != nil { - return nil, err - } - if v == nil { - return nil, UnknownValueErr{} - } - return v, nil + return v, err } // unknownResolver treats every reference as unknown. It is used as a safe @@ -4520,12 +4653,21 @@ func (n *TreeNode) find(ref Ref) (*TreeNode, Ref) { // DepthFirst performs a depth-first traversal of the rule tree rooted at n. If // f returns true, traversal will not continue to the children of n. +// DepthFirst calls f on n and then, in key order, on everything below it. The +// order is the map's own otherwise, and callers building an index from what they +// walk hand the order on to their results. func (n *TreeNode) DepthFirst(f func(*TreeNode) bool) { if f(n) { return } - for _, node := range n.Children { - node.DepthFirst(f) + if len(n.Children) < 2 { + for _, node := range n.Children { // no order to choose, and no slice to build + node.DepthFirst(f) + } + return + } + for _, key := range util.KeysSortedFunc(n.Children, Value.Compare) { + n.Children[key].DepthFirst(f) } } @@ -4660,8 +4802,7 @@ func (n *TreeNode) Copy() *TreeNode { } if n.Sorted != nil { - result.Sorted = make([]Value, len(n.Sorted)) - copy(result.Sorted, n.Sorted) + result.Sorted = slices.Clone(n.Sorted) } return result @@ -4768,11 +4909,9 @@ func (g *Graph) Sort() (sorted []util.T, ok bool) { temp: map[util.T]struct{}{}, } - nodesList := make([]util.T, 0, len(g.nodes)) - for node := range g.nodes { - nodesList = append(nodesList, node) - } + nodesList := util.Keys(g.nodes) sortGraphNodes(nodesList) + for _, node := range nodesList { if !sorter.Visit(node) { return nil, false @@ -4839,33 +4978,33 @@ func sortGraphNodes(nodes []util.T) { }) } -func (sort *graphSort) Marked(node util.T) bool { - _, marked := sort.marked[node] +func (gs *graphSort) Marked(node util.T) bool { + _, marked := gs.marked[node] return marked } -func (sort *graphSort) Visit(node util.T) (ok bool) { - if _, ok := sort.temp[node]; ok { +func (gs *graphSort) Visit(node util.T) (ok bool) { + if _, ok := gs.temp[node]; ok { return false } - if sort.Marked(node) { + if gs.Marked(node) { return true } - sort.temp[node] = struct{}{} - deps := sort.deps(node) + gs.temp[node] = struct{}{} + deps := gs.deps(node) depList := make([]util.T, 0, len(deps)) for other := range deps { depList = append(depList, other) } sortGraphNodes(depList) for _, other := range depList { - if !sort.Visit(other) { + if !gs.Visit(other) { return false } } - sort.marked[node] = struct{}{} - delete(sort.temp, node) - sort.sorted = append(sort.sorted, node) + gs.marked[node] = struct{}{} + delete(gs.temp, node) + gs.sorted = append(gs.sorted, node) return true } @@ -4970,17 +5109,19 @@ func (vs unsafeVars) Slice() (result []unsafePair) { // If the body cannot be reordered to ensure safety, the second return value // contains a mapping of expressions to unsafe variables in those expressions. func reorderBodyForSafety(builtins map[string]*Builtin, arity func(Ref) int, globals VarSet, body Body) (Body, unsafeVars) { - vis := varVisitorPool.Get().WithParams(SafetyCheckVisitorParamsWithArity(arity)) - vis.WalkBody(body) + params := SafetyCheckVisitorParamsWithArity(arity) + vis := varVisitorPool.Get().WithParams(params) defer varVisitorPool.Put(vis) + vis.WalkBody(body) + bodyVars := vis.Vars().Copy() safe := bodyVars.Intersect(globals) unsafe := make(unsafeVars, len(bodyVars)-len(safe)) for _, e := range body { - vis = vis.Clear().WithParams(SafetyCheckVisitorParamsWithArity(arity)) + vis = vis.Clear().WithParams(params) vis.Walk(e) for v := range vis.Vars() { if _, ok := safe[v]; !ok { @@ -5009,15 +5150,19 @@ func reorderBodyForSafety(builtins map[string]*Builtin, arity func(Ref) int, glo cv := unsVis.Vars().Intersect(bodyVars).Diff(globals) unsVis.Clear() - ob := outputVarsForBody(reordered, arity, safe, vis) + // ob is the expensive part of this loop, and an empty cv makes the + // comparisons below hold whatever it is. + if len(cv) > 0 { + ob := outputVarsForBody(reordered, arity, safe, vis) - if cv.DiffCount(ob) > 0 { - uv := cv.Diff(ob) - if uv.Equal(ovs) { // special case "closure-self" - continue + if cv.DiffCount(ob) > 0 { + uv := cv.Diff(ob) + if uv.Equal(ovs) { // special case "closure-self" + continue + } + // The expression is closing over variables not yet present in reordered body + unsafe.Set(e, uv) } - // The expression is closing over variables not yet present in reordered body - unsafe.Set(e, uv) } for v := range unsafe[e] { @@ -5043,19 +5188,20 @@ func reorderBodyForSafety(builtins map[string]*Builtin, arity func(Ref) int, glo // Recursively visit closures and perform the safety checks on them. // Update the globals at each expression to include the variables that could // be closed over. - g := globals.Copy() xform := newBodySafetyTransformer(builtins, arity) xform.gv = NewGenericVisitor(xform.Visit) + xform.unsafe = unsafe + xform.globals = globals.Copy() + + vis = vis.WithParams(params) + vis.vars = xform.globals + for i, e := range reordered { if i > 0 { - vis = vis.Clear().WithParams(SafetyCheckVisitorParamsWithArity(arity)) vis.Walk(reordered[i-1]) - g.Update(vis.Vars()) } xform.current = e - xform.globals = g - xform.unsafe = unsafe xform.gv.Walk(e) } @@ -5150,7 +5296,7 @@ func unsafeImplicitBodyVars(body Body, arity func(Ref) int) VarSet { } if e.IsEquality() { - for v := range outputVarsForExprEq(e, VarSet{}, VarSet{}) { + for v := range outputVarsForExprEq(e, VarSet{}, nil) { bindings[v] = struct{}{} } continue @@ -5218,22 +5364,24 @@ func (xform *bodySafetyTransformer) Visit(x any) bool { case *Term: switch x := term.Value.(type) { case *object: - cpy, _ := x.Map(func(k, v *Term) (*Term, *Term, error) { - kcpy := k.Copy() - xform.gv.Walk(kcpy) - vcpy := v.Copy() - xform.gv.Walk(vcpy) - return kcpy, vcpy, nil - }) - term.Value = cpy + if !x.IsGround() { + term.Value, _ = x.Map(func(k, v *Term) (*Term, *Term, error) { + kcpy := k.Copy() + xform.gv.Walk(kcpy) + vcpy := v.Copy() + xform.gv.Walk(vcpy) + return kcpy, vcpy, nil + }) + } return true case *set: - cpy, _ := x.Map(func(v *Term) (*Term, error) { - vcpy := v.Copy() - xform.gv.Walk(vcpy) - return vcpy, nil - }) - term.Value = cpy + if !x.IsGround() { + term.Value, _ = x.Map(func(v *Term) (*Term, error) { + vcpy := v.Copy() + xform.gv.Walk(vcpy) + return vcpy, nil + }) + } return true case *ArrayComprehension: xform.reorderArrayComprehensionSafety(x) @@ -5405,6 +5553,13 @@ func outputVarsForExprEq(expr *Expr, safe VarSet, output VarSet) VarSet { return safe } + // Clear the shared buffer before use. Callers of outputVarsForExpr reuse the + // same VarSet across candidate expressions in a single reorderBodyForSafety + // pass. Without this, leftover bindings from an earlier (not-yet-schedulable) + // call expression can leak into Unify() via the safe basis and incorrectly + // mark an equality as grounded. See issue #8302. + clear(output) + output = outputVarsForTerms(expr, safe, output) output.Update(safe) if expr.fromAssignment { @@ -5445,8 +5600,15 @@ func outputVarsForExprCall(expr *Expr, arity int, safe VarSet, terms []*Term, vi vis = ClearOrNewVarVisitor(vis).WithParams(params) vis.WalkArgs(Args(terms[:numInputTerms])) - unsafe := vis.Vars().Diff(output).DiffCount(safe) - if unsafe > 0 { + unsafe := vis.Vars() + for i := range output { + delete(unsafe, i) + } + for i := range safe { + delete(unsafe, i) + } + + if len(unsafe) > 0 { return VarSet{} } @@ -5497,13 +5659,42 @@ func (f *equalityFactory) Generate(other *Term) *Expr { return expr } -// TODO: Move to internal package? -const LocalVarPrefix = "__local" - type localVarGenerator struct { exclude VarSet suffix string next int + + // subjects maps a generated local back to the original term it replaced, + // so type errors can render the original expression (e.g. [1, 2][i] + // instead of __local0__[i]). Populated lazily. + subjects map[Var]Value +} + +// recordSubject records that local stands in for value. The value is copied, as +// stages running between the caller and CheckTypes may rewrite it in place: a +// composite subject recorded in RewriteExprTerms, say [x, input.y][i], has its +// dynamic elements hoisted by the later RewriteDynamicTerms stage, which would +// otherwise turn the recorded value into [__local5__, __local6__]. +func (l *localVarGenerator) recordSubject(local Var, value *Term) { + l.putSubject(local, CopyValue(value.Value)) +} + +// recordSubjectNoCopy records that local stands in for value, aliasing value +// rather than copying it. Only callers in the RewriteDynamicTerms stage may use +// this: only RewriteTestRulesForTracing and CheckRecursion run between that +// stage and CheckTypes, and neither rewrites hoisted terms, so nothing can +// mutate value before the mapping is read. Copying here instead would allocate +// on every hoisted ref of every compile, for a map only read when a type error +// is rendered. +func (l *localVarGenerator) recordSubjectNoCopy(local Var, value *Term) { + l.putSubject(local, value.Value) +} + +func (l *localVarGenerator) putSubject(local Var, value Value) { + if l.subjects == nil { + l.subjects = map[Var]Value{} + } + l.subjects[local] = value } func newLocalVarGeneratorForModuleSet(sorted []string, modules map[string]*Module) *localVarGenerator { @@ -5595,8 +5786,6 @@ type usedRef struct { } func resolveRefsInRule(globals map[Var]*usedRef, rule *Rule) error { - ignore := &declaredVarStack{} - vars := NewVarSet() var vis *GenericVisitor var err error @@ -5622,30 +5811,27 @@ func resolveRefsInRule(globals map[Var]*usedRef, rule *Rule) error { return true case *Term: - if _, ok := x.Value.(Ref); ok { - if RootDocumentRefs.Contains(x) { - // We could support args named input, data, etc. however - // this would require rewriting terms in the head and body. - // Preventing root document shadowing is simpler, and - // arguably, will prevent confusing names from being used. - // NOTE: this check is also performed as part of strict-mode in - // checkRootDocumentOverrides. - err = fmt.Errorf("args must not shadow %v (use a different variable name)", x) - return true - } + if TermValueIs[Ref](x) && RootDocumentRefs.Contains(x) { + // We could support args named input, data, etc. however + // this would require rewriting terms in the head and body. + // Preventing root document shadowing is simpler, and + // arguably, will prevent confusing names from being used. + // NOTE: this check is also performed as part of strict-mode in + // checkRootDocumentOverrides. + err = fmt.Errorf("args must not shadow %v (use a different variable name)", x) + return true } } return false }) - vis.Walk(rule.Head.Args) if err != nil { return err } - ignore.Push(vars) - ignore.Push(declaredVars(rule.Body)) + ignore := &declaredVarStack{} + ignore.Push(vars, declaredBodyVars(rule.Body)) ref := rule.Head.Ref() for i := 1; i < len(ref); i++ { @@ -5690,12 +5876,12 @@ func resolveRefsInExpr(globals map[Var]*usedRef, ignore *declaredVarStack, expr } } case *Every: - locals := NewVarSet() + vis := varVisitorPool.Get() if ts.Key != nil { - locals.Update(ts.Key.Vars()) + vis.Walk(ts.Key) } - locals.Update(ts.Value.Vars()) - ignore.Push(locals) + vis.Walk(ts.Value) + ignore.Push(vis.Vars()) cpy.Terms = &Every{ Key: ts.Key.Copy(), // TODO(sr): do more? Value: ts.Value.Copy(), // TODO(sr): do more? @@ -5703,6 +5889,7 @@ func resolveRefsInExpr(globals map[Var]*usedRef, ignore *declaredVarStack, expr Body: resolveRefsInBody(globals, ignore, ts.Body), } ignore.Pop() + varVisitorPool.Put(vis) case *Not: cpy.Terms = &Not{ Body: resolveRefsInBody(globals, ignore, ts.Body), @@ -5746,9 +5933,8 @@ func resolveRefsInTerm(globals map[Var]*usedRef, ignore *declaredVarStack, term } return term case Ref: - fqn := resolveRef(globals, ignore, v) cpy := *term - cpy.Value = fqn + cpy.Value = resolveRef(globals, ignore, v) return &cpy case *object: cpy := *term @@ -5767,38 +5953,37 @@ func resolveRefsInTerm(globals map[Var]*usedRef, ignore *declaredVarStack, term cpy.Value = Call(resolveRefsInTermSlice(globals, ignore, v)) return &cpy case Set: - s, _ := v.Map(func(e *Term) (*Term, error) { + cpy := *term + cpy.Value, _ = v.Map(func(e *Term) (*Term, error) { return resolveRefsInTerm(globals, ignore, e), nil }) - cpy := *term - cpy.Value = s return &cpy case *ArrayComprehension: - ac := &ArrayComprehension{} - ignore.Push(declaredVars(v.Body)) - ac.Term = resolveRefsInTerm(globals, ignore, v.Term) - ac.Body = resolveRefsInBody(globals, ignore, v.Body) + ignore.Push(declaredBodyVars(v.Body)) cpy := *term - cpy.Value = ac + cpy.Value = &ArrayComprehension{ + Term: resolveRefsInTerm(globals, ignore, v.Term), + Body: resolveRefsInBody(globals, ignore, v.Body), + } ignore.Pop() return &cpy case *ObjectComprehension: - oc := &ObjectComprehension{} - ignore.Push(declaredVars(v.Body)) - oc.Key = resolveRefsInTerm(globals, ignore, v.Key) - oc.Value = resolveRefsInTerm(globals, ignore, v.Value) - oc.Body = resolveRefsInBody(globals, ignore, v.Body) + ignore.Push(declaredBodyVars(v.Body)) cpy := *term - cpy.Value = oc + cpy.Value = &ObjectComprehension{ + Key: resolveRefsInTerm(globals, ignore, v.Key), + Value: resolveRefsInTerm(globals, ignore, v.Value), + Body: resolveRefsInBody(globals, ignore, v.Body), + } ignore.Pop() return &cpy case *SetComprehension: - sc := &SetComprehension{} - ignore.Push(declaredVars(v.Body)) - sc.Term = resolveRefsInTerm(globals, ignore, v.Term) - sc.Body = resolveRefsInBody(globals, ignore, v.Body) + ignore.Push(declaredBodyVars(v.Body)) cpy := *term - cpy.Value = sc + cpy.Value = &SetComprehension{ + Term: resolveRefsInTerm(globals, ignore, v.Term), + Body: resolveRefsInBody(globals, ignore, v.Body), + } ignore.Pop() return &cpy case *TemplateString: @@ -5840,20 +6025,16 @@ func resolveRefsInTermSlice(globals map[Var]*usedRef, ignore *declaredVarStack, type declaredVarStack []VarSet func (s declaredVarStack) Contains(v Var) bool { - for i := len(s) - 1; i >= 0; i-- { - if _, ok := s[i][v]; ok { + for _, v0 := range slices.Backward(s) { + if _, ok := v0[v]; ok { return ok } } return false } -func (s declaredVarStack) Add(v Var) { - s[len(s)-1].Add(v) -} - -func (s *declaredVarStack) Push(vs VarSet) { - *s = append(*s, vs) +func (s *declaredVarStack) Push(vs ...VarSet) { + *s = append(*s, vs...) } func (s *declaredVarStack) Pop() { @@ -5861,16 +6042,31 @@ func (s *declaredVarStack) Pop() { *s = curr[:len(curr)-1] } -func declaredVars(x any) VarSet { +func declaredBodyVars(body Body) VarSet { vars := NewVarSet() + for _, e := range body { + vars = declaredVars(e, vars) + } + return vars +} + +func declaredVars(x any, vars VarSet) VarSet { + if vars == nil { + vars = NewVarSet() + } vis := NewGenericVisitor(func(x any) bool { switch x := x.(type) { case *Expr: if x.IsAssignment() && validEqAssignArgCount(x) { - WalkVars(x.Operand(0), func(v Var) bool { + lhs := x.Operand(0) + if v, ok := lhs.Value.(Var); ok { vars.Add(v) - return false - }) + } else { + WalkVars(lhs, func(v Var) bool { + vars.Add(v) + return false + }) + } } else if decl, ok := x.Terms.(*SomeDecl); ok { for i := range decl.Symbols { switch val := decl.Symbols[i].Value.(type) { @@ -5960,17 +6156,17 @@ func rewriteComprehensionTerms(f *equalityFactory, node any) (any, error) { // partial evaluation cases we do want to rewrite == to = to simplify the // result. func rewriteEquals(x any) (modified bool) { + // Note: can't use Interned.Refs.Equality here as this may be mutated unifyOp := Equality.Ref() - t := NewGenericTransformer(func(x any) (any, error) { + t := GenericTransformer{f: func(x any) (any, error) { if x, ok := x.(*Expr); ok && x.IsCall() { - operator := x.Operator() - if operator.Equal(doubleEq) && len(x.Operands()) == 2 { + if x.Operator().Equal(Interned.Refs.Equal) && len(x.Operands()) == 2 { modified = true x.SetOperator(NewTerm(unifyOp)) } } return x, nil - }) + }} _, _ = Transform(t, x) // ignore error return modified } @@ -5988,7 +6184,8 @@ func rewriteTestEqualities(f *equalityFactory, body Body) Body { result, terms[2] = rewriteDynamicsShallow(expr, f, terms[2], result) case expr.IsEvery(): // We rewrite equalities inside of every-bodies as a fail here will be the cause of the test-rule fail. - // Failures inside other expressions with closures, such as comprehensions, won't cause the test-rule to fail, so we skip those. + // Failures inside other expressions with closures, such as comprehensions, won't cause the test-rule to + // fail, so we skip those. every := expr.Terms.(*Every) every.Body = rewriteTestEqualities(f, every.Body) } @@ -6127,6 +6324,7 @@ func rewriteDynamicsOne(original *Expr, f *equalityFactory, term *Term, result B generated.With = original.With result.Append(generated) connectGeneratedExprs(original, generated) + f.gen.recordSubjectNoCopy(generated.Operand(0).Value.(Var), term) return result, result[len(result)-1].Operand(0) case *Array: for i := range v.Len() { @@ -6144,30 +6342,33 @@ func rewriteDynamicsOne(original *Expr, f *equalityFactory, term *Term, result B }) return result, NewTerm(cpy).SetLocation(term.Location) case Set: - cpy := NewSet() + terms := make([]*Term, 0, v.Len()) for _, term := range v.Slice() { var rw *Term result, rw = rewriteDynamicsOne(original, f, term, result) - cpy.Add(rw) + terms = append(terms, rw) } - return result, NewTerm(cpy).SetLocation(term.Location) + return result, SetTerm(terms...).SetLocation(term.Location) case *ArrayComprehension: var extra *Expr v.Body, extra = rewriteDynamicsComprehensionBody(original, f, v.Body, term) result.Append(extra) connectGeneratedExprs(original, extra) + f.gen.recordSubjectNoCopy(extra.Operand(0).Value.(Var), term) return result, result[len(result)-1].Operand(0) case *SetComprehension: var extra *Expr v.Body, extra = rewriteDynamicsComprehensionBody(original, f, v.Body, term) result.Append(extra) connectGeneratedExprs(original, extra) + f.gen.recordSubjectNoCopy(extra.Operand(0).Value.(Var), term) return result, result[len(result)-1].Operand(0) case *ObjectComprehension: var extra *Expr v.Body, extra = rewriteDynamicsComprehensionBody(original, f, v.Body, term) result.Append(extra) connectGeneratedExprs(original, extra) + f.gen.recordSubjectNoCopy(extra.Operand(0).Value.(Var), term) return result, result[len(result)-1].Operand(0) } return result, term @@ -6378,7 +6579,25 @@ func expandExprTerm(gen *localVarGenerator, term *Term) (support []*Expr, output func expandExprRef(gen *localVarGenerator, v []*Term) (support []*Expr) { // Start by calling a normal expandExprTerm on all terms. - support = expandExprTermSlice(gen, v) + for i := range v { + // A call in a ref, e.g. the opa.runtime() in opa.runtime()[0].foo, is + // hoisted into a generated local by expandExprTerm below. Record the + // call that local stands in for, so type errors on this ref render the + // call rather than the local. The call has to be copied first: + // expandExprTerm hoists nested calls out of its arguments in place. + var subject Value + if call, ok := v[i].Value.(Call); ok { + subject = call.Copy() + } + + var extras []*Expr + extras, v[i] = expandExprTerm(gen, v[i]) + support = append(support, extras...) + + if local, ok := v[i].Value.(Var); ok && subject != nil { + gen.putSubject(local, subject) + } + } // Rewrite references in order to support indirect references. We rewrite // e.g. @@ -6399,6 +6618,7 @@ func expandExprRef(gen *localVarGenerator, v []*Term) (support []*Expr) { assignToLocal := f.Generate(subject) support = append(support, assignToLocal) v[0] = assignToLocal.Operand(0) + gen.recordSubject(v[0].Value.(Var), subject) } return } @@ -6412,15 +6632,6 @@ func expandExprTermArray(gen *localVarGenerator, arr *Array) (support []*Expr) { return } -func expandExprTermSlice(gen *localVarGenerator, v []*Term) (support []*Expr) { - for i := range v { - var extras []*Expr - extras, v[i] = expandExprTerm(gen, v[i]) - support = append(support, extras...) - } - return -} - type localDeclaredVars struct { vars []*declaredVarSet @@ -6432,6 +6643,10 @@ type localDeclaredVars struct { // indicates if an assignment (:= operator) has been seen *ever* assignment bool + + // strict-mode diagnostics for assigned and declared vars that are never used, + // kept apart from the rewrite errors because they're recoverable + unused Errors } type varOccurrence uint8 @@ -6483,13 +6698,12 @@ func (s *localDeclaredVars) Clear() { clear(s.rewritten) s.vars = s.vars[:0] + s.unused = nil if vs != nil { s.vars = append(s.vars, vs.clear()) } - if s.vars[0] == nil { - s.vars[0] = newDeclaredVarSet() - } + s.vars[0] = util.Or(s.vars[0], newDeclaredVarSet) s.assignment = false } @@ -6541,8 +6755,8 @@ func (s localDeclaredVars) Insert(x, y Var, occurrence varOccurrence) { } func (s localDeclaredVars) Declared(x Var) (y Var, ok bool) { - for i := len(s.vars) - 1; i >= 0; i-- { - if y, ok = s.vars[i].vs[x]; ok { + for _, v := range slices.Backward(s.vars) { + if y, ok = v.vs[x]; ok { return } } @@ -6558,8 +6772,8 @@ func (s localDeclaredVars) Occurrence(x Var) varOccurrence { // GlobalOccurrence returns a flag that indicates whether x has occurred in the // global scope. func (s localDeclaredVars) GlobalOccurrence(x Var) (varOccurrence, bool) { - for i := len(s.vars) - 1; i >= 0; i-- { - if occ, ok := s.vars[i].occurrence[x]; ok { + for _, v := range slices.Backward(s.vars) { + if occ, ok := v.occurrence[x]; ok { return occ, true } } @@ -6568,8 +6782,8 @@ func (s localDeclaredVars) GlobalOccurrence(x Var) (varOccurrence, bool) { // Seen marks x as seen by incrementing its counter func (s localDeclaredVars) Seen(x Var) { - for i := len(s.vars) - 1; i >= 0; i-- { - dvs := s.vars[i] + for _, dvs := range slices.Backward(s.vars) { + if c, ok := dvs.count[x]; ok { dvs.count[x] = c + 1 return @@ -6581,8 +6795,8 @@ func (s localDeclaredVars) Seen(x Var) { // Count returns how many times x has been seen func (s localDeclaredVars) Count(x Var) int { - for i := len(s.vars) - 1; i >= 0; i-- { - if c, ok := s.vars[i].count[x]; ok { + for _, v := range slices.Backward(s.vars) { + if c, ok := v.count[x]; ok { return c } } @@ -6619,8 +6833,7 @@ func rewriteDeclaredVarsInBody(g *localVarGenerator, stack *localDeclaredVars, u expr, errs = rewriteSomeDeclStatement(g, stack, body[i], errs, strict) case body[i].IsEvery(): expr, errs = rewriteEveryStatement(g, stack, body[i], errs, strict) - case body[i].IsNot() && body[i].Terms.(*Not).ExplicitBody: - // Only explicit not bodies are allowed to declare vars + case body[i].IsNot(): expr, errs = rewriteNotStatement(g, stack, body[i], errs, strict) case body[i].IsAnd() || body[i].IsOr(): expr, errs = rewriteLogicalStatement(g, stack, body[i], errs, strict) @@ -6639,13 +6852,15 @@ func rewriteDeclaredVarsInBody(g *localVarGenerator, stack *localDeclaredVars, u cpy.Append(NewExpr(BooleanTerm(true))) } - errs = checkUnusedAssignedVars(body, stack, used, errs, strict) - return cpy, checkUnusedDeclaredVars(body, stack, used, cpy, errs) + checkUnusedAssignedVars(body, stack, used, errs, strict) + checkUnusedDeclaredVars(body, stack, used, cpy, errs) + + return cpy, errs } -func checkUnusedAssignedVars(body Body, stack *localDeclaredVars, used VarSet, errs Errors, strict bool) Errors { - if !strict || len(errs) > 0 { - return errs +func checkUnusedAssignedVars(body Body, stack *localDeclaredVars, used VarSet, errs Errors, strict bool) { + if !strict || len(errs) > 0 || len(stack.unused) > 0 { + return } dvs := stack.Peek() @@ -6657,31 +6872,31 @@ func checkUnusedAssignedVars(body Body, stack *localDeclaredVars, used VarSet, e } } if !hasAssignedVars { - return errs + return } - unused := NewVarSet() + var unused VarSet for v, occ := range dvs.occurrence { // A var that was assigned in this scope must have been seen (used) more than once (the time of assignment) in // the same, or nested, scope to be counted as used. - if !v.IsWildcard() && stack.Count(v) <= 1 && occ == assignedVar { + if !v.IsWildcard() && stack.Count(v) <= 1 && occ == assignedVar && !used.Contains(dvs.vs[v]) { + if unused == nil { + unused = NewVarSet() + } unused.Add(dvs.vs[v]) } } - rewrittenUsed := NewVarSet() + // rewritten unused for v := range used { if gv, ok := stack.Declared(v); ok { - rewrittenUsed.Add(gv) - } else { - rewrittenUsed.Add(v) + delete(unused, gv) } } - unused = unused.Diff(rewrittenUsed) if len(unused) == 0 { - return errs + return } reversed := make(map[Var]Var, len(dvs.vs)) @@ -6693,25 +6908,22 @@ func checkUnusedAssignedVars(body Body, stack *localDeclaredVars, used VarSet, e found := false for i := range body { if body[i].Vars(VarVisitorParams{}).Contains(gv) { - errs = append(errs, NewError(CompileErr, body[i].Loc(), "assigned var %v unused", reversed[gv])) + stack.unused = append(stack.unused, NewError(CompileErr, body[i].Loc(), "assigned var %v unused", reversed[gv])) found = true break } } if !found { - errs = append(errs, NewError(CompileErr, body[0].Loc(), "assigned var %v unused", reversed[gv])) + stack.unused = append(stack.unused, NewError(CompileErr, body[0].Loc(), "assigned var %v unused", reversed[gv])) } } - - return errs } -func checkUnusedDeclaredVars(body Body, stack *localDeclaredVars, used VarSet, cpy Body, errs Errors) Errors { - +func checkUnusedDeclaredVars(body Body, stack *localDeclaredVars, used VarSet, cpy Body, errs Errors) { // NOTE(tsandall): Do not generate more errors if there are existing // declaration errors. - if len(errs) > 0 { - return errs + if len(errs) > 0 || len(stack.unused) > 0 { + return } dvs := stack.Peek() @@ -6723,7 +6935,7 @@ func checkUnusedDeclaredVars(body Body, stack *localDeclaredVars, used VarSet, c } } if !hasDeclaredVars { - return errs + return } declared := NewVarSet() @@ -6746,7 +6958,7 @@ func checkUnusedDeclaredVars(body Body, stack *localDeclaredVars, used VarSet, c dbv := declared.Diff(bodyvars) if dbv.DiffCount(used) == 0 { - return errs + return } reversed := make(map[Var]Var, len(dvs.vs)) @@ -6754,30 +6966,35 @@ func checkUnusedDeclaredVars(body Body, stack *localDeclaredVars, used VarSet, c reversed[v] = k } + var varsDeclaredInExpr VarSet + for _, gv := range dbv.Diff(used).Sorted() { rv := reversed[gv] if !rv.IsGenerated() { // Scan through body exprs, looking for a match between the // bad var's original name, and each expr's declared vars. foundUnusedVarByName := false + if varsDeclaredInExpr == nil { + varsDeclaredInExpr = NewVarSet() + } + for i := range body { - varsDeclaredInExpr := declaredVars(body[i]) + clear(varsDeclaredInExpr) + varsDeclaredInExpr = declaredVars(body[i], varsDeclaredInExpr) if varsDeclaredInExpr.Contains(rv) { // TODO(philipc): Clean up the offset logic here when the parser // reports more accurate locations. - errs = append(errs, NewError(CompileErr, body[i].Loc(), "declared var %v unused", rv)) + stack.unused = append(stack.unused, NewError(CompileErr, body[i].Loc(), "declared var %v unused", rv)) foundUnusedVarByName = true break } } // Default error location returned. if !foundUnusedVarByName { - errs = append(errs, NewError(CompileErr, body[0].Loc(), "declared var %v unused", rv)) + stack.unused = append(stack.unused, NewError(CompileErr, body[0].Loc(), "declared var %v unused", rv)) } } } - - return errs } func rewriteEveryStatement(g *localVarGenerator, stack *localDeclaredVars, expr *Expr, errs Errors, strict bool) (*Expr, Errors) { @@ -6864,6 +7081,16 @@ func rewriteSomeDeclStatement(g *localVarGenerator, stack *localDeclaredVars, ex } func rewriteNotStatement(g *localVarGenerator, stack *localDeclaredVars, expr *Expr, errs Errors, strict bool) (*Expr, Errors) { + if not := expr.Terms.(*Not); !not.ExplicitBody { + // Only explicit not bodies are allowed to declare vars. + numErrsBefore := len(errs) + errs = rewriteDeclaredVarsInImplicitBody(g, stack, not.Body, errAssignInNegated, errs, strict) + if len(errs) > numErrsBefore { + return expr, errs + } + return rewriteDeclaredVarsInExpr(g, stack, expr, errs, strict) + } + e := expr.Copy() not := e.Terms.(*Not) @@ -6879,26 +7106,33 @@ func rewriteNotStatement(g *localVarGenerator, stack *localDeclaredVars, expr *E func rewriteLogicalStatement(g *localVarGenerator, stack *localDeclaredVars, expr *Expr, errs Errors, strict bool) (*Expr, Errors) { e := expr.Copy() + numErrsBefore := len(errs) + switch t := e.Terms.(type) { case *LogicalAnd: - if t.ExplicitLhs { - t.Lhs, errs = rewriteLogicalOperandBody(g, stack, t.Lhs, errs, strict) - } - if t.ExplicitRhs { - t.Rhs, errs = rewriteLogicalOperandBody(g, stack, t.Rhs, errs, strict) - } + t.Lhs, errs = rewriteLogicalOperand(g, stack, t.Lhs, t.ExplicitLhs, errAssignInAndOperand, errs, strict) + t.Rhs, errs = rewriteLogicalOperand(g, stack, t.Rhs, t.ExplicitRhs, errAssignInAndOperand, errs, strict) case *LogicalOr: - if t.ExplicitLhs { - t.Lhs, errs = rewriteLogicalOperandBody(g, stack, t.Lhs, errs, strict) - } - if t.ExplicitRhs { - t.Rhs, errs = rewriteLogicalOperandBody(g, stack, t.Rhs, errs, strict) - } + t.Lhs, errs = rewriteLogicalOperand(g, stack, t.Lhs, t.ExplicitLhs, errAssignInOrOperand, errs, strict) + t.Rhs, errs = rewriteLogicalOperand(g, stack, t.Rhs, t.ExplicitRhs, errAssignInOrOperand, errs, strict) + } + + if len(errs) > numErrsBefore { + return e, errs } return rewriteDeclaredVarsInExpr(g, stack, e, errs, strict) } +func rewriteLogicalOperand(g *localVarGenerator, stack *localDeclaredVars, body Body, explicit bool, errMsg string, errs Errors, strict bool) (Body, Errors) { + if explicit { + return rewriteLogicalOperandBody(g, stack, body, errs, strict) + } + + // Only explicit operand bodies are allowed to declare vars. + return body, rewriteDeclaredVarsInImplicitBody(g, stack, body, errMsg, errs, strict) +} + func rewriteLogicalOperandBody(g *localVarGenerator, stack *localDeclaredVars, body Body, errs Errors, strict bool) (Body, Errors) { stack.Push() defer stack.Pop() @@ -6907,10 +7141,28 @@ func rewriteLogicalOperandBody(g *localVarGenerator, stack *localDeclaredVars, b return rewriteDeclaredVarsInBody(g, stack, used, body, errs, strict) } +// rewriteDeclaredVarsInImplicitBody rejects assignments made directly in an implicit +// and/or operand or not body. These contribute no bindings to the enclosing body, +// so the assignment is dead code. Only assignments need rejecting, as the parser +// doesn't allow some/every in an implicit body. +func rewriteDeclaredVarsInImplicitBody(g *localVarGenerator, stack *localDeclaredVars, body Body, errMsg string, errs Errors, strict bool) Errors { + for i := range body { + switch { + case body[i].IsAssignment(): + errs = append(errs, newErrorString(CompileErr, body[i].Loc(), errMsg)) + case body[i].IsNot(): + body[i], errs = rewriteNotStatement(g, stack, body[i], errs, strict) + case body[i].IsAnd(), body[i].IsOr(): + body[i], errs = rewriteLogicalStatement(g, stack, body[i], errs, strict) + } + } + return errs +} + func rewriteDeclaredVarsInExpr(g *localVarGenerator, stack *localDeclaredVars, expr *Expr, errs Errors, strict bool) (*Expr, Errors) { - vis := NewGenericVisitor(func(x any) bool { - var stop bool - // Note: we don't include *Not nodes here, as such bodies are allowed to contain assignments; e.g. 'not {x := input.x; f(x)}' + vis := NewGenericVisitor(func(x any) (stop bool) { + // Note: we don't include *Not nodes here, as such bodies are + // allowed to contain assignments; e.g. 'not {x := input.x; f(x)}' switch x := x.(type) { case *Term: stop, errs = rewriteDeclaredVarsInTerm(g, stack, x, errs, strict) @@ -6924,18 +7176,16 @@ func rewriteDeclaredVarsInExpr(g *localVarGenerator, stack *localDeclaredVars, e } func rewriteDeclaredAssignment(g *localVarGenerator, stack *localDeclaredVars, expr *Expr, errs Errors, strict bool) (*Expr, Errors) { - if expr.Negated { - errs = append(errs, NewError(CompileErr, expr.Location, "cannot assign vars inside negated expression")) - return expr, errs + return expr, append(errs, newErrorString(CompileErr, expr.Location, errAssignInNegated)) } - numErrsBefore := len(errs) - if !validEqAssignArgCount(expr) { return expr, errs } + numErrsBefore := len(errs) + // Rewrite terms on right hand side capture seen vars and recursively // process comprehensions before left hand side is processed. Also // rewrite with modifier. @@ -6994,7 +7244,10 @@ func rewriteDeclaredVarsInTerm(g *localVarGenerator, stack *localDeclaredVars, t switch v := term.Value.(type) { case Var: if gv, ok := stack.Declared(v); ok { - term.Value = gv + // don't allocate for boxing Var -> Value unless it changed + if gv != v { + term.Value = gv + } stack.Seen(v) } else if stack.Occurrence(v) == newVar { stack.Insert(v, v, seenVar) @@ -7003,8 +7256,10 @@ func rewriteDeclaredVarsInTerm(g *localVarGenerator, stack *localDeclaredVars, t if RootDocumentRefs.Contains(term) { x := v[0].Value.(Var) if occ, ok := stack.GlobalOccurrence(x); ok && occ != seenVar { - gv, _ := stack.Declared(x) - term.Value = gv + // don't allocate for boxing Var -> Value unless it changed + if gv, _ := stack.Declared(x); gv != x { + term.Value = gv + } } return true, errs @@ -7022,20 +7277,26 @@ func rewriteDeclaredVarsInTerm(g *localVarGenerator, stack *localDeclaredVars, t }) return false, errs case *object: - cpy, _ := v.Map(func(k, v *Term) (*Term, *Term, error) { - kcpy := k.Copy() - errs = rewriteDeclaredVarsInTermRecursive(g, stack, kcpy, errs, strict) - errs = rewriteDeclaredVarsInTermRecursive(g, stack, v, errs, strict) + term.Value, _ = v.Map(func(k, v *Term) (*Term, *Term, error) { + kcpy := k + if !IsScalar(k.Value) { + kcpy = k.Copy() + errs = rewriteDeclaredVarsInTermRecursive(g, stack, kcpy, errs, strict) + } + if !IsScalar(v.Value) { + errs = rewriteDeclaredVarsInTermRecursive(g, stack, v, errs, strict) + } return kcpy, v, nil }) - term.Value = cpy - case Set: - cpy, _ := v.Map(func(elem *Term) (*Term, error) { + case *set: + term.Value, _ = v.Map(func(elem *Term) (*Term, error) { + if IsScalar(elem.Value) { + return elem, nil + } elemcpy := elem.Copy() errs = rewriteDeclaredVarsInTermRecursive(g, stack, elemcpy, errs, strict) return elemcpy, nil }) - term.Value = cpy case *ArrayComprehension: errs = rewriteDeclaredVarsInArrayComprehension(g, stack, v, errs, strict) case *SetComprehension: @@ -7049,8 +7310,7 @@ func rewriteDeclaredVarsInTerm(g *localVarGenerator, stack *localDeclaredVars, t } func rewriteDeclaredVarsInTermRecursive(g *localVarGenerator, stack *localDeclaredVars, term *Term, errs Errors, strict bool) Errors { - WalkTerms(term, func(t *Term) bool { - var stop bool + WalkTerms(term, func(t *Term) (stop bool) { stop, errs = rewriteDeclaredVarsInTerm(g, stack, t, errs, strict) return stop }) @@ -7280,7 +7540,7 @@ func validateWithBuiltinTarget(bi *Builtin, target Ref, loc *location.Location) } switch { - case target.HasPrefix(Ref([]*Term{VarTerm("internal")})): + case len(target) > 0 && Var("internal").Equal(target[0].Value): return NewError(CompileErr, loc, "with keyword replacing built-in function: replacement of internal function %q invalid", target) case bi.Relation: @@ -7324,23 +7584,41 @@ func isBuiltinRefOrVar(bs map[string]*Builtin, unsafeBuiltinsMap map[string]stru return false, nil } -func safetyErrorSlice(unsafe unsafeVars, rewritten map[Var]Var) (result Errors) { +func safetyErrorSlice(unsafe unsafeVars, rewritten map[Var]Var, scope string) (result Errors) { if len(unsafe) == 0 { return } - for _, pair := range unsafe.Vars() { + assignmentLHS := assignmentLHSVars(unsafe, rewritten) + + pairs := unsafe.Vars() + hasNonAssignmentLHS := false + for _, pair := range pairs { + v := pair.Var + if w, ok := rewritten[v]; ok { + v = w + } + if !v.IsGenerated() && !assignmentLHS.Contains(v) && !assignmentLHS.Contains(pair.Var) { + hasNonAssignmentLHS = true + break + } + } + + for _, pair := range pairs { v := pair.Var if w, ok := rewritten[v]; ok { v = w } if !v.IsGenerated() { - if _, ok := allFutureKeywords[string(v)]; ok { - result = append(result, NewError(UnsafeVarErr, pair.Loc, - "var %[1]v is unsafe (hint: `import future.keywords.%[1]v` to import a future keyword)", v)) + if hasNonAssignmentLHS && (assignmentLHS.Contains(v) || assignmentLHS.Contains(pair.Var)) { continue } - result = append(result, NewError(UnsafeVarErr, pair.Loc, "var %v is unsafe", v)) + if _, ok := allFutureKeywords[string(v)]; ok { + result = append(result, NewError(UnsafeVarErr, pair.Loc, + "var %[1]v is unsafe%[2]v (hint: `import future.keywords.%[1]v` to import a future keyword)", v, scope)) + continue + } + result = append(result, NewError(UnsafeVarErr, pair.Loc, "var %v is unsafe%v", v, scope)) } } @@ -7351,14 +7629,14 @@ func safetyErrorSlice(unsafe unsafeVars, rewritten map[Var]Var) (result Errors) // If the expression contains unsafe generated variables, report which // expressions are unsafe instead of the variables that are unsafe (since // the latter are not meaningful to the user.) - pairs := util.SortedFunc(unsafe.Slice(), func(a, b unsafePair) int { + exprPairs := util.SortedFunc(unsafe.Slice(), func(a, b unsafePair) int { return a.Expr.Location.Compare(b.Expr.Location) }) // Report at most one error per generated variable. seen := NewVarSet() - for _, expr := range pairs { + for _, expr := range exprPairs { before := len(seen) for v := range expr.Vars { if v.IsGenerated() { @@ -7366,13 +7644,90 @@ func safetyErrorSlice(unsafe unsafeVars, rewritten map[Var]Var) (result Errors) } } if len(seen) > before { - result = append(result, NewError(UnsafeVarErr, expr.Expr.Location, "expression is unsafe")) + result = append(result, NewError(UnsafeVarErr, expr.Expr.Location, "expression is unsafe%v", scope)) } } return } +func assignmentLHSVars(unsafe unsafeVars, rewritten map[Var]Var) VarSet { + lhs := NewVarSet() + for expr := range unsafe { + if !expr.fromAssignment || !validEqAssignArgCount(expr) { + continue + } + WalkVars(expr.Operand(0), func(v Var) bool { + lhs.Add(v) + if w, ok := rewritten[v]; ok { + lhs.Add(w) + } + return false + }) + } + return lhs +} + +// ruleScopes resolves the "in rule ..." label appended to safety errors for the +// rules of one module, which is only added where a line holds rules of more than +// one name and the location alone is ambiguous. Its index of those lines is built +// on first use, once per module rather than once per error, as the safety stages +// keep reporting errors after the error limit is reached. +type ruleScopes struct { + module *Module + rows map[int]struct{} + built bool +} + +func (s *ruleScopes) scope(rule *Rule) string { + if s == nil || s.module == nil || rule.Location == nil { + return "" + } + + if !s.built { + s.rows = sharedRuleRows(s.module) + s.built = true + } + + if _, ok := s.rows[rule.Location.Row]; !ok { + return "" + } + + // The ground prefix of the head ref is the rule's name: any dynamic part + // (e.g. the key in p[k]) may have been rewritten to a generated local by an + // earlier compiler stage, and isn't needed to identify the rule. + return " in rule " + rule.Head.Ref().GroundPrefix().String() +} + +// sharedRuleRows returns the source rows of module that hold rules of more than +// one name. +func sharedRuleRows(module *Module) map[int]struct{} { + var shared map[int]struct{} + first := map[int]Ref{} + + WalkRules(module, func(rule *Rule) bool { + if rule.Location == nil { + return false + } + + row := rule.Location.Row + name := rule.Head.Ref().GroundPrefix() + + if prev, ok := first[row]; !ok { + first[row] = name + } else if !prev.Equal(name) { + if shared == nil { + shared = map[int]struct{}{} + } + shared[row] = struct{}{} + } + + return false + }) + + return shared +} + func checkUnsafeBuiltins(unsafeBuiltinsMap map[string]struct{}, node any) Errors { var errs Errors WalkExprs(node, func(x *Expr) bool { @@ -7401,6 +7756,39 @@ func rewriteVarsInRef(vars ...map[Var]Var) varRewriter { } } +// mergeRefSubjects merges src into dst, allocating dst if needed. +func mergeRefSubjects(dst, src map[Var]Value) map[Var]Value { + if len(src) == 0 { + return dst + } + if dst == nil { + dst = make(map[Var]Value, len(src)) + } + maps.Copy(dst, src) + return dst +} + +// rewriteRefErrVars returns a varRewriter for rendering refs in type errors. +// Beyond the var-to-var mappings of rewriteVarsInRef, it substitutes generated +// locals recorded in localVarGenerator.subjects with the original term (so +// errors show [1, 2][i] rather than __local0__[i]). It operates on a copy. +func rewriteRefErrVars(subjects map[Var]Value, vars ...map[Var]Var) varRewriter { + return func(node Ref) Ref { + i, _ := TransformVars(node.Copy(), func(v Var) (Value, error) { + if val, ok := subjects[v]; ok { + return CopyValue(val), nil + } + for _, m := range vars { + if u, ok := m[v]; ok { + return u, nil + } + } + return v, nil + }) + return i.(Ref) + } +} + type ruleRefSet struct { s []ruleRef } diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/env.go b/vendor/github.com/open-policy-agent/opa/v1/ast/env.go index c3a5fcef02..98c8f4b20f 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/env.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/env.go @@ -14,7 +14,10 @@ import ( // TypeEnv contains type info for static analysis such as type checking. type TypeEnv struct { - tree *typeTreeNode + tree *typeTreeNode + // vars is the tree the types inferred for variables are stored in, which is + // tree except in the environments created for with modifiers. + vars *typeTreeNode next *TypeEnv newChecker func() *typeChecker } @@ -22,8 +25,10 @@ type TypeEnv struct { // newTypeEnv returns an empty TypeEnv. The constructor is not exported because // type environments should only be created by the type checker. func newTypeEnv(f func() *typeChecker) *TypeEnv { + tree := newTypeTree() return &TypeEnv{ - tree: newTypeTree(), + tree: tree, + vars: tree, newChecker: f, } } @@ -64,12 +69,7 @@ func (env *TypeEnv) GetByValue(v Value) types.Type { static[i] = env.GetByValue(x.Elem(i).Value) } - var dynamic types.Type - if len(static) == 0 { - dynamic = types.A - } - - return types.NewArray(static, dynamic) + return types.NewArray(static, nil) case *lazyObj: return env.GetByValue(x.force()) @@ -88,10 +88,6 @@ func (env *TypeEnv) GetByValue(v Value) types.Type { dynamic = types.NewDynamicProperty(env.GetByValue(k.Value), env.GetByValue(v.Value)) }) - if len(static) == 0 && dynamic == nil { - dynamic = types.NewDynamicProperty(types.A, types.A) - } - return types.NewObject(static, dynamic) case *set: @@ -99,9 +95,6 @@ func (env *TypeEnv) GetByValue(v Value) types.Type { x.Foreach(func(elem *Term) { tpe = types.Or(tpe, env.GetByValue(elem.Value)) }) - if tpe == nil { - tpe = types.A - } return types.NewSet(tpe) // Comprehensions. @@ -231,9 +224,19 @@ func (env *TypeEnv) wrap() *TypeEnv { cpy := *env cpy.next = env cpy.tree = newTypeTree() + cpy.vars = cpy.tree return &cpy } +// wrapWith returns a TypeEnv for checking a single expression carrying with +// modifiers: unlike wrap, the types it infers for variables are kept in the +// enclosing environment, as those variables outlive the expression. +func (env *TypeEnv) wrapWith() *TypeEnv { + cpy := env.wrap() + cpy.vars = env.vars + return cpy +} + // typeTreeNode is used to store type information in a tree. type typeTreeNode struct { key Value @@ -368,6 +371,8 @@ func (n *typeTreeNode) Insert(path Ref, tpe types.Type, env *TypeEnv) { // with an types.Or, instead of being merged. // If 'a' is an Any containing an Object, and 'b' is an Object (or vice versa); AND both objects have no // static properties, they are merged. +// If either object has neither static nor dynamic properties, it is the empty object type, and the other +// type is returned unchanged. // If 'a' and 'b' are different types, they are joined with an types.Or. func mergeTypes(a, b types.Type) types.Type { if a == nil { @@ -388,25 +393,33 @@ func mergeTypes(a, b types.Type) types.Type { switch a := a.(type) { case *types.Object: + aDynProps := a.DynamicProperties() if bObj, ok := b.(*types.Object); ok && len(a.StaticProperties()) == 0 && len(bObj.StaticProperties()) == 0 { - if len(a.StaticProperties()) > 0 || len(bObj.StaticProperties()) > 0 { - return types.Or(a, bObj) + bDynProps := bObj.DynamicProperties() + + // An object type with neither static nor dynamic properties is the + // empty object, which the other object type already covers. + if aDynProps == nil { + return bObj + } + if bDynProps == nil { + return a } - aDynProps := a.DynamicProperties() - bDynProps := bObj.DynamicProperties() dynProps := types.NewDynamicProperty( types.Or(aDynProps.Key, bDynProps.Key), mergeTypes(aDynProps.Value, bDynProps.Value), ) return types.NewObject(nil, dynProps) - } else if bAny, ok := b.(types.Any); ok && len(a.StaticProperties()) == 0 { + } else if bAny, ok := b.(types.Any); ok && len(a.StaticProperties()) == 0 && aDynProps != nil { // If a is an object type with no static components ... for _, t := range bAny { if tObj, ok := t.(*types.Object); ok && len(tObj.StaticProperties()) == 0 { // ... and b is a types.Any containing an object with no static components, we merge them. - aDynProps := a.DynamicProperties() tDynProps := tObj.DynamicProperties() + if tDynProps == nil { + continue + } tDynProps.Key = types.Or(tDynProps.Key, aDynProps.Key) tDynProps.Value = types.Or(tDynProps.Value, aDynProps.Value) return bAny diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/errors.go b/vendor/github.com/open-policy-agent/opa/v1/ast/errors.go index 1188cd6e08..a1ea433683 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/errors.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/errors.go @@ -9,6 +9,8 @@ import ( "slices" "strconv" "strings" + + "github.com/open-policy-agent/opa/v1/util" ) // Errors represents a series of errors encountered during parsing, compiling, @@ -16,21 +18,14 @@ import ( type Errors []*Error func (e Errors) Error() string { - if len(e) == 0 { return "no error(s)" } - if len(e) == 1 { - return fmt.Sprintf("1 error occurred: %v", e[0].Error()) + return "1 error occurred: " + e[0].Error() } - s := make([]string, len(e)) - for i, err := range e { - s[i] = err.Error() - } - - return fmt.Sprintf("%d errors occurred:\n%s", len(e), strings.Join(s, "\n")) + return fmt.Sprintf("%d errors occurred:\n%s", len(e), strings.Join(util.Map(e, (*Error).Error), "\n")) } // Sort sorts the error slice by location. If the locations are equal then the @@ -67,10 +62,8 @@ const ( // IsError returns true if err is an AST error with code. func IsError(code string, err error) bool { - if err, ok := err.(*Error); ok { - return err.Code == code - } - return false + e, ok := err.(*Error) + return ok && e.Code == code } // ErrorDetails defines the interface for detailed error messages. @@ -90,7 +83,6 @@ func (e *Error) Error() string { var prefix string if e.Location != nil { - if len(e.Location.File) > 0 { prefix += e.Location.File + ":" + strconv.Itoa(e.Location.Row) } else { diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/index.go b/vendor/github.com/open-policy-agent/opa/v1/ast/index.go index 3502550798..ea96d6f00b 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/index.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/index.go @@ -5,6 +5,9 @@ package ast import ( + "cmp" + "maps" + "math/bits" "slices" "strings" "sync" @@ -13,15 +16,8 @@ import ( ) var ( - equalityRef = Equality.Ref() - equalRef = Equal.Ref() - globMatchRef = GlobMatch.Ref() - internalPrintRef = InternalPrint.Ref() - internalTestCaseRef = InternalTestCase.Ref() - internalMemberRef = Member.Ref() - globwildcard = VarTerm("$globwildcard") - skipIndexing = NewSet(NewTerm(internalPrintRef), NewTerm(internalTestCaseRef)) + skipIndexing = NewSet(NewTerm(Interned.Refs.InternalPrint), NewTerm(Interned.Refs.InternalTestCase)) // anyValue is a fake variable we used to put "naked ref" expressions // into the rule index @@ -60,6 +56,25 @@ type ( defaultRule *Rule kind RuleKind onlyGroundRefs bool + // rules holds one entry per rule and else branch the trie carries, groups + // the position of its ruleset among the rules Build was given. Reading the + // ids a lookup reached in increasing order groups them and orders each + // group by priority; see trieTraversalResult and gather. + rules []*Rule + groups []int32 + // required holds, per rule id, the refs it needs defined that are not + // trie levels, as positions in requiredRefs. See refindices.partition. + required map[int32][]int32 + // requiredRefs names those refs, indexed by the positions required holds. + // Numbering them keeps a lookup's memo a lookup by position rather than a + // search: a rule reading a ref of its own is the shape that made the memo + // a linear scan over one entry per rule. + requiredRefs []Ref + // memberships holds, per rule id, the collections gather consults; recorded + // by refindices.recordMembership. + memberships map[int32][]membership + // mayEarlyExit decides whether consulting a collection pays; see gather. + mayEarlyExit bool } ) @@ -87,7 +102,7 @@ func (i *baseDocEqIndex) Build(rules []*Rule) bool { } i.kind = rules[0].Head.RuleKind() - indices := newrefindices(i.isVirtual) + indices := newrefindices(i.isVirtual, newRefTable()) values := make(map[Var]Value) // build indices for each rule. @@ -100,7 +115,7 @@ func (i *baseDocEqIndex) Build(rules []*Rule) bool { if i.onlyGroundRefs { i.onlyGroundRefs = rule.Head.Reference.IsGround() } - if !slices.ContainsFunc(rule.Body, skipIndexingOperator) { + if !bodySkipsIndexing(rule.Body) { clear(values) for i := range rule.Body { indices.Update(rule, rule.Body[i], values) @@ -111,143 +126,427 @@ func (i *baseDocEqIndex) Build(rules []*Rule) bool { } // build trie out of indices. + levels, unvalued := indices.partition(indices.Sorted()) + for idx := range rules { - var prio int WalkRules(rules[idx], func(rule *Rule) bool { if rule.Default { return false } - node := i.root - if indices.Indexed(rule) { - for _, ref := range indices.Sorted() { - var values []*refindex - for _, ri := range indices.rules[rule] { - if ri.Ref.Equal(ref) { - values = append(values, ri) - } - } - if len(values) == 0 { - node = node.Insert(ref, nil, nil) - } else if len(values) == 1 { - node = node.Insert(ref, values[0].Value, values[0].Mapper) - } else { - if slices.ContainsFunc(values, (*refindex).isVar) { - child := node.Insert(ref, anyValue, values[0].Mapper) - for i := range values { - if values[i].Mapper != nil { - node.next.addMapper(values[i].Mapper) - } - } - node = child - } else { - // When a rule has multiple scalar values (e.g., internal.member_2 with a set), - // each value should have its own child node, and the rule is appended to each. - // This creates separate paths for each value so different rules with overlapping - // values don't interfere with each other. - for _, val := range values { - child := node.Insert(ref, val.Value, val.Mapper) - child.append([...]int{idx, prio}, rule) - } - prio++ - return false - } - } + + // Ids are minted in WalkRules' order, so they ascend with priority + // within a ruleset -- the (insertion, priority) pair a node used to + // carry, in one integer: + // + // f(x) := 1 if x == "a" # group 0, id 0 + // else := 2 if x == "b" # id 1 + // f(x) := 3 if x == "c" # group 1, id 2 + id := int32(len(i.rules)) + i.rules = append(i.rules, rule) + i.groups = append(i.groups, int32(idx)) + + if ms := indices.memberships[rule]; len(ms) > 0 { + if i.memberships == nil { + i.memberships = make(map[int32][]membership, len(rules)) } + i.memberships[id] = ms + } + + // Each set of indices the rule can be reached through gets its own + // path. They share an id, so a lookup arriving at the rule down + // several of them still reports it once (see trieTraversalResult.Add). + paths := indices.disjunctions[rule] + if len(paths) == 0 { + i.insertPath(indices.table, levels, indices.rules[rule], id, rule) + i.require(indices.table, id, unvalued, alternatives{indices.rules[rule]}) + } else { + alts := indices.paths(rule) + for _, path := range alts { + i.insertPath(indices.table, levels, path, id, rule) + } + i.require(indices.table, id, unvalued, alts) } - // Insert rule into trie with (insertion order, priority order) - // tuple. Retaining the insertion order allows us to return rules - // in the order they were passed to this function. - node.append([...]int{idx, prio}, rule) - prio++ return false }) } + + i.root.compact() + i.mayEarlyExit = mayEarlyExit(i.rules) + return true } +// mayEarlyExit reports whether a caller could stop at the first of these rules +// that holds. Build asks it of every rule, which is what gather can know before it +// has candidates; Lookup asks resultMayEarlyExit of the candidates, which is finer. +func mayEarlyExit(rules []*Rule) bool { + var value Value + return agreeOnValue(rules, &value) +} + +// require records the refs rule needs defined, of those partition kept out of the +// trie. Only a ref every path to the rule reads is recorded: one an `or` reads on +// a single alternative does not have to hold for the rule to match, and a lookup +// that dropped the rule over it would lose an answer rather than a shortcut. +func (i *baseDocEqIndex) require(table *refTable, id int32, unvalued []refID, paths alternatives) { + if len(unvalued) == 0 || len(paths) == 0 { + return + } + + var required []int32 + for pos, ref := range unvalued { + reads := func(path []*refindex) bool { + return slices.ContainsFunc(path, func(ri *refindex) bool { return ri.ref == ref }) + } + if !slices.ContainsFunc(paths, func(path []*refindex) bool { return !reads(path) }) { + required = append(required, int32(pos)) + } + } + + if len(required) > 0 { + if i.required == nil { + i.required = make(map[int32][]int32, len(unvalued)) + i.requiredRefs = make([]Ref, len(unvalued)) + for pos, ref := range unvalued { + i.requiredRefs[pos] = table.ref(ref) + } + } + i.required[id] = required + } +} + +func (i *baseDocEqIndex) insertPath(table *refTable, levels []refID, path []*refindex, id int32, rule *Rule) { + node := i.root + + // The path stops at the last level it constrains. A rule that constrains + // nothing below has nothing to test there, so walking on would only pad the + // path with an "absent" node per remaining level -- a copy of the whole tail + // that no other rule shares, which is what made a trie of n levels cost n^2 + // nodes to build and to walk. The multiple-scalar case below has always + // attached mid-trie for the same reason. + remaining := len(path) + + // One scratch slice for every level, not one per level: a rule's path crosses + // every level above the last it constrains, most of them constraining nothing. + var values []*refindex + + for _, level := range levels { + if remaining == 0 { + break + } + + values = values[:0] + for _, ri := range path { + if ri.ref == level { + values = append(values, ri) + } + } + remaining -= len(values) + + ref := table.ref(level) + + // A var value records "this ref can be anything", which a concrete value + // for the same ref supersedes: everything on one path has to hold, so the + // concrete value is the stronger of the two constraints. A chain of + // assignments, `x := input.a; y := x`, leaves one var entry per local + // behind, and only the first of them is replaced when the concrete value + // is inserted. Keeping the rest would index the rule under anyValue below + // and give up all the discrimination the concrete value buys us. + if len(values) > 1 { + if concrete := slices.DeleteFunc(slices.Clone(values), (*refindex).isVar); len(concrete) > 0 { + values = concrete + } + } + + if len(values) == 0 { + node = node.Insert(ref, nil, nil) + } else if len(values) == 1 { + node = values[0].insertInto(node, ref) + } else { + if slices.ContainsFunc(values, (*refindex).isVar) { + child := node.Insert(ref, anyValue, values[0].Mapper) + for i := range values { + if values[i].Mapper != nil { + node.next.addMapper(values[i].Mapper) + } + } + node = child + } else if remaining == 0 || slices.ContainsFunc(values, (*refindex).isAffix) || + slices.ContainsFunc(values, (*refindex).isComposite) { + // Nothing below to continue a path with, so the rule hangs off + // every alternative -- which rules reaching the same values + // share. Affixes always take this route; see alternation, and + // so does anything a converging level could not be keyed on: + // insertValue sends an object or a set to the "anything" node + // and an array into the array trie, which is where a lookup + // goes looking for them. + for _, val := range oneAffixEnd(values) { + child := val.insertInto(node, ref) + child.append(id, rule) + } + return + } else { + // The alternatives meet again on one node, and the rest of the + // path is built from there rather than under each of them. + node = node.insertAlternatives(ref, values) + } + } + } + + node.append(id, rule) +} + func (i *baseDocEqIndex) Lookup(resolver ValueResolver) (*IndexResult, error) { tr := ttrPool.Get().(*trieTraversalResult) defer func() { - // Note(anderseknert): `clear`ing the map is not good enough here, as it'd mean - // resetting each of its slice values, costing us new allocations on each append - // in subsequent lookups - for i := range tr.unordered { - tr.unordered[i] = tr.unordered[i][:0] - } - tr.ordering = tr.ordering[:0] - tr.multiple = false - tr.exist = nil - + tr.reset() ttrPool.Put(tr) }() + tr.grow(len(i.rules)) err := i.root.Traverse(resolver, tr) if err != nil { return nil, err } - result := IndexResultPool.Get() result.Kind = i.kind result.Default = i.defaultRule result.OnlyGroundRefs = i.onlyGroundRefs - if result.Rules == nil { - result.Rules = make([]*Rule, 0, len(tr.ordering)) - } else { - result.Rules = result.Rules[:0] - } + result.Rules = result.Rules[:0] clear(result.Else) - for _, pos := range tr.ordering { - if len(tr.unordered[pos]) == 0 { - continue - } - slices.SortFunc(tr.unordered[pos], (*ruleNode).prio1Cmp) - nodes := tr.unordered[pos] - root := nodes[0].rule - - result.Rules = append(result.Rules, root) - if len(nodes) > 1 { - if result.Else == nil { - result.Else = map[*Rule][]*Rule{} - } - - result.Else[root] = make([]*Rule, len(nodes)-1) - for i := 1; i < len(nodes); i++ { - result.Else[root][i-1] = nodes[i].rule - } - } + if err := i.gather(tr, resolver, result); err != nil { + IndexResultPool.Put(result) + return nil, err } - if !tr.multiple { - // even when the indexer hasn't seen multiple values, the rule itself could be one - // where early exit shouldn't be applied. - var lastValue Value - for i := range result.Rules { - if result.Rules[i].Head.DocKind() != CompleteDoc { - tr.multiple = true - break - } - if result.Rules[i].Head.Value != nil { - if lastValue != nil && !ValueEqual(lastValue, result.Rules[i].Head.Value.Value) { - tr.multiple = true - break - } - lastValue = result.Rules[i].Head.Value.Value - } - } - } + // Decided over the candidates rather than over what traversal saw, which is + // finer -- and has to be, now that the refs partition keeps out of the trie no + // longer separate the definitions into nodes of their own: of `p := 1 if + // input.foo`, `p := 2 if input.bar` and `p := 1 if input.baz`, the two agreeing + // on 1 are all a lookup returns when input has no bar. + tr.multiple = !resultMayEarlyExit(result) result.EarlyExit = !tr.multiple return result, nil } +// resultMayEarlyExit reports whether a caller could stop at the first candidate +// that holds, the else branches included: they are values it could stop at too. +func resultMayEarlyExit(result *IndexResult) bool { + var value Value + if !agreeOnValue(result.Rules, &value) { + return false + } + for _, branches := range result.Else { + if !agreeOnValue(branches, &value) { + return false + } + } + return true +} + +// agreeOnValue reports whether rules are complete documents agreeing on value, +// which it carries in so that several sets of rules can be asked as one. +func agreeOnValue(rules []*Rule, value *Value) bool { + for _, rule := range rules { + if rule.Head.DocKind() != CompleteDoc { + return false + } + if rule.Head.Value == nil { + continue + } + // A value that is not ground is a different one per binding, so there is + // no first answer to stop at. + v := rule.Head.Value.Value + if !v.IsGround() { + return false + } + if *value != nil && !ValueEqual(*value, v) { + return false + } + *value = v + } + return true +} + +// resolve answers for a reference, asking the resolver the first time only. +func (c *resolveCache) resolve(resolver ValueResolver, ref Ref) (Value, error) { + if c.keyOK && RefEqual(c.keyRef, ref) { + return c.keyVal, nil + } + v, err := resolver.Resolve(ref) + if err != nil { + return nil, err + } + c.keyRef, c.keyVal, c.keyOK = ref, v, true + return v, nil +} + +// container resolves the collection at ref, resolving the container holding it +// once: `data.groups.g1.members` and `data.groups.g2.members` share one resolve. +func (c *resolveCache) container(resolver ValueResolver, ref Ref) (Value, error) { + const shared = 2 // data. + + if len(ref) <= shared { + return resolver.Resolve(ref) + } + + prefix := ref[:shared] + if !c.prefixOK || !RefEqual(c.prefix, prefix) { + v, err := resolver.Resolve(prefix) + if err != nil { + return nil, err + } + c.prefix, c.prefixVal, c.prefixOK = prefix, v, true + } + if c.prefixVal == nil { + return nil, nil + } + + v, err := c.prefixVal.Find(ref[shared:]) + if err != nil { + return nil, nil // undefined, not an error + } + return v, nil +} + +// consultsCollections reports whether excluding a candidate is worth consulting +// its collections for. It is not where the caller stops at the first candidate that +// holds: gather would have to consult all of them to exclude any, which is the work +// evaluation was about to do -- the lookup being what the rule tests. Whether a +// caller stops is the caller's own business, not IndexResult.EarlyExit's, which +// says what the ruleset permits, so the resolver is asked rather than assumed. +func (i *baseDocEqIndex) consultsCollections(resolver ValueResolver) bool { + if !i.mayEarlyExit { + return true + } + every, ok := resolver.(IndexEveryCandidateEvaluated) + return ok && every.IndexEveryCandidateEvaluated() +} + +// inCollections reports whether the rule's collection memberships hold. A +// collection the resolver cannot answer for keeps the rule, as does an array: +// the only thing to ask one is a position, which tells a ruleset's rules apart +// only where their lengths differ. +func (i *baseDocEqIndex) inCollections(resolver ValueResolver, id int32, cache *resolveCache) (bool, error) { + for _, m := range i.memberships[id] { + key, err := cache.resolve(resolver, m.key) + if err != nil { + if IsUnknownValueErr(err) { + continue + } + return false, err + } + if key == nil { + return false, nil + } + + coll, err := cache.container(resolver, m.collection) + if err != nil { + if IsUnknownValueErr(err) { + continue + } + return false, err + } + if coll == nil { + return false, nil + } + + probe := Term{Value: key} + switch c := coll.(type) { + case Object: + if c.Get(&probe) == nil { + return false, nil + } + // Base data read from JSON holds no set, but a store keeping ast.Value can, + // and so can a `with` statement replacing the collection. + case Set: + if !c.Contains(&probe) { + return false, nil + } + } + } + + return true, nil +} + +// gather reads the rules a traversal reached into result. Ids ascend with +// priority, so a run of them sharing a group is that ruleset's definitions in +// order, the first being the one to evaluate. +// +// The refs partition kept out of the trie are checked here rather than as +// traversal reaches a rule: a bit set for a rule that turns out undefined costs +// nothing to leave set, and asking here means the resolver's error is the return +// value of something rather than a field to be picked up afterwards. A nil +// resolver asks nothing, which is what AllRules wants. +func (i *baseDocEqIndex) gather(tr *trieTraversalResult, resolver ValueResolver, result *IndexResult) error { + var cache resolveCache + var root *Rule + group := int32(-1) + consults := i.consultsCollections(resolver) + + // Words are marked as they are first written to, in traversal order. + slices.Sort(tr.touched) + + found := 0 + for _, w := range tr.touched { + found += bits.OnesCount64(tr.hits[w]) + } + result.Rules = slices.Grow(result.Rules, found) + + // A word holds 64 ids: `w<<6` is the id of its first bit, TrailingZeros64 the + // offset of the lowest set one, and `word &= word - 1` clears it. + for _, w := range tr.touched { + for word := tr.hits[w]; word != 0; word &= word - 1 { + id := w<<6 | int32(bits.TrailingZeros64(word)) + + if resolver != nil && len(i.required) > 0 { + defined, err := i.defined(resolver, id, &cache) + if err != nil { + return err + } + if !defined { + continue + } + } + + if consults && resolver != nil && len(i.memberships) > 0 { + in, err := i.inCollections(resolver, id, &cache) + if err != nil { + return err + } + if !in { + continue + } + } + + rule := i.rules[id] + + if g := i.groups[id]; g != group { + group, root = g, rule + result.Rules = append(result.Rules, rule) + continue + } + + if result.Else == nil { + result.Else = map[*Rule][]*Rule{} + } + result.Else[root] = append(result.Else[root], rule) + } + } + + return nil +} + func (i *baseDocEqIndex) AllRules(ValueResolver) (*IndexResult, error) { tr := newTrieTraversalResult() + tr.grow(len(i.rules)) // Walk over the rule trie and accumulate _all_ rules rw := &ruleWalker{result: tr} @@ -256,27 +555,10 @@ func (i *baseDocEqIndex) AllRules(ValueResolver) (*IndexResult, error) { result := NewIndexResult(i.kind) result.Default = i.defaultRule result.OnlyGroundRefs = i.onlyGroundRefs - result.Rules = make([]*Rule, 0, len(tr.ordering)) - for _, pos := range tr.ordering { - if len(tr.unordered[pos]) == 0 { - continue - } - slices.SortFunc(tr.unordered[pos], (*ruleNode).prio1Cmp) - nodes := tr.unordered[pos] - root := nodes[0].rule - result.Rules = append(result.Rules, root) - if len(nodes) > 1 { - if result.Else == nil { - result.Else = map[*Rule][]*Rule{} - } - - result.Else[root] = make([]*Rule, len(nodes)-1) - for i := 1; i < len(nodes); i++ { - result.Else[root][i-1] = nodes[i].rule - } - } - } + // Every rule the trie holds, so nothing is asked of the resolver and + // nothing can fail; see gather. + _ = i.gather(tr, nil, result) result.EarlyExit = !tr.multiple @@ -298,30 +580,235 @@ type valueMapper struct { MapValue func(Value) Value } +// IndexEveryCandidateEvaluated may be implemented by a ValueResolver to answer +// whether every candidate a lookup returns goes on to be evaluated, rather than the +// caller stopping at the first that holds. Where it does, excluding a candidate +// saves evaluating it; see baseDocEqIndex.gather. +type IndexEveryCandidateEvaluated interface { + IndexEveryCandidateEvaluated() bool +} + +// membership is "the value at key has to be a key of the collection at +// collection", which a lookup asks the collection rather than the trie. +type membership struct{ key, collection Ref } + +// refID identifies one of the references an index is built on. +type refID int32 + +// refTable numbers the references an index is built on. One table is shared by +// every refindices of a build, the scratch ones an `and`/`or` operand is +// indexed into included, so that an id means the same thing wherever it turns +// up. +type refTable struct { + // refs are the references in id order; ids answers the other direction, and + // is only built past refTableScan entries. + refs []Ref + ids *util.HasherMap[Ref, refID] +} + +// refTableScan is how many references a table holds before it builds a map: +// below that, comparing a ref to the few already here beats hashing it, and most +// rulesets are indexed on a handful. +const refTableScan = 8 + +func newRefTable() *refTable { + return &refTable{} +} + +func (t *refTable) intern(ref Ref) refID { + if t.ids == nil { + for id, other := range t.refs { + if RefEqual(other, ref) { + return refID(id) + } + } + if len(t.refs) < refTableScan { + t.refs = append(t.refs, ref) + return refID(len(t.refs) - 1) + } + t.ids = util.NewHasherMap[Ref, refID](RefEqual) + for id, other := range t.refs { + t.ids.Put(other, refID(id)) + } + } + + if id, ok := t.ids.Get(ref); ok { + return id + } + id := refID(len(t.refs)) + t.refs = append(t.refs, ref) + t.ids.Put(ref, id) + return id +} + +func (t *refTable) ref(id refID) Ref { + return t.refs[id] +} + type refindex struct { - Ref Ref Value Value Mapper *valueMapper + // ref is the reference this constrains, as numbered by the build's table. + ref refID + // Affix says whether Value is a string the value at ref has to start or end + // with, rather than one it has to equal -- what startswith, endswith and + // their strings.any_*_match forms contribute. Several of them for one ref + // are alternatives, as for `in`. + Affix affix } +// affix is which end of the value at a reference a refindex constrains, if it +// constrains an end rather than the whole of it. +type affix uint8 + +const ( + affixNone affix = iota + affixPrefix + affixSuffix +) + +// insertInto adds the level this index constrains to the path being built, +// returning the node the rest of the path continues from. +func (i *refindex) insertInto(node *trieNode, ref Ref) *trieNode { + switch i.Affix { + case affixPrefix: + return node.InsertPrefix(ref, i.Value) + case affixSuffix: + return node.InsertSuffix(ref, i.Value) + } + return node.Insert(ref, i.Value, i.Mapper) +} + +// oneAffixEnd keeps the affixes of one end of the value where values constrain +// both, and everything that is not an affix. +// +// A rule hung off the leaves of both the prefix and the suffix trie is admitted +// by either, which is the disjunction of what it wrote where it wrote a +// conjunction: +// +// p if { +// strings.any_prefix_match(input.path, ["/a", "/b"]) +// strings.any_suffix_match(input.path, [".go", ".rego"]) +// } +// +// admits "/c/x.go" on the suffix alone. Testing one end and leaving the other to +// evaluation admits a subset of that -- what one end admits, both admit -- so +// one end is kept. A level cannot test both: the tries hold leaves, and a leaf +// cannot be made to depend on another trie's answer. +// +// Which end is kept is decided by the shortest base string of each, since a set +// admits a value that matches any one of its bases and the shortest of them +// admits the most. Counting them instead would keep ["/"] over [".go", +// ".rego"], and every absolute path matches "/". +func oneAffixEnd(values []*refindex) []*refindex { + prefix, suffix := -1, -1 + for _, val := range values { + s, ok := val.Value.(String) + if !ok { + continue + } + switch val.Affix { + case affixPrefix: + if prefix < 0 || len(s) < prefix { + prefix = len(s) + } + case affixSuffix: + if suffix < 0 || len(s) < suffix { + suffix = len(s) + } + } + } + + if prefix < 0 || suffix < 0 { + return values + } + + drop := affixSuffix + if suffix > prefix { + drop = affixPrefix + } + + return slices.DeleteFunc(slices.Clone(values), func(val *refindex) bool { + return val.Affix == drop + }) +} + +// alternatives are sets of indices, any one of which is enough to reach a rule. +type alternatives = [][]*refindex + type refindices struct { isVirtual func(Ref) bool rules map[*Rule][]*refindex - frequency *util.HasherMap[Ref, int] - sorted []Ref + // disjunctions holds the alternatives contributed by each `or` in the rule; + // every combination of them is a way to reach it. + disjunctions map[*Rule][]alternatives + // outer holds the enclosing scope's indices when this is the scratch for an + // operand body: resolvable from inside, but not the operand's own. + outer []*refindex + table *refTable + // memberships holds the collection memberships of each rule; see membership. + memberships map[*Rule][]membership + // stats holds what Sorted ranks the references by, indexed by ref id. + stats []refStats + sorted []refID } -func newrefindices(isVirtual func(Ref) bool) *refindices { +// refStats is what one reference accumulated over a build, which is what decides +// the order of the trie's levels. Dropped once the trie is built. +type refStats struct { + // count is how often the ref took part in indexing a rule. Sorted passes + // over the ids that never counted: a scratch interns the refs of an operand + // that may turn out unindexable, and then nothing records them. + count int32 + // alternated is whether some rule reaches the ref by more than one value, + // and what that costs insertPath. An `or` is not recorded: its alternatives + // are separate paths, and only meet a second value for one ref once paths() + // combines them, after Sorted has run. + alternated alternation +} + +// maxIndexPaths caps the ways a single rule may be reached: `or` expressions +// multiply out (`{a or b} and {c or d}` is four), and at some point the trie +// nodes cost more than evaluating the rule. +const maxIndexPaths = 32 + +func newrefindices(isVirtual func(Ref) bool, table *refTable) *refindices { return &refindices{ - isVirtual: isVirtual, - rules: map[*Rule][]*refindex{}, - frequency: util.NewHasherMap[Ref, int](RefEqual), + isVirtual: isVirtual, + table: table, + rules: map[*Rule][]*refindex{}, + memberships: map[*Rule][]membership{}, } } +// growTo extends s so that it can be indexed by every id below n, leaving what +// it already holds in place. +func growTo[T any](s []T, n int) []T { + if len(s) >= n { + return s + } + return append(s, make([]T, n-len(s))...) +} + +func valueIsVar(v Value) bool { + _, ok := v.(Var) + return ok +} + func (i *refindex) isVar() bool { - _, isVar := i.Value.(Var) - return isVar + return valueIsVar(i.Value) +} + +func (i *refindex) isAffix() bool { + return i.Affix != affixNone +} + +// isComposite reports whether a lookup could not find this value among a +// level's alternatives, which are keyed on the value as it stands. insertValue +// sends an object or a set to the "anything" node and an array into the array +// trie, which is where a lookup goes looking for them instead. +func (i *refindex) isComposite() bool { + return !IsScalar(i.Value) } // Update attempts to update the refindices for the given expression in the @@ -340,6 +827,15 @@ func (i *refindices) Update(rule *Rule, expr *Expr, values map[Var]Value) { return } + switch terms := expr.Terms.(type) { + case *LogicalAnd: + i.updateLogicalAnd(rule, terms, values) + return + case *LogicalOr: + i.updateLogicalOr(rule, terms, values) + return + } + op := expr.Operator() if op == nil { if ts, ok := expr.Terms.(*Term); ok { @@ -347,18 +843,23 @@ func (i *refindices) Update(rule *Rule, expr *Expr, values map[Var]Value) { // check for type "Var" here. But since it's impossible to call a // function with a undefined argument, there's no point to recording // "needs to be anything" for function args - if _, ok := ts.Value.(Ref); ok { // "naked ref" - i.updateEq(rule, ts.Value, anyValue, nil) + if ref, ok := ts.Value.(Ref); ok { // "naked ref" + // `data.groups.g1.members[input.subject]` constrains its last + // element to the keys of the collection at the ground prefix, + // which is more than the "is defined" updateEq can record. + if !i.updateCollectionKey(rule, ref) { + i.updateEq(rule, ts.Value, anyValue, nil) + } } } } - equalish := op.Equal(equalityRef) || // unification, no 3-operands version exists + equalish := op.Equal(Interned.Refs.Equality) || // unification, no 3-operands version exists // NOTE(tsandall): if equal() is called with more than two arguments the // output value is being captured in which case the indexer cannot // exclude the rule if the equal() call would return false (because the // false value must still be produced.) - (op.Equal(equalRef) && len(expr.Operands()) == 2) + (op.Equal(Interned.Refs.Equal) && len(expr.Operands()) == 2) a, b := expr.Operand(0), expr.Operand(1) switch { @@ -367,14 +868,152 @@ func (i *refindices) Update(rule *Rule, expr *Expr, values map[Var]Value) { i.updateEq(rule, a.Value, b.Value, values) } - case op.Equal(globMatchRef) && len(expr.Operands()) == 3: + case op.Equal(Interned.Refs.GlobMatch) && len(expr.Operands()) == 3: // NOTE(sr): Same as with equal() above -- 4 operands means the output // of `glob.match` is captured and the rule can thus not be excluded. i.updateGlobMatch(rule, expr) - case op.Equal(internalMemberRef) && len(expr.Operands()) == 2: + case op.Equal(Interned.Refs.Member) && len(expr.Operands()) == 2: // NOTE(sr): Again, 3 operands means captured output (like above). i.updateMember(rule, expr, values) + + case op.Equal(Interned.Refs.StartsWith) && len(expr.Operands()) == 2: + // As with equal() above: a third operand captures the result, and a + // rule producing `false` still has to be evaluated. + i.updateAffix(rule, expr, values, affixPrefix) + + case op.Equal(Interned.Refs.AnyPrefixMatch) && len(expr.Operands()) == 2: + i.updateAnyAffixMatch(rule, expr, values, affixPrefix) + + case op.Equal(Interned.Refs.EndsWith) && len(expr.Operands()) == 2: + i.updateAffix(rule, expr, values, affixSuffix) + + case op.Equal(Interned.Refs.AnySuffixMatch) && len(expr.Operands()) == 2: + i.updateAnyAffixMatch(rule, expr, values, affixSuffix) + } +} + +// updateLogicalAnd folds both operands of a conjunction into the rule's +// indices: `lhs and rhs` only succeeds if both operands do, so whatever either +// operand requires of the input, the rule requires. +// +// Each operand is indexed against the indices the rule has so far, not against +// what its sibling contributes: operand bodies are separate scopes, so the same +// var in each is a different var, and resolveVarToRef must not connect them. +func (i *refindices) updateLogicalAnd(rule *Rule, and *LogicalAnd, values map[Var]Value) { + lhs := i.operandAlternatives(rule, and.Lhs, values) + rhs := i.operandAlternatives(rule, and.Rhs, values) + + i.require(rule, lhs) + i.require(rule, rhs) +} + +// require records that the rule is only defined if one of the alternatives +// holds. A lone alternative is unconditional, so its indices join the rule's +// own; several are kept apart for Build to turn into separate paths. +func (i *refindices) require(rule *Rule, alts alternatives) { + switch len(alts) { + case 0: + return + case 1: + for _, ri := range alts[0] { + i.insert(rule, ri) + } + default: + for _, alt := range alts { + for _, ri := range alt { + i.count(ri.ref) + } + } + if i.disjunctions == nil { + i.disjunctions = map[*Rule][]alternatives{} + } + i.disjunctions[rule] = append(i.disjunctions[rule], alts) + } +} + +// updateLogicalOr records the operands of a disjunction as alternative ways to +// reach the rule, `lhs or rhs` holding if either operand does. An operand +// nothing can be indexed on could be satisfied by any input at all, which +// leaves the disjunction saying nothing about the rule. +func (i *refindices) updateLogicalOr(rule *Rule, or *LogicalOr, values map[Var]Value) { + lhs := i.operandAlternatives(rule, or.Lhs, values) + if len(lhs) == 0 { + return + } + + rhs := i.operandAlternatives(rule, or.Rhs, values) + if len(rhs) == 0 { + return + } + + i.require(rule, slices.Concat(lhs, rhs)) +} + +// operandAlternatives returns the ways the body of an `and`/`or` operand can be +// satisfied; an operand with an `or` of its own has one per branch, and none at +// all means nothing about it could be indexed. It is indexed into a scratch, so +// that what it requires reaches the rule only through require(). +func (i *refindices) operandAlternatives(rule *Rule, body Body, values map[Var]Value) alternatives { + scratch := newrefindices(i.isVirtual, i.table) + scratch.outer = append(slices.Clone(i.rules[rule]), i.outer...) + scratch.updateOperand(rule, body, values) + + alts := scratch.paths(rule) + if len(alts) == 1 && len(alts[0]) == 0 { + return nil + } + + for _, alt := range alts { + for pos, ri := range alt { + // The var is scoped to the operand body and must not become + // resolvable from the outside (see resolveVarToRef); that the ref + // has to be defined still holds. + if ri.isVar() { + alt[pos] = &refindex{ref: ri.ref, Value: anyValue, Mapper: ri.Mapper} + } + } + } + + return alts +} + +// paths returns every set of indices that can lead to the rule: the ones that +// always hold, combined with one branch from each disjunction. Past +// maxIndexPaths the disjunctions are dropped -- fewer constraints only widen +// what the index admits, so the result stays correct. +func (i *refindices) paths(rule *Rule) alternatives { + unconditional := i.rules[rule] + paths := alternatives{unconditional} + + for _, alts := range i.disjunctions[rule] { + if len(paths)*len(alts) > maxIndexPaths { + return alternatives{unconditional} + } + + combined := make(alternatives, 0, len(paths)*len(alts)) + for _, path := range paths { + for _, alt := range alts { + combined = append(combined, append(slices.Clone(path), alt...)) + } + } + paths = combined + } + + return paths +} + +// updateOperand folds the expressions of an `and`/`or` operand body into the +// rule's indices. An operand body is a closed scope -- bindings made inside it +// reach neither the enclosing body nor the sibling operand (see +// evalLogicalOperand in topdown) -- so its constants are copied in and dropped +// on return. +func (i *refindices) updateOperand(rule *Rule, body Body, values map[Var]Value) { + scoped := make(map[Var]Value, len(values)) + maps.Copy(scoped, values) + + for _, expr := range body { + i.Update(rule, expr, scoped) } } @@ -386,41 +1025,98 @@ func (i *refindices) isValidIndexRef(ref Ref) bool { !i.isVirtual(ref) } -// Sorted returns a sorted list of references that the indices were built from. -// References that appear more frequently in the indexed rules are ordered -// before less frequently appearing references. -func (i *refindices) Sorted() []Ref { - if i.sorted == nil { - i.sorted = util.SortedFunc(i.frequency.Keys(), func(a, b Ref) int { - countsA, _ := i.frequency.Get(a) - countsB, _ := i.frequency.Get(b) - if countsA < countsB { // descending, we want highest-freq first - return 1 - } else if countsA > countsB { - return -1 - } - return a[0].Loc().Compare(b[0].Loc()) - }) +// Sorted returns the references the indices were built from, ordered so that +// the ones appearing in more of the indexed rules come first. +func (i *refindices) Sorted() []refID { + if i.sorted != nil { + return i.sorted } + + for id, stats := range i.stats { + if stats.count > 0 { + i.sorted = append(i.sorted, refID(id)) + } + } + + slices.SortFunc(i.sorted, func(a, b refID) int { + // A ref reached by several values is worth less as an early level, + // and one that ends the rule's path less again, however often + // either was recorded -- so both outrank frequency. + if c := cmp.Compare(i.stats[a].alternated, i.stats[b].alternated); c != 0 { + return c + } + if c := cmp.Compare(i.stats[b].count, i.stats[a].count); c != 0 { // descending + return c + } + if c := i.table.ref(a)[0].Loc().Compare(i.table.ref(b)[0].Loc()); c != 0 { + return c + } + // Refs built rather than parsed -- a function's args[n] -- share a + // location, so fall back on the order they were first seen in. + return cmp.Compare(a, b) + }) + return i.sorted } -func (i *refindices) Indexed(rule *Rule) bool { - return len(i.rules[rule]) > 0 -} - -func (i *refindices) Value(rule *Rule, ref Ref) Value { - if index := i.index(rule, ref); index != nil { - return index.Value +// partition splits sorted into the refs that become trie levels and the refs that +// do not, which is those no rule constrains to a value. +// +// A ref every rule records only as "holds something" -- which is what +// RewriteDynamicTerms leaves behind when it hoists a term into a local, +// `__local1__ = data.groups.g0.members` -- gives the trie a level whose only +// children are "anything" and "absent". It cannot narrow a lookup by value. What +// it can do is exclude the rules that read the ref when the ref is absent, since +// traversal stops at a level that resolves to nothing, and for a naked reference +// -- `allow if input.x` -- that is the whole of the indexing. +// +// Keeping it as a level is an expensive way to ask that question. Both children +// carry their own copy of the levels below, so the levels multiply out, and each +// lookup resolves a ref per copy. One such level per rule makes traversal +// quadratic: +// +// allow if { input.subject in data.groups.g0.members; input.resource.foo == "A" } +// allow if { input.subject in data.groups.g1.members; input.resource.foo == "A" } +// ... +// +// 500 of those resolve 125k refs on every lookup -- N(N+1)/2 -- to exclude nothing, +// because input.resource.foo is what discriminates. The index costs more than it +// saves there: the same policy evaluates 6.5x faster with indexing disabled, and +// 8.8x at a thousand rules. +// +// So the question moves out of the trie: Lookup checks these refs against the +// candidates traversal produced, which asks it once per surviving rule instead of +// once per copy of the level. The candidates are the same either way. +func (i *refindices) partition(sorted []refID) (levels, unvalued []refID) { + // An `or` records its operands' indices on disjunctions rather than through + // insert, so collect from both rather than counting as they arrive. + valued := make([]bool, len(i.stats)) + note := func(path []*refindex) { + for _, ri := range path { + if !ri.isVar() { + valued[ri.ref] = true + } + } } - return nil -} - -func (i *refindices) Mapper(rule *Rule, ref Ref) *valueMapper { - if index := i.index(rule, ref); index != nil { - return index.Mapper + for _, path := range i.rules { + note(path) } - return nil + for _, alts := range i.disjunctions { + for _, alt := range alts { + for _, path := range alt { + note(path) + } + } + } + + for _, ref := range sorted { + if valued[ref] { + levels = append(levels, ref) + } else { + unvalued = append(unvalued, ref) + } + } + return levels, unvalued } func (i *refindices) updateEq(rule *Rule, a, b Value, constants map[Var]Value) { @@ -441,6 +1137,11 @@ func (i *refindices) tryIndexWildcardRef(rule *Rule, a, b Value, constants map[V return false } + ref = i.resolveRefHead(rule, rule.Head.Args, ref) + if ref == nil { + return false + } + groundPrefix := ref.GroundPrefix() if len(groundPrefix) != len(ref)-1 || !i.isValidIndexRef(groundPrefix) { return false @@ -461,7 +1162,7 @@ func (i *refindices) tryIndexWildcardRef(rule *Rule, a, b Value, constants map[V return false } - i.insert(rule, &refindex{Ref: groundPrefix, Value: resolvedValue}) + i.insert(rule, &refindex{ref: i.table.intern(groundPrefix), Value: resolvedValue}) return true } @@ -479,9 +1180,9 @@ func (i *refindices) updateGlobMatch(rule *Rule, expr *Expr) { // variable earlier in the query OR a function argument variable. match := expr.Operand(2) if v, ok := match.Value.(Var); ok { - if ref := resolveVarToRef(i.rules[rule], args, v); ref != nil { + if ref := i.resolveVarToRef(i.resolvable(rule), args, v); ref != nil { i.insert(rule, &refindex{ - Ref: ref, + ref: i.table.intern(ref), Value: arr.Value, Mapper: &valueMapper{ Key: delim, @@ -502,7 +1203,7 @@ func (i *refindices) updateMember(rule *Rule, expr *Expr, constants map[Var]Valu lhs, rhs := expr.Operand(0), expr.Operand(1) lvar, ok := lhs.Value.(Var) if ok { - lref := resolveVarToRef(i.rules[rule], rule.Head.Args, lvar) + lref := i.resolveVarToRef(i.resolvable(rule), rule.Head.Args, lvar) if lref != nil { i.updateMemberRefInValue(rule, lref, rhs, constants) // `ref in value` return @@ -528,7 +1229,7 @@ func (i *refindices) updateMemberValueInRef(rule *Rule, args []*Term, lval Value return } - i.insert(rule, &refindex{Ref: rref, Value: lval}) + i.insert(rule, &refindex{ref: i.table.intern(rref), Value: lval}) } func (i *refindices) updateMemberRefInValue(rule *Rule, ref Ref, rhs *Term, constants map[Var]Value) { @@ -536,24 +1237,175 @@ func (i *refindices) updateMemberRefInValue(rule *Rule, ref Ref, rhs *Term, cons if rvar, ok := rval.(Var); ok { // rhs is var, try to resolve if resolved, ok := constants[rvar]; ok { rval = resolved + } else if cref := i.resolveVarToRef(i.resolvable(rule), rule.Head.Args, rvar); cref != nil { + // The collection is behind a reference the compiler hoisted into a + // local: `__local0__ = data.groups.g1.members` ahead of the call. + rval = cref } } - addRef := func(t *Term) error { - i.insert(rule, &refindex{Ref: ref, Value: t.Value}) - return nil + var ( + forEach func(func(*Term)) + n int + ) + + // `input.subject in data.groups.g1.members` asks for the collection's + // *values*, which base data -- an object or an array, never a set -- answers + // only by being walked. updateCollectionKey has the question it does answer. + if _, ok := rval.(Ref); ok { + return } switch rcol := rval.(type) { case *Array: - _ = rcol.Iter(addRef) + forEach, n = rcol.Foreach, rcol.Len() case Set: - _ = rcol.Iter(addRef) + forEach, n = rcol.Foreach, rcol.Len() case Object: - _ = rcol.Iter(func(_, v *Term) error { - return addRef(v) - }) + n = rcol.Len() + // Function literal does not escape / allocate + if o, ok := rcol.(*object); ok { + forEach = func(f func(*Term)) { + for _, node := range o.sortedKeys() { + f(node.value) + } + } + // Function literal escapes + } else { + forEach = func(f func(*Term)) { + rcol.Foreach(func(_, v *Term) { f(v) }) + } + } + default: + return } + + members := make([]Value, 0, n) + forEach(func(t *Term) { + members = append(members, t.Value) + }) + + i.insertMembers(rule, ref, members) +} + +// insertMembers records the members of an `in` collection, each a value the +// rule may reach ref by, hoisting insert's scan out of the loop. insertAffixes +// is the same for base strings; the two dedup on different key types. +func (i *refindices) insertMembers(rule *Rule, ref Ref, members []Value) { + id := i.table.intern(ref) + + if len(members) < 2 { + for _, member := range members { + i.insert(rule, &refindex{ref: id, Value: member}) + } + return + } + + // Unlike a prefix, a concrete member takes the place of a "reference is + // anything" entry (see insert), so the first one goes the ordinary way -- + // the rule's list is short at that point, so the scan it costs is cheap. + i.insert(rule, &refindex{ref: id, Value: members[0]}) + + // insert is the only one that may put a value somewhere other than the end + // of the list, which is what a var needs, so those go in through it and are + // left out of the block below. A collection holding one is rare, and paying + // a copy for it keeps the common case a single pass. + rest := members[1:] + if slices.ContainsFunc(rest, valueIsVar) { + for _, member := range rest { + if valueIsVar(member) { + i.insert(rule, &refindex{ref: id, Value: member}) + } + } + rest = slices.DeleteFunc(slices.Clone(rest), valueIsVar) + } + + concrete := 0 + seen := util.NewHasherMap[Value, struct{}](ValueEqual) + + for _, other := range i.rules[rule] { + if other.ref != id { + continue + } + if !other.isVar() { + concrete++ + } + if other.Affix == affixNone { + seen.Put(other.Value, struct{}{}) + } + } + + // One refindex per member, laid down in a single block rather than + // allocated one at a time, as in insertAffixes. Duplicates leave slack at + // the end of the block, which the reslice drops. + pos := len(i.rules[rule]) + indices := util.GrowPtrSlice(i.rules[rule], len(rest)) + + for _, member := range rest { + if _, ok := seen.Get(member); ok { + continue + } + seen.Put(member, struct{}{}) + concrete++ + + *indices[pos] = refindex{ref: id, Value: member} + pos++ + } + i.rules[rule] = indices[:pos] + + i.countN(id, len(rest)) + + if concrete > 1 { + i.alternate(id, alternationConverging) + } +} + +// updateCollectionKey records `[]` -- a reference whose ground +// prefix names a collection in base data and whose last element is the value +// being looked up in it. Reports whether it did. +func (i *refindices) updateCollectionKey(rule *Rule, ref Ref) bool { + if len(ref) < 2 || !ref[0].Equal(DefaultRootDocument) { + return false + } + + prefix := ref[:len(ref)-1] + if !prefix.IsGround() || i.isVirtual(prefix) { + return false + } + + keyRef := i.keyRefOf(rule, ref[len(ref)-1]) + if keyRef == nil || i.isVirtual(keyRef) { + return false + } + + i.recordMembership(rule, keyRef, prefix) + return true +} + +// keyRefOf resolves the term a collection is keyed by to the reference it stands +// for: `input.subject` directly, or the local a rule bound it to. +func (i *refindices) keyRefOf(rule *Rule, term *Term) Ref { + switch v := term.Value.(type) { + case Ref: + if v.IsGround() && !i.isVirtual(v) { + return v + } + case Var: + return i.resolveVarToRef(i.resolvable(rule), rule.Head.Args, v) + } + return nil +} + +// recordMembership notes that rule only matches when the value at key is a key +// of the collection at collection, and that both take part in indexing it. +func (i *refindices) recordMembership(rule *Rule, key, collection Ref) { + for _, m := range i.memberships[rule] { + if RefEqual(m.key, key) && RefEqual(m.collection, collection) { + return + } + } + i.memberships[rule] = append(i.memberships[rule], membership{key: key, collection: collection}) + i.count(i.table.intern(key)) } func (i *refindices) resolveAndValidateRef(rule *Rule, args []*Term, term *Term) Ref { @@ -562,7 +1414,7 @@ func (i *refindices) resolveAndValidateRef(rule *Rule, args []*Term, term *Term) case Ref: ref = v case Var: - ref = resolveVarToRef(i.rules[rule], args, v) + ref = i.resolveVarToRef(i.resolvable(rule), args, v) default: return nil } @@ -574,6 +1426,28 @@ func (i *refindices) resolveAndValidateRef(rule *Rule, args []*Term, term *Term) return ref } +// resolveRefHead resolves a ref rooted at a local variable -- what +// +// x := input +// x.foo == "bar" +// +// gets compiled to -- into the ref that local aliases, splicing the remainder of +// the ref onto it: `input.foo`. Refs that are already rooted at a root document +// are returned unchanged; a head that does not resolve yields nil. +func (i *refindices) resolveRefHead(rule *Rule, args []*Term, ref Ref) Ref { + head, isVar := ref[0].Value.(Var) + if !isVar || RootDocumentNames.Contains(ref[0]) { + return ref + } + + resolved := i.resolveVarToRef(i.resolvable(rule), args, head) + if resolved == nil { + return nil + } + + return resolved.Concat(ref[1:]) +} + // resolveVarToRef checks the previously prepared `*refindex` slice for // occurrences of the var `v`. Since we store `ref = var` expressions for // "any" lookups (i.e. "return the rule if ref is anything"), we can @@ -595,10 +1469,10 @@ func (i *refindices) resolveAndValidateRef(rule *Rule, args []*Term, term *Term) // // // as we're not capturing `var = var` expressions in the index. -func resolveVarToRef(ri []*refindex, args []*Term, v Var) Ref { +func (i *refindices) resolveVarToRef(ri []*refindex, args []*Term, v Var) Ref { for _, other := range ri { if v.Equal(other.Value) { - return other.Ref + return i.table.ref(other.ref) } } for j, arg := range args { @@ -610,46 +1484,198 @@ func resolveVarToRef(ri []*refindex, args []*Term, v Var) Ref { return nil } -func (i *refindices) insert(rule *Rule, index *refindex) { - count, _ := i.frequency.Get(index.Ref) - i.frequency.Put(index.Ref, count+1) +// resolvable returns the indices a var here can be resolved against: the rule's +// own, plus those of any scope enclosing an operand body. +func (i *refindices) resolvable(rule *Rule) []*refindex { + if len(i.outer) == 0 { + return i.rules[rule] + } + return append(slices.Clone(i.rules[rule]), i.outer...) +} - _, indexValueIsVar := index.Value.(Var) +// count records that ref took part in indexing a rule, which is what orders the +// trie levels (see Sorted). +func (i *refindices) count(ref refID) { + i.countN(ref, 1) +} + +func (i *refindices) countN(ref refID, n int) { + i.stat(ref).count += int32(n) +} + +// stat returns the reference's statistics, making room for them if this is the +// first thing recorded about it. +func (i *refindices) stat(ref refID) *refStats { + i.stats = growTo(i.stats, int(ref)+1) + return &i.stats[ref] +} + +// alternation is what a ref reached by several values costs the rest of the +// rule's path, and what Sorted ranks such refs by. +type alternation uint8 + +const ( + // alternationNone: no rule reaches the ref by more than one value. + alternationNone alternation = iota + + // alternationConverging: the alternatives meet again on one node, so the + // path continues from there. Still ranked after the plain refs, since the + // rule gets a node of its own and stops sharing what is below. + alternationConverging + + // alternationTerminal: the alternatives cannot meet again, so the rule + // hangs off each and whatever it constrains below goes unindexed. Affixes + // are these -- a prefix trie cannot point several leaves at one node. + alternationTerminal +) + +// alternate records that a rule reaches ref by more than one value, and what +// that costs. The worse kind recorded for a ref wins. Only the values +// surviving insertPath's var-stripping count. +func (i *refindices) alternate(ref refID, kind alternation) { + if kind == alternationNone { + return + } + + stats := i.stat(ref) + stats.alternated = max(stats.alternated, kind) +} + +func (i *refindices) insert(rule *Rule, index *refindex) { + indexValueIsVar := index.isVar() + seen := false for pos, other := range i.rules[rule] { - if other.Ref.Equal(index.Ref) { - if ValueEqual(other.Value, index.Value) { + if other.ref == index.ref { + seen = true + if other.Affix == index.Affix && ValueEqual(other.Value, index.Value) { return } - _, otherValueIsVar := other.Value.(Var) - if !indexValueIsVar && otherValueIsVar { + otherValueIsVar := other.isVar() + // An affix constraint does not take the place of the "ref is + // anything" entry the way a concrete value does: that entry is what + // lets a later expression resolve the same local back to this ref + // (see resolveVarToRef), and insertPath drops it anyway once the + // ref has a concrete value on the path. + if !indexValueIsVar && index.Affix == affixNone && otherValueIsVar { i.rules[rule][pos] = index return } + if !indexValueIsVar && !otherValueIsVar { + // insertPath cannot converge a level that any affix reaches, + // so one on either side makes this pair a terminal one. + kind := alternationConverging + if index.Affix != affixNone || other.Affix != affixNone { + kind = alternationTerminal + } + i.alternate(index.ref, kind) + } } } + if !seen { + i.count(index.ref) + } i.rules[rule] = append(i.rules[rule], index) } -func (i *refindices) index(rule *Rule, ref Ref) *refindex { - for _, index := range i.rules[rule] { - if index.Ref.Equal(ref) { - return index - } - } - return nil -} - type trieWalker interface { Do(any) trieWalker } +// trieTraversalResult is what a walk of the trie -- a lookup, or the whole of it +// for AllRules -- collects. +// +// The rules reached are a bitset over the index's rule ids, so reaching one down +// several paths costs nothing to notice: the second arrival writes a bit that is +// already set. Reading it back in id order is reading it grouped and in priority +// order, ids having been handed out that way, so there is nothing left to sort. type trieTraversalResult struct { - unordered map[int][]*ruleNode - ordering []int - exist *Term - multiple bool + hits []uint64 + // touched holds the words of hits that were written to, so that clearing + // costs what a lookup found rather than what the index holds. + touched []int32 + exist *Term + multiple bool +} + +// defined reports whether every ref the rule needs resolves to something. A ref +// that is unknown rather than absent cannot exclude it, the same way traversal +// keeps everything below a level it cannot resolve (see traverseUnknown). +func (i *baseDocEqIndex) defined(resolver ValueResolver, id int32, cache *resolveCache) (bool, error) { + for _, pos := range i.required[id] { + defined, err := cache.defined(resolver, i.requiredRefs, pos) + if err != nil { + return false, err + } + if !defined { + return false, nil + } + } + + return true, nil +} + +// resolveCache memoizes, for the length of one lookup, what the resolver answered. +// The refs kept out of the trie are asked for once each however many rules read +// them, and the collections of a ruleset sit under a shared prefix. +// +// Where topdown's baseCache holds what the store gave, making a resolve cheap, this +// skips making the call. +type resolveCache struct { + // required is one entry per ref in the index's requiredRefs, so a memo is + // read at a position rather than searched for. It used to be one entry per + // ref asked about, found by scanning: refs no rule constrains to a value are + // usually the same few, but a rule reading one of its own gives a lookup as + // many distinct ones as there are candidates, and the scan then costs a + // comparison per pair of them. + required []resolved + // keyRef and prefix are the last key and the last collection container asked + // for: a ruleset's rules test the same field, and their collections sit side + // by side under one prefix. + keyRef Ref + keyVal Value + keyOK bool + prefix Ref + prefixVal Value + prefixOK bool +} + +// resolved is a memo entry: unasked until a lookup asks, and then one of the two +// answers. Zero has to mean unasked, so that the buffer needs no initialising +// beyond being allocated. +type resolved uint8 + +const ( + unasked resolved = iota + isDefined + isAbsent +) + +func (c *resolveCache) defined(resolver ValueResolver, refs []Ref, pos int32) (bool, error) { + if c.required == nil { + c.required = make([]resolved, len(refs)) + } + if r := c.required[pos]; r != unasked { + return r == isDefined, nil + } + + v, err := resolver.Resolve(refs[pos]) + if err != nil { + if !IsUnknownValueErr(err) { + return false, err + } + // Unknown rather than absent cannot exclude the rule, the same way + // traversal keeps everything below a level it cannot resolve. + v = Boolean(true) + } + + if v != nil { + c.required[pos] = isDefined + return true, nil + } + c.required[pos] = isAbsent + return false, nil } var ttrPool = &sync.Pool{ @@ -659,20 +1685,34 @@ var ttrPool = &sync.Pool{ } func newTrieTraversalResult() *trieTraversalResult { - return &trieTraversalResult{ - unordered: make(map[int][]*ruleNode, 16), + return &trieTraversalResult{} +} + +// grow makes room for an index holding n rules. The pool never sizes back down, +// which is a byte per eight rules against an index costing hundreds per rule. +func (tr *trieTraversalResult) grow(n int) { + tr.hits = growTo(tr.hits, (n+63)/64) +} + +func (tr *trieTraversalResult) reset() { + for _, w := range tr.touched { + tr.hits[w] = 0 } + tr.touched = tr.touched[:0] + tr.multiple = false + tr.exist = nil } func (tr *trieTraversalResult) Add(t *trieNode) { - for _, node := range t.rules { - root := node.prio[0] - if nodes, ok := tr.unordered[root]; !ok || len(nodes) == 0 { - tr.ordering = append(tr.ordering, root) - tr.unordered[root] = append(nodes, node) - } else if !slices.ContainsFunc(nodes, node.prioEqual) { - tr.unordered[root] = append(nodes, node) + for _, id := range t.rules { + word, bit := id>>6, uint64(1)<<(uint(id)&63) + if tr.hits[word]&bit != 0 { + continue } + if tr.hits[word] == 0 { + tr.touched = append(tr.touched, word) + } + tr.hits[word] |= bit } if t.multiple { tr.multiple = true @@ -688,20 +1728,17 @@ func (tr *trieTraversalResult) Add(t *trieNode) { } type trieNode struct { - ref Ref - mappers []*valueMapper - next *trieNode - any *trieNode - undefined *trieNode - scalars *util.HasherMap[Value, *trieNode] - array *trieNode - rules []*ruleNode - value *Term - multiple bool + // next is the level below this node, nil where the paths under it end. + next *levelDetail + // rules are the ids of the rules whose path ends here, see + // baseDocEqIndex.rules. + rules []int32 + value *Term + multiple bool } -func (node *trieNode) append(prio [2]int, rule *Rule) { - node.rules = append(node.rules, &ruleNode{prio, rule}) +func (node *trieNode) append(id int32, rule *Rule) { + node.rules = append(node.rules, id) if node.value != nil && rule.Head.Value != nil && !node.value.Equal(rule.Head.Value) { node.multiple = true @@ -712,23 +1749,103 @@ func (node *trieNode) append(prio [2]int, rule *Rule) { } } -type ruleNode struct { - prio [2]int - rule *Rule +// levelDetail is everything a trieNode has by virtue of being a *level* -- the +// reference it resolves, the children it dispatches the resolved value to, and +// the constraints that are not exact values. The suffix trie holds its base +// strings reversed, so that requiring one at the end of a value is requiring it +// at the start of the value reversed and the same trie answers both (see +// traverseSuffix). +// +// It is held behind one pointer because a trieNode is allocated per indexed +// value and almost none of them are levels: half a million prefixes make one +// level and half a million nodes that only carry rules. Measured over such an +// index, every field here is set on 0 or 1 of the 500002 nodes. +// +// Where the boundaries fall decides how much that is worth. Inline, these +// fields put trieNode in Go's 160-byte size class; out of line it is 56 bytes, +// which rounds to 64. Moving them out a few at a time buys nothing -- 136 and +// 112 bytes both round up to a class the struct already occupied. +// +// The same reasoning applies once more within levelDetail: alternatives is set +// on the few levels some rule reaches by more than one value, so it costs 8 +// bytes here rather than the 32 its two fields would inline. +type levelDetail struct { + ref Ref + any *trieNode + undefined *trieNode + array *arrayTrie + scalars *util.HasherMap[Value, *trieNode] + mappers []*valueMapper + prefixes *prefixTrie + suffixes *prefixTrie + alternatives *alternativeChildren } -func (a *ruleNode) prio1Cmp(b *ruleNode) int { - return a.prio[1] - b.prio[1] -} - -func (a *ruleNode) prioEqual(b *ruleNode) bool { - return a.prio == b.prio +// alternativeChildren are the nodes that rules reaching a level by several +// values continue from. The two fields hold the same nodes for two different +// jobs, and neither does the other's: +// +// members answers "which nodes does this value reach", which is what a lookup +// asks. A node is in it under every one of the values that reaches it, so a +// rule with a thousand-member collection puts its one node under a thousand +// keys, and several rules sharing a value put several nodes under that one. +// +// converged answers "which nodes are below this level", which is what the +// walks over the whole trie ask -- traverseUnknown, Do and compact. Reading +// that off members would visit a node once per value that reaches it: correct, +// since trieTraversalResult.Add folds a rule reached twice into one, but a +// thousand times the work for the collection above. So the nodes are listed +// once each here as they are created. +type alternativeChildren struct { + members *util.HasherMap[Value, []*trieNode] + converged []*trieNode } func newTrieNodeImpl() *trieNode { return &trieNode{} } +// level returns the level below node, creating it if this is the first rule to +// be discriminated there. +func (node *trieNode) level() *levelDetail { + node.next = util.Or(node.next, newLevelDetail) + return node.next +} + +func (d *levelDetail) converged() []*trieNode { + if d != nil && d.alternatives != nil { + return d.alternatives.converged + } + return nil +} + +// affixTrie returns the trie for one end of the value, creating it and the +// detail that holds it on first use. +func (d *levelDetail) affixTrie(a affix) *prefixTrie { + detail := d + + switch a { + case affixSuffix: + detail.suffixes = util.Or(detail.suffixes, newPrefixTrie) + return detail.suffixes + default: + detail.prefixes = util.Or(detail.prefixes, newPrefixTrie) + return detail.prefixes + } +} + +func newLevelDetail() *levelDetail { + return &levelDetail{} +} + +func newScalarChildren() *util.HasherMap[Value, *trieNode] { + return util.NewHasherMap[Value, *trieNode](ValueEqual) +} + +func newPrefixTrie() *prefixTrie { + return &prefixTrie{} +} + func (node *trieNode) Do(walker trieWalker) { if node == nil { return @@ -738,29 +1855,106 @@ func (node *trieNode) Do(walker trieWalker) { return } - node.any.Do(next) - node.undefined.Do(next) + node.next.do(next) +} - node.scalars.Iter(func(_ Value, child *trieNode) bool { - child.Do(next) +func (d *levelDetail) do(walker trieWalker) { + if d == nil { + return + } + + d.any.Do(walker) + d.undefined.Do(walker) + + d.scalars.Iter(func(_ Value, child *trieNode) bool { + child.Do(walker) return false }) - node.array.Do(next) - node.next.Do(next) + for _, child := range d.converged() { + child.Do(walker) + } + + d.prefixes.do(walker) + d.suffixes.do(walker) + d.array.do(walker) +} + +// compact walks the trie once the index is built and releases what its slices +// grew but do not use. +func (node *trieNode) compact() { + if node == nil { + return + } + + node.next.compact() +} + +func (d *levelDetail) compact() { + if d == nil { + return + } + + d.prefixes.compact() + d.suffixes.compact() + + d.any.compact() + d.undefined.compact() + d.array.compact() + + for _, child := range d.converged() { + child.compact() + } + + d.scalars.Iter(func(_ Value, child *trieNode) bool { + child.compact() + return false + }) + + if d.alternatives != nil { + d.alternatives.converged = slices.Clip(d.alternatives.converged) + } +} + +// insertAlternatives adds a level a rule reaches by any one of several values, +// and returns the one node the rest of its path continues from. Every value +// keys to that node, so what the rule constrains below is built once instead of +// repeated under each alternative. +func (node *trieNode) insertAlternatives(ref Ref, values []*refindex) *trieNode { + level := node.level() + level.ref = ref + level.alternatives = util.Or(level.alternatives, newAlternativeChildren) + alt := level.alternatives + + converge := newTrieNodeImpl() + alt.converged = append(alt.converged, converge) + + for _, val := range values { + if val.Mapper != nil { + level.addMapper(val.Mapper) + } + nodes, _ := alt.members.Get(val.Value) + alt.members.Put(val.Value, append(nodes, converge)) + } + + return converge +} + +func newAlternativeChildren() *alternativeChildren { + return &alternativeChildren{ + members: util.NewHasherMap[Value, []*trieNode](ValueEqual), + } } func (node *trieNode) Insert(ref Ref, value Value, mapper *valueMapper) *trieNode { - if node.next == nil { - node.next = newTrieNodeImpl() - node.next.ref = ref - } + level := node.level() + level.ref = ref if mapper != nil { - node.next.addMapper(mapper) + level.addMapper(mapper) } - return node.next.insertValue(value) + return level.insertValue(value) } func (node *trieNode) Traverse(resolver ValueResolver, tr *trieTraversalResult) error { @@ -773,36 +1967,37 @@ func (node *trieNode) Traverse(resolver ValueResolver, tr *trieTraversalResult) return node.next.traverse(resolver, tr) } -func (node *trieNode) addMapper(mapper *valueMapper) { - for i := range node.mappers { - if node.mappers[i].Key == mapper.Key { +func (d *levelDetail) addMapper(mapper *valueMapper) { + detail := d + for i := range detail.mappers { + if detail.mappers[i].Key == mapper.Key { return } } - node.mappers = append(node.mappers, mapper) + detail.mappers = append(detail.mappers, mapper) } -func (node *trieNode) insertValue(value Value) *trieNode { +func (d *levelDetail) insertValue(value Value) *trieNode { + detail := d + switch value := value.(type) { case nil: - node.undefined = util.Or(node.undefined, newTrieNodeImpl) - return node.undefined + detail.undefined = util.Or(detail.undefined, newTrieNodeImpl) + return detail.undefined case Var: - node.any = util.Or(node.any, newTrieNodeImpl) - return node.any + detail.any = util.Or(detail.any, newTrieNodeImpl) + return detail.any case Null, Boolean, Number, String: - child, ok := node.scalars.Get(value) + child, ok := detail.scalars.Get(value) if !ok { child = newTrieNodeImpl() - if node.scalars == nil { - node.scalars = util.NewHasherMap[Value, *trieNode](ValueEqual) - } - node.scalars.Put(value, child) + detail.scalars = util.Or(detail.scalars, newScalarChildren) + detail.scalars.Put(value, child) } return child case *Array: - node.array = util.Or(node.array, newTrieNodeImpl) - return node.array.insertArray(value) + detail.array = util.Or(detail.array, newArrayTrie) + return detail.array.insert(value) // `x in ` (see updateMemberRefInValue) inserts each element of // the literal collection as-is, without restricting it to scalars/arrays @@ -813,59 +2008,147 @@ func (node *trieNode) insertValue(value Value) *trieNode { // Call - can't actually reach here: the compiler rewrites them into // separate statements, bound to a Var, before the index is built.) case Object, Set: - node.any = util.Or(node.any, newTrieNodeImpl) - return node.any + detail.any = util.Or(detail.any, newTrieNodeImpl) + return detail.any } panic("illegal value") } -func (node *trieNode) insertArray(arr *Array) *trieNode { +// arrayTrie dispatches on the elements of an array, one node per position. A +// position dispatches the element after it and ends a rule's array, which a +// trieNode cannot hold at once. +type arrayTrie struct { + any *arrayTrie + scalars *util.HasherMap[Value, *arrayTrie] + // end is where a rule whose array ends at this position continues. + end *trieNode +} + +func newArrayTrie() *arrayTrie { + return &arrayTrie{} +} + +func newArrayChildren() *util.HasherMap[Value, *arrayTrie] { + return util.NewHasherMap[Value, *arrayTrie](ValueEqual) +} + +// insert returns the node the rule's path continues from once arr is consumed. +func (a *arrayTrie) insert(arr *Array) *trieNode { if arr.Len() == 0 { - return node + a.end = util.Or(a.end, newTrieNodeImpl) + return a.end } switch head := arr.Elem(0).Value.(type) { - case Var: - node.any = util.Or(node.any, newTrieNodeImpl) - return node.any.insertArray(arr.Slice(1, -1)) case Null, Boolean, Number, String: - child, ok := node.scalars.Get(head) + child, ok := a.scalars.Get(head) if !ok { - child = newTrieNodeImpl() - if node.scalars == nil { - node.scalars = util.NewHasherMap[Value, *trieNode](ValueEqual) - } - node.scalars.Put(head, child) + child = newArrayTrie() + a.scalars = util.Or(a.scalars, newArrayChildren) + a.scalars.Put(head, child) } - return child.insertArray(arr.Slice(1, -1)) + return child.insert(arr.Slice(1, -1)) - // Same reasoning as in insertValue above: an array element can itself be - // a nested array, object, or set, none of which can be indexed precisely - // at this position, so fall back to "any" and keep indexing the - // remaining elements. - case *Array, Object, Set: - node.any = util.Or(node.any, newTrieNodeImpl) - return node.any.insertArray(arr.Slice(1, -1)) + // An element that is itself an array, object or set cannot be indexed + // precisely at this position, so -- as for a var -- it falls back to any, + // and the elements after it go on being indexed. + case Var, *Array, Object, Set: + a.any = util.Or(a.any, newArrayTrie) + return a.any.insert(arr.Slice(1, -1)) } panic("illegal value") } -func (node *trieNode) traverse(resolver ValueResolver, tr *trieTraversalResult) error { - if node == nil { +func (a *arrayTrie) traverse(resolver ValueResolver, tr *trieTraversalResult, arr *Array) error { + if a == nil { return nil } - v, err := resolver.Resolve(node.ref) + if arr.Len() == 0 { + return a.end.Traverse(resolver, tr) + } + + if err := a.any.traverse(resolver, tr, arr.Slice(1, -1)); err != nil { + return err + } + + switch head := arr.Elem(0).Value.(type) { + case Null, Boolean, Number, String: + child, _ := a.scalars.Get(head) + return child.traverse(resolver, tr, arr.Slice(1, -1)) + } + + return nil +} + +func (a *arrayTrie) traverseUnknown(resolver ValueResolver, tr *trieTraversalResult) error { + if a == nil { + return nil + } + + if err := a.end.Traverse(resolver, tr); err != nil { + return err + } + + if err := a.any.traverseUnknown(resolver, tr); err != nil { + return err + } + + var iterErr error + a.scalars.Iter(func(_ Value, child *arrayTrie) bool { + iterErr = child.traverseUnknown(resolver, tr) + return iterErr != nil + }) + + return iterErr +} + +func (a *arrayTrie) do(walker trieWalker) { + if a == nil { + return + } + + a.end.Do(walker) + a.any.do(walker) + a.scalars.Iter(func(_ Value, child *arrayTrie) bool { + child.do(walker) + return false + }) +} + +func (a *arrayTrie) compact() { + if a == nil { + return + } + + a.end.compact() + a.any.compact() + a.scalars.Iter(func(_ Value, child *arrayTrie) bool { + child.compact() + return false + }) +} + +func (d *levelDetail) traverse(resolver ValueResolver, tr *trieTraversalResult) error { + if d == nil { + return nil + } + + v, err := resolver.Resolve(d.ref) if err != nil { if IsUnknownValueErr(err) { - return node.traverseUnknown(resolver, tr) + return d.traverseUnknown(resolver, tr) } return err } - if err = node.undefined.Traverse(resolver, tr); err != nil { + // Which order the branches below are taken in does not decide the order the + // candidates come back in -- gather reads them by id. Only undefined coming + // before the nil return is load-bearing: a ref that resolved to nothing + // admits the rules wanting it undefined and no others. + if err = d.undefined.Traverse(resolver, tr); err != nil { return err } @@ -873,18 +2156,30 @@ func (node *trieNode) traverse(resolver ValueResolver, tr *trieTraversalResult) return nil } - if err = node.any.Traverse(resolver, tr); err != nil { + if err = d.any.Traverse(resolver, tr); err != nil { return err } - if err = node.traverseValue(resolver, tr, v); err != nil { + if err = d.traverseValue(resolver, tr, v); err != nil { return err } - for i := range node.mappers { - mapped := node.mappers[i].MapValue(v) + // Prefix constraints are tested against the value as it is, never against + // what a mapper makes of it: the glob mapper turns a string into the array + // of its segments, and matching prefixes against those segments would + // answer a question no rule asked. + if err = d.traversePrefixes(resolver, tr, v); err != nil { + return err + } + + if err = d.traverseSuffixes(resolver, tr, v); err != nil { + return err + } + + for i := range d.mappers { + mapped := d.mappers[i].MapValue(v) if !ValueEqual(mapped, v) { - if err := node.traverseValue(resolver, tr, mapped); err != nil { + if err := d.traverseValue(resolver, tr, mapped); err != nil { return err } } @@ -893,33 +2188,67 @@ func (node *trieNode) traverse(resolver ValueResolver, tr *trieTraversalResult) return nil } -func (node *trieNode) traverseValue(resolver ValueResolver, tr *trieTraversalResult, value Value) error { +func (d *levelDetail) traverseValue(resolver ValueResolver, tr *trieTraversalResult, value Value) error { switch value := value.(type) { case *Array, Set, Object: - if node.array != nil { + if d.array != nil { if arr, ok := value.(*Array); ok { - if err := node.array.traverseArray(resolver, tr, arr); err != nil { + if err := d.array.traverse(resolver, tr, arr); err != nil { return err } } } - if node.scalars.Len() > 0 { - return node.traverseCollectionMembership(resolver, tr, value) + // Alternatives as well as scalars: a level every rule reaches by + // several values has its children under alternatives and none under + // scalars, and a collection at the reference still has to be tested + // against them. + if d.scalars.Len() > 0 || d.alternatives != nil { + return d.traverseCollectionMembership(resolver, tr, value) } case Null, Boolean, Number, String: - if child, ok := node.scalars.Get(value); ok { - return child.Traverse(resolver, tr) + if child, ok := d.scalars.Get(value); ok { + if err := child.Traverse(resolver, tr); err != nil { + return err + } + } + // A level with no alternatives -- almost all of them -- pays a branch + // and nothing more. + if d.alternatives != nil { + return d.alternatives.traverse(resolver, tr, value) } } return nil } -func (node *trieNode) traverseCollectionMembership(resolver ValueResolver, tr *trieTraversalResult, collection Value) error { +// traverse visits the nodes that the rules reaching this level by value +// continue from. +func (alt *alternativeChildren) traverse(resolver ValueResolver, tr *trieTraversalResult, value Value) error { + nodes, ok := alt.members.Get(value) + if !ok { + return nil + } + + for _, child := range nodes { + if err := child.Traverse(resolver, tr); err != nil { + return err + } + } + + return nil +} + +func (d *levelDetail) traverseCollectionMembership(resolver ValueResolver, tr *trieTraversalResult, collection Value) error { + alt := d.alternatives checkMember := func(t *Term) error { if IsScalar(t.Value) { - child, _ := node.scalars.Get(t.Value) - return child.Traverse(resolver, tr) + child, _ := d.scalars.Get(t.Value) + if err := child.Traverse(resolver, tr); err != nil { + return err + } + if alt != nil { + return alt.traverse(resolver, tr, t.Value) + } } return nil } @@ -930,6 +2259,13 @@ func (node *trieNode) traverseCollectionMembership(resolver ValueResolver, tr *t case Set: return col.Iter(checkMember) case Object: + // Function literal does not escape + if o, ok := col.(*object); ok { + return o.Iter(func(_, v *Term) error { + return checkMember(v) + }) + } + // Function literal escapes return col.Iter(func(_, v *Term) error { return checkMember(v) }) @@ -938,50 +2274,44 @@ func (node *trieNode) traverseCollectionMembership(resolver ValueResolver, tr *t return nil } -func (node *trieNode) traverseArray(resolver ValueResolver, tr *trieTraversalResult, arr *Array) (err error) { - if node == nil { +// traverseUnknown visits every child of a level whose reference the resolver +// cannot answer for. What each child constrains below resolves as usual: an +// unknown at one level says nothing about the levels under it. +func (d *levelDetail) traverseUnknown(resolver ValueResolver, tr *trieTraversalResult) error { + if d == nil { return nil } - if arr.Len() == 0 { - return node.Traverse(resolver, tr) + if err := d.undefined.Traverse(resolver, tr); err != nil { + return err } - if err = node.any.traverseArray(resolver, tr, arr.Slice(1, -1)); err == nil { - switch head := arr.Elem(0).Value.(type) { - case Null, Boolean, Number, String: - child, _ := node.scalars.Get(head) - return child.traverseArray(resolver, tr, arr.Slice(1, -1)) + if err := d.any.Traverse(resolver, tr); err != nil { + return err + } + + if err := d.array.traverseUnknown(resolver, tr); err != nil { + return err + } + + if err := d.prefixes.traverseUnknown(resolver, tr); err != nil { + return err + } + + if err := d.suffixes.traverseUnknown(resolver, tr); err != nil { + return err + } + + for _, child := range d.converged() { + if err := child.Traverse(resolver, tr); err != nil { + return err } } - return err -} - -func (node *trieNode) traverseUnknown(resolver ValueResolver, tr *trieTraversalResult) error { - if node == nil { - return nil - } - - if err := node.Traverse(resolver, tr); err != nil { - return err - } - - if err := node.undefined.traverseUnknown(resolver, tr); err != nil { - return err - } - - if err := node.any.traverseUnknown(resolver, tr); err != nil { - return err - } - - if err := node.array.traverseUnknown(resolver, tr); err != nil { - return err - } - var iterErr error - node.scalars.Iter(func(_ Value, child *trieNode) bool { - return child.traverseUnknown(resolver, tr) != nil + d.scalars.Iter(func(_ Value, child *trieNode) bool { + iterErr = child.Traverse(resolver, tr) + return iterErr != nil }) return iterErr @@ -1003,13 +2333,16 @@ func (i *refindices) eqOperandsToRefAndValue(rule *Rule, args []*Term, a, b Valu if !ok { return false } - if ref := resolveVarToRef(i.rules[rule], args, v); ref != nil { - i.insert(rule, &refindex{Ref: ref, Value: bval}) + if ref := i.resolveVarToRef(i.resolvable(rule), args, v); ref != nil { + i.insert(rule, &refindex{ref: i.table.intern(ref), Value: bval}) return true } case Ref: - if !i.isValidIndexRef(v) { + // A ref rooted at a local -- `x := input; x.foo == "bar"` -- indexes the + // same as the ref that local aliases, so long as the local resolves. + v = i.resolveRefHead(rule, args, v) + if v == nil || !i.isValidIndexRef(v) { return false } @@ -1023,7 +2356,7 @@ func (i *refindices) eqOperandsToRefAndValue(rule *Rule, args []*Term, a, b Valu return false } - i.insert(rule, &refindex{Ref: v, Value: b}) + i.insert(rule, &refindex{ref: i.table.intern(v), Value: b}) return true } return false @@ -1154,3 +2487,41 @@ func skipIndexingOperator(expr *Expr) bool { op := expr.OperatorTerm() return op != nil && skipIndexing.Contains(op) } + +// bodySkipsIndexing reports whether body contains an expression that must not +// be indexed away, either at the top level or inside a nested body. The nested +// bodies matter: a rule holding a `print` call inside an `and`, `or`, `not` or +// `every` body is still a rule whose side effects are lost if the indexer +// excludes it from evaluation. +func bodySkipsIndexing(body Body) bool { + if slices.ContainsFunc(body, skipIndexingOperator) { + return true + } + for _, expr := range body { + if !exprHasNestedBody(expr) { + continue + } + found := false + WalkBodies(expr, func(b Body) bool { + if !found && slices.ContainsFunc(b, skipIndexingOperator) { + found = true + } + return found + }) + if found { + return true + } + } + return false +} + +// exprHasNestedBody is a cheap pre-check for bodySkipsIndexing: only these +// expression shapes hold a body directly, so only these are worth the cost of +// a full walk. +func exprHasNestedBody(expr *Expr) bool { + switch expr.Terms.(type) { + case *Every, *Not, *LogicalAnd, *LogicalOr: + return true + } + return false +} diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/index_affix.go b/vendor/github.com/open-policy-agent/opa/v1/ast/index_affix.go new file mode 100644 index 0000000000..9288428646 --- /dev/null +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/index_affix.go @@ -0,0 +1,594 @@ +// Copyright 2026 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package ast + +import ( + "slices" + + "github.com/open-policy-agent/opa/v1/util" +) + +// This file holds the indexing of both ends of a string: `startswith` and +// `strings.any_prefix_match`, and `endswith` and `strings.any_suffix_match`. +// One structure answers both -- a suffix trie is a prefixTrie over the base +// strings reversed (see InsertSuffix and traverseSuffix) -- so prefixTrie is +// what the file is named after. +// +// prefixTrie holds the string-prefix constraints recorded for one level of the +// rule index: what `startswith(input.x, "/api/")` and +// `strings.any_prefix_match(input.x, [...])` contribute. +// +// A scalar constraint is answered with a map lookup, but a prefix constraint +// has to answer "which of the recorded prefixes does this value start with", +// and the answer is a set, not a single entry. Testing the value against every +// recorded prefix in turn costs O(p) string comparisons per lookup for p +// prefixes -- which is the work strings.any_prefix_match exists to avoid doing +// in the rule body, so doing it in the index instead would be no bargain. +// +// This is a compressed (radix) trie instead: a lookup walks the value once and +// costs O(len(value)) byte comparisons whatever p is. Compressed rather than +// one node per byte because the node count is then bounded by 2p-1 rather than +// by the total length of all prefixes -- 10k prefixes cost thousands of nodes, +// not hundreds of thousands. +type prefixTrie struct { + // edges are sorted by the first byte of their label, which is unique among + // them, so a step down the trie is a binary search. + edges []prefixEdge + // child is where the rules of the prefixes ending exactly here hang off. It + // is an ordinary trieNode, so whatever a rule constrains below a prefix + // constraint indexes as usual. + child *trieNode +} + +type prefixEdge struct { + label string + // node is the trie under this edge; leaf stands in for it when nothing is + // recorded past the edge's label, which is almost every edge. + node *prefixTrie + leaf *trieNode +} + +// edge locates the edge labelled with first byte b, or the position a new one +// would be inserted at to keep edges sorted. Only that byte is matched; labels +// are compressed, so comparing the rest of one is left to the caller. +func (p *prefixTrie) edge(b byte) (int, bool) { + return slices.BinarySearchFunc(p.edges, b, func(e prefixEdge, b byte) int { + return int(e.label[0]) - int(b) + }) +} + +// insert returns the node that the rules constrained by prefix hang off, +// creating it if this is the first time the prefix is recorded. +func (p *prefixTrie) insert(prefix string) *trieNode { + node := p + + for { + if prefix == "" { + if node.child == nil { + node.child = newTrieNodeImpl() + } + return node.child + } + + pos, found := node.edge(prefix[0]) + if !found { + leaf := newTrieNodeImpl() + node.edges = slices.Insert(node.edges, pos, prefixEdge{label: prefix, leaf: leaf}) + return leaf + } + + edge := node.edges[pos] + common := commonPrefixLen(edge.label, prefix) + + switch { + // The two diverge inside this edge -- "/api/v1" meeting "/api/v2" -- + // so the edge is split where they stop agreeing and what used to hang + // off it moves down onto the tail, whichever kind it is. + case common < len(edge.label): + tail := prefixEdge{label: edge.label[common:], node: edge.node, leaf: edge.leaf} + node.edges[pos] = prefixEdge{ + label: edge.label[:common], + node: &prefixTrie{edges: []prefixEdge{tail}}, + } + + // The prefix ends where an edge does with nothing past it, so its + // continuation is already the answer. + case common == len(prefix) && edge.leaf != nil: + return edge.leaf + + // Something is recorded past the edge now, so its continuation becomes + // the child of a trie of its own. + case edge.node == nil: + node.edges[pos] = prefixEdge{ + label: edge.label, + node: &prefixTrie{child: edge.leaf}, + } + } + + node = node.edges[pos].node + prefix = prefix[common:] + } +} + +// traverse visits the continuation of every recorded prefix that s starts with. +// One walk down the trie finds all of them: the prefixes of s that are in the +// trie are exactly the ends-of-prefix passed on the way down. +func (p *prefixTrie) traverse(s string, resolver ValueResolver, tr *trieTraversalResult) error { + for node := p; node != nil; { + if node.child != nil { + if err := node.child.Traverse(resolver, tr); err != nil { + return err + } + } + + if s == "" { + return nil + } + + pos, found := node.edge(s[0]) + if !found { + return nil + } + + edge := node.edges[pos] + if len(edge.label) > len(s) || s[:len(edge.label)] != edge.label { + return nil + } + + s = s[len(edge.label):] + if edge.node == nil { + return edge.leaf.Traverse(resolver, tr) + } + node = edge.node + } + + return nil +} + +// traverseSuffix is traverse over the end of s. A suffix trie holds its base +// strings reversed (see affixTries), so the walk consumes s from its +// last byte back -- which needs no reversed copy of s to be made per lookup. +func (p *prefixTrie) traverseSuffix(s string, resolver ValueResolver, tr *trieTraversalResult) error { + for node := p; node != nil; { + if node.child != nil { + if err := node.child.Traverse(resolver, tr); err != nil { + return err + } + } + + if s == "" { + return nil + } + + pos, found := node.edge(s[len(s)-1]) + if !found { + return nil + } + + edge := node.edges[pos] + if len(edge.label) > len(s) || !equalReversed(s[len(s)-len(edge.label):], edge.label) { + return nil + } + + s = s[:len(s)-len(edge.label)] + if edge.node == nil { + return edge.leaf.Traverse(resolver, tr) + } + node = edge.node + } + + return nil +} + +// equalReversed reports whether tail read backwards is reversed. +func equalReversed(tail, reversed string) bool { + for i := range reversed { + if reversed[i] != tail[len(tail)-1-i] { + return false + } + } + return true +} + +// reverseString returns s with its bytes reversed. A base string is reversed +// once, when it is recorded; `endswith` is a byte comparison, so reversing +// bytes rather than runes is what makes a suffix of s a prefix of reversed s. +func reverseString(s string) string { + b := []byte(s) + slices.Reverse(b) + + // b was made here and is not written to again, so it can be handed over + // rather than copied a second time. + return util.ByteSliceToString(b) +} + +// compact releases the spare capacity in the edge slices. Edges arrive in +// arbitrary order, so they are placed by insertion and grow the way append does +// -- which leaves 60% of the slots unused across a large prefix set -- and +// nothing inserts once the index is built. slices.Clip only caps the capacity; +// releasing the block means copying out of it. +func (p *prefixTrie) compact() { + if p == nil { + return + } + + if cap(p.edges) > len(p.edges) { + exact := make([]prefixEdge, len(p.edges)) + copy(exact, p.edges) + p.edges = exact + } + + p.child.compact() + + for _, edge := range p.edges { + edge.node.compact() + edge.leaf.compact() + } +} + +func (p *prefixTrie) do(walker trieWalker) { + if p == nil { + return + } + + p.child.Do(walker) + + for _, edge := range p.edges { + edge.node.do(walker) + edge.leaf.Do(walker) + } +} + +func (p *prefixTrie) traverseUnknown(resolver ValueResolver, tr *trieTraversalResult) error { + if p == nil { + return nil + } + + if err := p.child.Traverse(resolver, tr); err != nil { + return err + } + + for _, edge := range p.edges { + if err := edge.node.traverseUnknown(resolver, tr); err != nil { + return err + } + if err := edge.leaf.Traverse(resolver, tr); err != nil { + return err + } + } + + return nil +} + +// prefixEntry is a prefix the trie holds, spelled out, with the node its rules +// hang off. +type prefixEntry struct { + prefix string + node *trieNode +} + +// walk returns the prefixes the trie holds, in lexicographic order. Only the +// index's debug rendering and its tests have any use for reading back what the +// compression made of them. +func (p *prefixTrie) walk() []prefixEntry { + if p == nil { + return nil + } + + var ( + entries []prefixEntry + collect func(node *prefixTrie, prefix string) + ) + + collect = func(node *prefixTrie, prefix string) { + if node.child != nil { + entries = append(entries, prefixEntry{prefix: prefix, node: node.child}) + } + for _, edge := range node.edges { + if edge.node == nil { + entries = append(entries, prefixEntry{prefix: prefix + edge.label, node: edge.leaf}) + continue + } + collect(edge.node, prefix+edge.label) + } + } + + collect(p, "") + + return entries +} + +func commonPrefixLen(a, b string) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[i] == b[i] { + i++ + } + return i +} + +// InsertPrefix records that the rules below this node require the value at ref +// to be a string starting with prefix. +func (node *trieNode) InsertPrefix(ref Ref, prefix Value) *trieNode { + level := node.level() + level.ref = ref + + s, ok := prefix.(String) + if !ok { + panic("illegal prefix value") + } + + return level.affixTrie(affixPrefix).insert(string(s)) +} + +// InsertSuffix records that the rules below this node require the value at ref +// to be a string ending with suffix. +func (node *trieNode) InsertSuffix(ref Ref, suffix Value) *trieNode { + level := node.level() + level.ref = ref + + s, ok := suffix.(String) + if !ok { + panic("illegal suffix value") + } + + return level.affixTrie(affixSuffix).insert(reverseString(string(s))) +} + +// traversePrefixes visits the rules whose prefix constraints value satisfies. +// +// strings.any_prefix_match takes a collection of search strings as readily as a +// single one, and holds if any of them starts with any of the base strings, so +// a collection is tested element by element -- the same way a scalar constraint +// is matched against the members of a collection (see +// traverseCollectionMembership). +func (d *levelDetail) traversePrefixes(resolver ValueResolver, tr *trieTraversalResult, value Value) error { + prefixes := d.prefixes + if prefixes == nil { + return nil + } + + if s, ok := value.(String); ok { + return prefixes.traverse(string(s), resolver, tr) + } + + checkMember := func(t *Term) error { + if s, ok := t.Value.(String); ok { + return prefixes.traverse(string(s), resolver, tr) + } + return nil + } + + switch col := value.(type) { + case *Array: + return col.Iter(checkMember) + case Set: + return col.Iter(checkMember) + case Object: + if o, ok := col.(*object); ok { + return o.Iter(func(_, v *Term) error { + return checkMember(v) + }) + } + return col.Iter(func(_, v *Term) error { + return checkMember(v) + }) + } + + return nil +} + +// traverseSuffixes visits the rules whose suffix constraints value satisfies. +// A collection is tested element by element, as for prefixes. +func (d *levelDetail) traverseSuffixes(resolver ValueResolver, tr *trieTraversalResult, value Value) error { + suffixes := d.suffixes + if suffixes == nil { + return nil + } + + if s, ok := value.(String); ok { + return suffixes.traverseSuffix(string(s), resolver, tr) + } + + checkMember := func(t *Term) error { + if s, ok := t.Value.(String); ok { + return suffixes.traverseSuffix(string(s), resolver, tr) + } + return nil + } + + switch col := value.(type) { + case *Array: + return col.Iter(checkMember) + case Set: + return col.Iter(checkMember) + case Object: + if o, ok := col.(*object); ok { + // doesn't allocate / escape + for _, node := range o.sortedKeys() { + if err := checkMember(node.value); err != nil { + return err + } + } + return nil + } + // allocates / escapes + return col.Iter(func(_, v *Term) error { + return checkMember(v) + }) + } + + return nil +} + +// updateStartsWith indexes `startswith(x, "base")`: x has to be a string +// starting with base for the rule to hold. +func (i *refindices) updateAffix(rule *Rule, expr *Expr, constants map[Var]Value, a affix) { + ref := i.resolveAndValidateRef(rule, rule.Head.Args, expr.Operand(0)) + if ref == nil { + return + } + + base, ok := constantString(expr.Operand(1), constants) + if !ok { + return + } + + i.insert(rule, &refindex{ref: i.table.intern(ref), Value: base, Affix: a}) +} + +// updateAnyPrefixMatch indexes `strings.any_prefix_match(x, base)`, which is +// a disjunction of startswith calls: each base string is recorded as an +// alternative prefix for x, the same way each element of `x in [...]` is +// recorded as an alternative value (see updateMemberRefInValue). +// +// The search operand has to be a single ref: a collection of search strings +// would need every element of one collection tested against the other, which +// is not a constraint on the value at any one ref. +func (i *refindices) updateAnyAffixMatch(rule *Rule, expr *Expr, constants map[Var]Value, a affix) { + ref := i.resolveAndValidateRef(rule, rule.Head.Args, expr.Operand(0)) + if ref == nil { + return + } + + base := expr.Operand(1).Value + if v, ok := base.(Var); ok { + resolved, ok := constants[v] + if !ok { + return + } + base = resolved + } + + if s, ok := base.(String); ok { + i.insert(rule, &refindex{ref: i.table.intern(ref), Value: s, Affix: a}) + return + } + + // Every base string has to be recorded, or the index would exclude rules + // the dropped ones would have matched -- so a base that isn't a collection + // of ground strings throughout leaves the rule unindexed rather than + // partly indexed. + bases, ok := groundStrings(base) + if !ok || len(bases) == 0 { + return + } + + i.insertAffixes(rule, ref, bases, a) +} + +// insertAffixes records a whole base collection at once, for either end of the +// value. insert() rescans the rule's indices on every call, which is quadratic +// over the thousands strings.any_prefix_match carries, so the scan happens once +// here instead. insertMembers is the same for `in`; the two dedup on different +// key types. +func (i *refindices) insertAffixes(rule *Rule, ref Ref, bases []Value, a affix) { + id := i.table.intern(ref) + + // concrete counts the values this rule already reaches ref by that survive + // insertPath's var-stripping, so that the alternatives the base adds can be + // weighed against them without a second scan (see refindices.alternate). + concrete := 0 + known := false + seen := make(map[String]struct{}, len(bases)) + for _, other := range i.rules[rule] { + if other.ref != id { + continue + } + known = true + if !other.isVar() { + concrete++ + } + if other.Affix == a { + if s, ok := other.Value.(String); ok { + seen[s] = struct{}{} + } + } + } + n := len(bases) + if known && n > 0 { + n-- + } + i.countN(id, n) + + // One refindex per base, laid down in a single block rather than allocated + // one at a time: a base collection runs to thousands of them. Duplicates + // leave slack at the end of the block, which the reslice below drops. + pos := len(i.rules[rule]) + indices := util.GrowPtrSlice(i.rules[rule], len(bases)) + + for _, base := range bases { + // groundStrings has established that every base is a String, and hands + // the Term's own Value over so that refindex.Value costs no second box. + key := base.(String) + if _, ok := seen[key]; ok { + continue + } + seen[key] = struct{}{} + concrete++ + + *indices[pos] = refindex{ref: id, Value: base, Affix: a} + pos++ + } + i.rules[rule] = indices[:pos] + + if concrete > 1 { + i.alternate(id, alternationTerminal) + } +} + +// constantString resolves term to a string literal, following one level of +// var binding recorded earlier in the rule body. +func constantString(term *Term, constants map[Var]Value) (String, bool) { + v := term.Value + if vr, ok := v.(Var); ok { + resolved, ok := constants[vr] + if !ok { + return "", false + } + v = resolved + } + + s, ok := v.(String) + return s, ok +} + +// groundStrings returns the members of an array or set literal, and reports +// false unless every one of them is a string. The member's own Value is what +// comes back, not the String inside it: every caller puts it straight into a +// refindex, and a Term is already holding it boxed. +func groundStrings(v Value) ([]Value, bool) { + var ( + until func(func(*Term) bool) bool + n int + ) + + switch col := v.(type) { + case *Array: + until, n = col.Until, col.Len() + case Set: + until, n = col.Until, col.Len() + default: + return nil, false + } + + // The base of a strings.any_prefix_match runs to thousands of strings, so + // the length is worth taking off the collection rather than growing into. + out := make([]Value, 0, n) + + // Until stops on the first member that is not a string, and reports having + // stopped -- which is the whole of "unless every one of them is a string". + if until(func(t *Term) bool { + _, ok := t.Value.(String) + if ok { + out = append(out, t.Value) + } + return !ok + }) { + return nil, false + } + + return out, true +} diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/index_debug.go b/vendor/github.com/open-policy-agent/opa/v1/ast/index_debug.go index 88d451b175..18ca875b88 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/index_debug.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/index_debug.go @@ -6,27 +6,30 @@ package ast import ( "fmt" - "sort" + "slices" "strings" + + "github.com/open-policy-agent/opa/v1/util" ) -func (node *trieNode) mermaid() string { +// mermaid renders the trie, naming the rules by their bodies -- which the index +// holds, and the nodes only the ids of. +func (i *baseDocEqIndex) mermaid() string { var sb strings.Builder sb.WriteString("graph TD\n") nodeCounter := 0 nodeIDs := make(map[*trieNode]string) - node.mermaidFormat(&sb, &nodeCounter, nodeIDs, "") + i.root.mermaidFormat(&sb, &nodeCounter, nodeIDs, "", i.rules) return sb.String() } -func (node *trieNode) mermaidFormat(sb *strings.Builder, counter *int, nodeIDs map[*trieNode]string, parentID string) { +func (node *trieNode) mermaidFormat(sb *strings.Builder, counter *int, nodeIDs map[*trieNode]string, parentID string, rules []*Rule) { currentID, exists := nodeIDs[node] if !exists { currentID = fmt.Sprintf("n%d", *counter) *counter++ nodeIDs[node] = currentID - label := node.mermaidLabel() - fmt.Fprintf(sb, " %s[\"%s\"]\n", currentID, label) + fmt.Fprintf(sb, " %s[\"%s\"]\n", currentID, node.mermaidLabel(rules)) } if parentID != "" { @@ -37,101 +40,96 @@ func (node *trieNode) mermaidFormat(sb *strings.Builder, counter *int, nodeIDs m return } - if node.undefined != nil { - if childID, childExists := nodeIDs[node.undefined]; childExists { - fmt.Fprintf(sb, " %s -->|undefined| %s\n", currentID, childID) - } else { - node.undefined.mermaidFormat(sb, counter, nodeIDs, "") - fmt.Fprintf(sb, " %s -->|undefined| %s\n", currentID, nodeIDs[node.undefined]) - } - } - - if node.any != nil { - if childID, childExists := nodeIDs[node.any]; childExists { - fmt.Fprintf(sb, " %s -->|any| %s\n", currentID, childID) - } else { - node.any.mermaidFormat(sb, counter, nodeIDs, "") - fmt.Fprintf(sb, " %s -->|any| %s\n", currentID, nodeIDs[node.any]) - } - } - - if node.scalars.Len() > 0 { - type scalarPair struct { - key Value - node *trieNode - } - pairs := make([]scalarPair, 0, node.scalars.Len()) - node.scalars.Iter(func(key Value, val *trieNode) bool { - pairs = append(pairs, scalarPair{key, val}) - return false - }) - sort.Slice(pairs, func(a, b int) bool { - return pairs[a].key.Compare(pairs[b].key) < 0 - }) - for _, pair := range pairs { - var scalarLabel string - if s, ok := pair.key.(String); ok { - scalarLabel = string(s) - } else { - scalarLabel = pair.key.String() - } - if len(scalarLabel) > 20 { - scalarLabel = scalarLabel[:20] + "..." - } - scalarLabel = mermaidEscape(scalarLabel) - if childID, childExists := nodeIDs[pair.node]; childExists { - fmt.Fprintf(sb, " %s -->|\"%s\"| %s\n", currentID, scalarLabel, childID) - } else { - pair.node.mermaidFormat(sb, counter, nodeIDs, "") - fmt.Fprintf(sb, " %s -->|\"%s\"| %s\n", currentID, scalarLabel, nodeIDs[pair.node]) - } - } - } - - if node.array != nil { - if childID, childExists := nodeIDs[node.array]; childExists { - fmt.Fprintf(sb, " %s -->|array| %s\n", currentID, childID) - } else { - node.array.mermaidFormat(sb, counter, nodeIDs, "") - fmt.Fprintf(sb, " %s -->|array| %s\n", currentID, nodeIDs[node.array]) - } - } - - if node.next != nil { - node.next.mermaidFormat(sb, counter, nodeIDs, currentID) - } + node.next.mermaidFormat(sb, counter, nodeIDs, currentID, rules) } -func (node *trieNode) mermaidLabel() string { +// mermaidEdge draws one way of matching the level's reference, emitting the +// child first if this is where it is reached from. +func mermaidEdge(sb *strings.Builder, counter *int, nodeIDs map[*trieNode]string, from, label string, child *trieNode, rules []*Rule) { + if child == nil { + return + } + if _, exists := nodeIDs[child]; !exists { + child.mermaidFormat(sb, counter, nodeIDs, "", rules) + } + fmt.Fprintf(sb, " %s -->|\"%s\"| %s\n", from, mermaidEscape(label), nodeIDs[child]) +} + +func (d *levelDetail) mermaidFormat(sb *strings.Builder, counter *int, nodeIDs map[*trieNode]string, from string, rules []*Rule) { + if d == nil { + return + } + + mermaidEdge(sb, counter, nodeIDs, from, "undefined", d.undefined, rules) + mermaidEdge(sb, counter, nodeIDs, from, "any", d.any, rules) + + d.scalars.Iter(func(key Value, child *trieNode) bool { + mermaidEdge(sb, counter, nodeIDs, from, key.String(), child, rules) + return false + }) + + if d.alternatives != nil { + d.alternatives.members.Iter(func(key Value, nodes []*trieNode) bool { + for _, child := range nodes { + mermaidEdge(sb, counter, nodeIDs, from, key.String(), child, rules) + } + return false + }) + } + + for _, p := range d.prefixes.walk() { + mermaidEdge(sb, counter, nodeIDs, from, p.prefix+"*", p.node, rules) + } + + // A suffix trie holds its bases reversed (see affixTries), so what it + // walks back is what was written. + for _, p := range d.suffixes.walk() { + mermaidEdge(sb, counter, nodeIDs, from, "*"+reverseString(p.prefix), p.node, rules) + } + + d.array.mermaidFormat(sb, counter, nodeIDs, from, "array", rules) +} + +func (a *arrayTrie) mermaidFormat(sb *strings.Builder, counter *int, nodeIDs map[*trieNode]string, from, label string, rules []*Rule) { + if a == nil { + return + } + + mermaidEdge(sb, counter, nodeIDs, from, label, a.end, rules) + a.any.mermaidFormat(sb, counter, nodeIDs, from, label+" any", rules) + a.scalars.Iter(func(key Value, child *arrayTrie) bool { + child.mermaidFormat(sb, counter, nodeIDs, from, label+" "+key.String(), rules) + return false + }) +} + +func (node *trieNode) mermaidLabel(rules []*Rule) string { var parts []string - if len(node.ref) > 0 { - parts = append(parts, node.ref.String()) + if node.next != nil && len(node.next.ref) > 0 { + parts = append(parts, node.next.ref.String()) } - if len(node.rules) > 0 { - for _, rn := range node.rules { - bodyStr := "" - if rn.rule.Body != nil { - bodyStr = rn.rule.Body.String() - if len(bodyStr) > 50 { - bodyStr = bodyStr[:50] + "..." - } + for _, id := range node.rules { + bodyStr := "" + if rule := rules[id]; rule.Body != nil { + bodyStr = rule.Body.String() + if len(bodyStr) > 50 { + bodyStr = bodyStr[:50] + "..." } - bodyStr = mermaidEscape(bodyStr) - parts = append(parts, bodyStr) } + parts = append(parts, mermaidEscape(bodyStr)) } - if len(node.mappers) > 0 { - parts = append(parts, fmt.Sprintf("%d mapper(s)", len(node.mappers))) + if node.next != nil && len(node.next.mappers) > 0 { + parts = append(parts, fmt.Sprintf("%d mapper(s)", len(node.next.mappers))) } if node.multiple { parts = append(parts, "multiple") } if len(parts) == 0 { - return "·" + return "\u00b7" } return strings.Join(parts, "
") @@ -149,71 +147,159 @@ func (node *trieNode) String() string { } func (node *trieNode) format(sb *strings.Builder, depth int) { + if node == nil { + return + } + indent := strings.Repeat(" ", depth) - if len(node.ref) > 0 { - sb.WriteString(indent) - sb.WriteString(node.ref.String()) - } else if depth == 0 { + if depth == 0 { sb.WriteString("root") + } else { + sb.WriteString(indent) } - if len(node.rules) > 0 { - fmt.Fprintf(sb, " [%d rule(s)]", len(node.rules)) - } - if len(node.mappers) > 0 { - fmt.Fprintf(sb, " [%d mapper(s)]", len(node.mappers)) + sb.WriteString(" [") + util.WriteInt(sb, len(node.rules)) + sb.WriteString(" rule(s)]") } if node.value != nil { - fmt.Fprintf(sb, " value=%v", node.value) + sb.WriteString(" value=") + sb.WriteString(node.value.String()) } if node.multiple { sb.WriteString(" [multiple]") } - sb.WriteString("\n") + sb.WriteByte('\n') - if node.undefined != nil { + node.next.format(sb, depth) +} + +// format prints the level below a node: the reference it resolves, and a line +// per way of matching it. +func (d *levelDetail) format(sb *strings.Builder, depth int) { + if d == nil { + return + } + + indent := strings.Repeat(" ", depth) + + if len(d.ref) > 0 { + sb.WriteString(indent) + sb.WriteString(d.ref.String()) + if len(d.mappers) > 0 { + sb.WriteString(" [") + util.WriteInt(sb, len(d.mappers)) + sb.WriteString(" mapper(s)]") + } + sb.WriteByte('\n') + } + + if d.undefined != nil { sb.WriteString(indent) sb.WriteString(" undefined:\n") - node.undefined.format(sb, depth+2) + d.undefined.format(sb, depth+2) } - if node.any != nil { + if d.any != nil { sb.WriteString(indent) sb.WriteString(" any:\n") - node.any.format(sb, depth+2) + d.any.format(sb, depth+2) } - if node.scalars.Len() > 0 { - scalars := make([]Value, 0, node.scalars.Len()) - nodes := make([]*trieNode, 0, node.scalars.Len()) - node.scalars.Iter(func(key Value, val *trieNode) bool { + if d.scalars.Len() > 0 { + scalars := make([]Value, 0, d.scalars.Len()) + d.scalars.Iter(func(key Value, _ *trieNode) bool { scalars = append(scalars, key) - nodes = append(nodes, val) return false }) - sort.Slice(scalars, func(a, b int) bool { - return scalars[a].Compare(scalars[b]) < 0 - }) - for i := range scalars { + slices.SortFunc(scalars, Value.Compare) + for _, k := range scalars { + child, _ := d.scalars.Get(k) sb.WriteString(indent) - fmt.Fprintf(sb, " %v:\n", scalars[i]) - for j := range nodes { - if ValueEqual(scalars[i], scalars[j]) { - nodes[j].format(sb, depth+2) - break - } - } + sb.WriteString(" ") + sb.WriteString(k.String()) + sb.WriteString(":\n") + child.format(sb, depth+2) } } - if node.array != nil { - sb.WriteString(indent) - sb.WriteString(" array:\n") - node.array.format(sb, depth+2) + // Several values reaching one node, so the node is printed once and the + // values that reach it are named together (see alternativeChildren). + if d.alternatives != nil { + for i, conv := range d.alternatives.converged { + var keys []Value + d.alternatives.members.Iter(func(k Value, nodes []*trieNode) bool { + if slices.Contains(nodes, conv) { + keys = append(keys, k) + } + return false + }) + slices.SortFunc(keys, Value.Compare) + + sb.WriteString(indent) + sb.WriteString(" any of ") + for j, k := range keys { + if j > 0 { + sb.WriteString(", ") + } + sb.WriteString(k.String()) + } + fmt.Fprintf(sb, " -> #%d:\n", i) + conv.format(sb, depth+2) + } } - if node.next != nil { - node.next.format(sb, depth) + if d.array != nil { + sb.WriteString(indent) + sb.WriteString(" array:\n") + d.array.format(sb, depth+2) + } + + for _, p := range d.prefixes.walk() { + sb.WriteString(indent) + sb.WriteString(` prefix "`) + sb.WriteString(p.prefix) + sb.WriteString("\":\n") + p.node.format(sb, depth+2) + } + + for _, p := range d.suffixes.walk() { + sb.WriteString(indent) + sb.WriteString(` suffix "`) + sb.WriteString(reverseString(p.prefix)) + sb.WriteString("\":\n") + p.node.format(sb, depth+2) + } +} + +func (a *arrayTrie) format(sb *strings.Builder, depth int) { + if a == nil { + return + } + + indent := strings.Repeat(" ", depth) + + a.end.format(sb, depth) + + if a.any != nil { + sb.WriteString(indent) + sb.WriteString(" any:\n") + a.any.format(sb, depth+2) + } + + keys := make([]Value, 0, a.scalars.Len()) + a.scalars.Iter(func(k Value, _ *arrayTrie) bool { + keys = append(keys, k) + return false + }) + slices.SortFunc(keys, Value.Compare) + for _, k := range keys { + child, _ := a.scalars.Get(k) + sb.WriteString(indent) + sb.WriteString(" ") + sb.WriteString(k.String()) + sb.WriteString(":\n") + child.format(sb, depth+2) } } diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/internal/scanner/scanner.go b/vendor/github.com/open-policy-agent/opa/v1/ast/internal/scanner/scanner.go index 6b2b03b27a..cbf4cd4799 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/internal/scanner/scanner.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/internal/scanner/scanner.go @@ -502,12 +502,9 @@ func (s *Scanner) scanRawTemplateString() (string, tokens.Token) { break } - if ch == '\\' { - switch s.curr { - case '{': - escapes = append(escapes, s.offset-1) - s.next() - } + if ch == '\\' && s.curr == '{' { + escapes = append(escapes, s.offset-1) + s.next() } } diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/interning.go b/vendor/github.com/open-policy-agent/opa/v1/ast/interning.go index 65ffd0b591..7b604d5661 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/interning.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/interning.go @@ -13,12 +13,54 @@ type internable interface { bool | string | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 } +type interned struct { + Refs *internedRefs +} + +type internedRefs struct { + AnyPrefixMatch Ref + AnySuffixMatch Ref + EndsWith Ref + Equal Ref + Equality Ref + GlobMatch Ref + InternalPrint Ref + InternalTestCase Ref + Member Ref + MemberWithKey Ref + Or Ref + Print Ref + RegoMetadataChain Ref + RegoMetadataRule Ref + StartsWith Ref +} + // NOTE! Great care must be taken **not** to modify the terms returned // from these functions, as they are shared across all callers. // This package is currently considered experimental, and may change // at any time without notice. var ( + Interned = &interned{ + Refs: &internedRefs{ + AnyPrefixMatch: AnyPrefixMatch.Ref(), + AnySuffixMatch: AnySuffixMatch.Ref(), + EndsWith: EndsWith.Ref(), + Equal: Equal.Ref(), + Equality: Equality.Ref(), + GlobMatch: GlobMatch.Ref(), + InternalPrint: InternalPrint.Ref(), + InternalTestCase: InternalTestCase.Ref(), + Member: Member.Ref(), + MemberWithKey: MemberWithKey.Ref(), + Or: Or.Ref(), + Print: Print.Ref(), + RegoMetadataChain: RegoMetadataChain.Ref(), + RegoMetadataRule: RegoMetadataRule.Ref(), + StartsWith: StartsWith.Ref(), + }, + } + InternedNullValue Value = Null{} InternedNullTerm = NewTerm(InternedNullValue) @@ -57,7 +99,12 @@ var ( "internal": Var("internal"), "else": Var("else"), - "i": Var("i"), "j": Var("j"), "k": Var("k"), "v": Var("v"), "x": Var("x"), "y": Var("y"), "z": Var("z"), + "a": Var("a"), "b": Var("b"), "c": Var("c"), + "i": Var("i"), "j": Var("j"), + "k": Var("k"), "v": Var("v"), + "x": Var("x"), "y": Var("y"), "z": Var("z"), + + "allow": Var("allow"), "deny": Var("deny"), } ) diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/location/location.go b/vendor/github.com/open-policy-agent/opa/v1/ast/location/location.go index e08088cff1..795480c1b9 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/location/location.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/location/location.go @@ -97,29 +97,31 @@ func (loc *Location) HasFile() bool { return loc != nil && loc.File != "" } -// End returns the (row, col) one past the last rune of loc.Text — an -// exclusive end matching the scanner's offset calculation, so [Start, End) -// covers the text. Columns are counted per rune. Returns (Row, Col) for -// empty text and (0, 0) for a nil receiver. +// End determines the end position of loc. func (loc *Location) End() (row, col int) { if loc == nil { return 0, 0 } + return EndOf(loc.Row, loc.Col, loc.Text) +} - if len(loc.Text) == 0 { - return loc.Row, loc.Col +// EndOf returns the end (row, col) position reached by starting at (row, col) +// and advancing through text. +func EndOf(row, col int, text []byte) (endRow, endCol int) { + if len(text) == 0 { + return row, col } - row = loc.Row + bytes.Count(loc.Text, []byte{'\n'}) - col = loc.Col + endRow = row + bytes.Count(text, []byte{'\n'}) + endCol = col - lastLine := loc.Text - if row != loc.Row { - col = 1 - lastLine = loc.Text[bytes.LastIndex(loc.Text, []byte{'\n'})+1:] + lastLine := text + if endRow != row { + endCol = 1 + lastLine = text[bytes.LastIndex(text, []byte{'\n'})+1:] } - return row, col + utf8.RuneCount(lastLine) + return endRow, endCol + utf8.RuneCount(lastLine) } // Compare returns -1, 0, or 1 to indicate if this loc is less than, equal to, @@ -127,7 +129,7 @@ func (loc *Location) End() (row, col int) { // column of the Location (but not on the text.) Nil locations are greater than // non-nil locations. func (loc *Location) Compare(other *Location) int { - if loc == other { + if loc == other { //nolint:gocritic // this is fine as an ifElseChain return 0 } else if loc == nil { return 1 diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/map.go b/vendor/github.com/open-policy-agent/opa/v1/ast/map.go index aa0e655b9e..2120e00b0d 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/map.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/map.go @@ -26,7 +26,7 @@ func NewValueMap() *ValueMap { // MarshalJSON provides a custom marshaller for the ValueMap which // will include the key, value, and value type. func (vs *ValueMap) MarshalJSON() ([]byte, error) { - var tmp []map[string]any + tmp := make([]map[string]any, 0, vs.Len()) vs.Iter(func(k Value, v Value) bool { tmp = append(tmp, map[string]any{ "name": k.String(), diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/mermaid.go b/vendor/github.com/open-policy-agent/opa/v1/ast/mermaid.go index 5f0911ec11..d16a3ad58b 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/mermaid.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/mermaid.go @@ -280,9 +280,9 @@ func mermaidFormatWith(w *With, b *mermaidBuilder) string { // --- Not --- -func (not *Not) mermaidFormat(b *mermaidBuilder) string { +func (n *Not) mermaidFormat(b *mermaidBuilder) string { id := b.node("stadium", "not") - for i, expr := range not.Body { + for i, expr := range n.Body { exprID := mermaidFormatExpr(expr, b) b.edgeLabeled(id, exprID, strconv.Itoa(i)) } diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/object.go b/vendor/github.com/open-policy-agent/opa/v1/ast/object.go new file mode 100644 index 0000000000..4255bb8960 --- /dev/null +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/object.go @@ -0,0 +1,166 @@ +package ast + +import ( + "slices" + "sync" + + "github.com/open-policy-agent/opa/v1/util" +) + +var ObjectBuilderPool = &util.SyncPool[ObjectBuilder]{ + Pool: sync.Pool{ + New: func() any { + return newObjectBuilder(32) + }, + }, +} + +// ObjectBuilder is a builder to help building ast.Object values without having to step through intermediate +// formats and conversions, and with things like resource pooling and interning of keys handled conveniently. +// While ast.Object's support other key types than strings, this builder currently doesn't. +// +// NOTE that this is a helper intended for internal use. While anyone is welcome to use it, the API is not +// considered part of the public API contract, and can change without notice. +type ObjectBuilder struct { + pairs [][2]*Term + keyMapFn func(string) string + valMapFn func(*Term) *Term +} + +func newObjectBuilder(size int) *ObjectBuilder { + return &ObjectBuilder{pairs: make([][2]*Term, 0, size)} +} + +// MapToObject returns an Object mapped from m where an optional key mapper is used to transform +// the keys before they're made to (interned) terms, and a mandatory value mapper to transform +// generic values to terms. +func MapToObject[V any](m map[string]V, keyFn func(string) string, valueFn func(V) *Term) Object { + ob := ObjectBuilderPool.Get().Reset().WithKeyMapper(keyFn) + defer ObjectBuilderPool.Put(ob) + + for k, v := range m { + ob.Item(k, valueFn(v)) + } + return ob.AsObject() +} + +func TryMapToObject[V any](m map[string]V, keyFn func(string) string, valueFn func(V) (*Term, error)) (Object, error) { + ob := ObjectBuilderPool.Get().Reset().WithKeyMapper(keyFn) + defer ObjectBuilderPool.Put(ob) + + for k, v := range m { + t, err := valueFn(v) + if err != nil { + return nil, err + } + ob.Item(k, t) + } + return ob.AsObject(), nil +} + +// WithKeyMapper sets a key mapping function to be used when adding items to the builder. +func (b *ObjectBuilder) WithKeyMapper(fn func(string) string) *ObjectBuilder { + b.keyMapFn = fn + return b +} + +// WithValueMapper sets a value mapping function to be used when adding items to the builder. +func (b *ObjectBuilder) WithValueMapper(fn func(*Term) *Term) *ObjectBuilder { + b.valMapFn = fn + return b +} + +// Grow ensures that the builder has capacity for at least n additional items, and returns the builder. +func (b *ObjectBuilder) Grow(n int) *ObjectBuilder { + if b.pairs == nil { + b.pairs = make([][2]*Term, 0, n) + } else if cap(b.pairs)-len(b.pairs) < n { + b.pairs = slices.Grow(b.pairs, n) + } + return b +} + +// Item adds a key-value pair to the builder, where the key is a string (interned as term) and the value as term. +func (b *ObjectBuilder) Item(key string, value *Term) *ObjectBuilder { + k, v := b.mapKey(key), b.mapValue(value) + if k == nil || v == nil { + return b + } + b.pairs = append(b.pairs, [2]*Term{k, v}) + return b +} + +// AsObject builds and returns an ast.Object. +func (b *ObjectBuilder) AsObject() Object { + return NewObject(b.pairs...) +} + +// AsTerm builds and returns an ast.Object contained in an ast.Term for convenience. +func (b *ObjectBuilder) AsTerm() *Term { + return NewTerm(b.AsObject()) +} + +// Reset resets the builder to an empty state, but keeps the underlying slice for reuse. +func (b *ObjectBuilder) Reset() *ObjectBuilder { + b.pairs = b.pairs[:0] + b.keyMapFn = nil + b.valMapFn = nil + return b +} + +func (b *ObjectBuilder) mapKey(key string) *Term { + if b.keyMapFn != nil { + key = b.keyMapFn(key) + } + return InternedTerm(key) +} + +func (b *ObjectBuilder) mapValue(value *Term) *Term { + if b.valMapFn != nil { + value = b.valMapFn(value) + } + return value +} + +// InterfaceToTermMapper works similarly to [InterfaceToValue], but returns a term instead of a value, +// and tries to find an interned version of the term if possible. +func InterfaceToTermMapper(x any) (*Term, error) { + switch v := x.(type) { + case *Term: + return v, nil + case Value: + // TODO: Find interned term from value + return NewTerm(v), nil + case bool: + return InternedTerm(v), nil + case string: + return InternedTerm(v), nil + case int: + return InternedTerm(v), nil + case int8: + return InternedTerm(v), nil + case int16: + return InternedTerm(v), nil + case int32: + return InternedTerm(v), nil + case int64: + return InternedTerm(v), nil + case uint: + return InternedTerm(v), nil + case uint8: + return InternedTerm(v), nil + case uint16: + return InternedTerm(v), nil + case uint32: + return InternedTerm(v), nil + case uint64: + return InternedTerm(v), nil + default: + x, err := InterfaceToValue(v) + if err != nil { + return nil, err + } + return NewTerm(x), nil // TODO: InternedTerm from value + } + +} diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/parser.go b/vendor/github.com/open-policy-agent/opa/v1/ast/parser.go index 3ecf654a54..65a3a2a18a 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/parser.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/parser.go @@ -15,7 +15,6 @@ import ( "net/url" "regexp" "slices" - "sort" "strconv" "strings" "unicode/utf8" @@ -29,36 +28,42 @@ import ( "github.com/open-policy-agent/opa/v1/util" ) -// DefaultMaxParsingRecursionDepth is the default maximum recursion -// depth for the parser -const DefaultMaxParsingRecursionDepth = 100000 - -// ErrMaxParsingRecursionDepthExceeded is returned when the parser -// recursion exceeds the maximum allowed depth -var ErrMaxParsingRecursionDepthExceeded = errors.New("max parsing recursion depth exceeded") - -var RegoV1CompatibleRef = Ref{VarTerm("rego"), InternedTerm("v1")} - // RegoVersion defines the Rego syntax requirements for a module. -type RegoVersion int - -const DefaultRegoVersion = RegoV1 +type RegoVersion uint8 const ( + // DefaultRegoVersion is the default Rego version for this OPA version. + DefaultRegoVersion = RegoV1 + + // DefaultMaxParsingRecursionDepth is the default maximum recursion depth for the parser + DefaultMaxParsingRecursionDepth = 100000 +) + +const ( + // RegoUndefined represents a Rego version unknown to OPA, like for a policy that has + // yet to be parsed and a no version information has been provided by other means. RegoUndefined RegoVersion = iota - // RegoV0 is the default, original Rego syntax. + // RegoV0 is the original Rego syntax, which was used by default in OPA < 1.0. RegoV0 - // RegoV0CompatV1 requires modules to comply with both the RegoV0 and RegoV1 syntax (as when 'rego.v1' is imported in a module). - // Shortly, RegoV1 compatibility is required, but 'rego.v1' or 'future.keywords' must also be imported. + // RegoV0CompatV1 requires modules to comply with both the RegoV0 and RegoV1 + // syntax (requiring Rego v1 imports in a module to use v1 keywords). + // For more information, see https://www.openpolicyagent.org/docs/v0-compatibility RegoV0CompatV1 - // RegoV1 is the Rego syntax enforced by OPA 1.0; e.g.: - // future.keywords part of default keyword set, and don't require imports; - // 'if' and 'contains' required in rule heads; - // (some) strict checks on by default. + // RegoV1 is the Rego syntax enforced by OPA 1.0 and later versions, including the following changes: + // - Keywords `in`, `every`, `ìf` and `contains` now part of the default set, and don't require explicit import + // - Using 'if' and 'contains' now required in rule heads + // - Most compiler checks previously enabled in "strict mode" now enabled by default + // For more information, see https://www.openpolicyagent.org/docs/v0-upgrade RegoV1 ) var ( + // ErrMaxParsingRecursionDepthExceeded is returned when the parser + // recursion exceeds the maximum allowed depth + ErrMaxParsingRecursionDepthExceeded = errors.New("max parsing recursion depth exceeded") + + RegoV1CompatibleRef = Ref{RegoRootDocument, InternedTerm("v1")} + // this is the name to use for instantiating an empty set, e.g., `set()`. setConstructor = RefTerm(VarTerm("set")) @@ -66,15 +71,9 @@ var ( Var("$0"), Var("$1"), Var("$2"), Var("$3"), Var("$4"), Var("$5"), Var("$6"), Var("$7"), Var("$8"), Var("$9"), Var("$10"), } - - // use static references to avoid allocations, and - // copy them to the call term only when needed - memberWithKeyRef = MemberWithKey.Ref() - memberRef = Member.Ref() - - newlineBytes = []byte{'\n'} metadataBytes = []byte("METADATA") metadataParserPool = util.NewSyncPool[metadataParser]() + noScanOptions []scanner.ScanOption ) func (v RegoVersion) Int() int { @@ -342,7 +341,6 @@ func (p *Parser) presentParser() (*Parser, map[string]tokens.Token) { // comments as they are found. Any errors encountered while // parsing will be accumulated and returned as a list of Errors. func (p *Parser) Parse() ([]Statement, []*Comment, Errors) { - if p.po.Capabilities == nil { p.po.Capabilities = CapabilitiesForThisVersion(CapabilitiesRegoVersion(p.po.RegoVersion)) } @@ -352,11 +350,7 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) { if p.po.EffectiveRegoVersion() == RegoV1 { if !p.po.Capabilities.ContainsFeature(FeatureRegoV1) { return nil, nil, Errors{ - &Error{ - Code: ParseErr, - Message: "illegal capabilities: rego_v1 feature required for parsing v1 Rego", - Location: nil, - }, + &Error{Code: ParseErr, Message: "illegal capabilities: rego_v1 feature required for parsing v1 Rego"}, } } @@ -370,11 +364,7 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) { // For sake of error reporting, we still need to check that keywords in capabilities are known in v0 if _, ok := futureKeywordsV0[kw]; !ok { return nil, nil, Errors{ - &Error{ - Code: ParseErr, - Message: fmt.Sprintf("illegal capabilities: unknown keyword: %v", kw), - Location: nil, - }, + &Error{Code: ParseErr, Message: "illegal capabilities: unknown keyword: " + kw}, } } } @@ -383,13 +373,7 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) { // Check that explicitly requested future keywords are known. for _, kw := range p.po.FutureKeywords { if _, ok := allowedFutureKeywords[kw]; !ok { - return nil, nil, Errors{ - &Error{ - Code: ParseErr, - Message: fmt.Sprintf("unknown future keyword: %v", kw), - Location: nil, - }, - } + return nil, nil, Errors{&Error{Code: ParseErr, Message: "unknown future keyword: " + kw}} } } } else { @@ -397,13 +381,7 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) { var ok bool allowedFutureKeywords[kw], ok = allFutureKeywords[kw] if !ok { - return nil, nil, Errors{ - &Error{ - Code: ParseErr, - Message: fmt.Sprintf("illegal capabilities: unknown keyword: %v", kw), - Location: nil, - }, - } + return nil, nil, Errors{&Error{Code: ParseErr, Message: "illegal capabilities: unknown keyword: " + kw}} } } @@ -413,48 +391,28 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) { } } - var err error - p.s.s, err = scanner.New(p.r) - if err != nil { - return nil, nil, Errors{ - &Error{ - Code: ParseErr, - Message: err.Error(), - Location: nil, - }, - } - } - - selected := map[string]tokens.Token{} + var selected map[string]tokens.Token if p.po.AllFutureKeywords { + selected = make(map[string]tokens.Token, len(allowedFutureKeywords)) maps.Copy(selected, allowedFutureKeywords) } else { if p.po.EffectiveRegoVersion() == RegoV1 { + selected = make(map[string]tokens.Token, len(futureKeywordsV0)+len(p.po.FutureKeywords)) for kw := range futureKeywordsV0 { tok, ok := allowedFutureKeywords[kw] if !ok { - return nil, nil, Errors{ - &Error{ - Code: ParseErr, - Message: fmt.Sprintf("unknown future keyword: %v", kw), - Location: nil, - }, - } + return nil, nil, Errors{&Error{Code: ParseErr, Message: "unknown future keyword: " + kw}} } selected[kw] = tok } + } else { + selected = make(map[string]tokens.Token, len(p.po.FutureKeywords)) } for _, kw := range p.po.FutureKeywords { tok, ok := allowedFutureKeywords[kw] if !ok { - return nil, nil, Errors{ - &Error{ - Code: ParseErr, - Message: fmt.Sprintf("unknown future keyword: %v", kw), - Location: nil, - }, - } + return nil, nil, Errors{&Error{Code: ParseErr, Message: "unknown future keyword: " + kw}} } selected[kw] = tok } @@ -464,12 +422,13 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) { p.notBodies = true } - p.s.s = p.s.s.WithKeywords(selected) + var err error + if p.s.s, err = scanner.New(p.r); err != nil { + return nil, nil, Errors{&Error{Code: ParseErr, Message: err.Error()}} + } - if p.po.EffectiveRegoVersion() == RegoV1 { - for kw, tok := range futureKeywordsV0 { - p.s.s.AddKeyword(kw, tok) - } + for name, token := range selected { + p.s.s.AddKeyword(name, token) } // read the first token to initialize the parser @@ -484,38 +443,46 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) { // next type of statement. If a statement can be parsed, continue from that // point trying to parse packages, imports, etc. in the same order. for p.s.tok != tokens.EOF { - s := p.save() + var s *state - if pkg := p.parsePackage(); pkg != nil { - stmts = append(stmts, pkg) - continue - } else if len(p.s.errors) > 0 { + // Reported here rather than in parseRules: `package := 1` and `import := 1` + // are consumed by the statement parsers below, which fail pointing at the + // assign token instead of the keyword. + if !p.po.SkipRules && p.errKeywordRuleName(false) { break } - p.restore(s) - s = p.save() - - if imp := p.parseImport(); imp != nil { - if RegoRootDocument.Equal(imp.Path.Value.(Ref)[0]) { - p.regoV1Import(imp) + if p.s.tok == tokens.Package { + s = p.save() + if pkg := p.parsePackage(); pkg != nil { + stmts = append(stmts, pkg) + continue + } else if len(p.s.errors) > 0 { + break } - - if FutureRootDocument.Equal(imp.Path.Value.(Ref)[0]) { - p.futureImport(imp, allowedFutureKeywords) - } - - stmts = append(stmts, imp) - continue - } else if len(p.s.errors) > 0 { - break + p.restore(s) } - p.restore(s) + if p.s.tok == tokens.Import { + s = p.save() + if imp := p.parseImport(); imp != nil { + if RegoRootDocument.Equal(imp.Path.Value.(Ref)[0]) { + p.regoV1Import(imp) + p.reclassifyKeyword() + } else if FutureRootDocument.Equal(imp.Path.Value.(Ref)[0]) { + p.futureImport(imp, allowedFutureKeywords) + p.reclassifyKeyword() + } + stmts = append(stmts, imp) + continue + } else if len(p.s.errors) > 0 { + break + } + p.restore(s) + } if !p.po.SkipRules { s = p.save() - if rules := p.parseRules(); rules != nil { for i := range rules { stmts = append(stmts, rules[i]) @@ -524,7 +491,6 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) { } else if len(p.s.errors) > 0 { break } - p.restore(s) } @@ -558,7 +524,7 @@ func (p *Parser) parseAnnotations(stmts []Statement) []Statement { } func parseAnnotations(comments []*Comment) (stmts []*Annotations, errs Errors) { - numBlocks := CountFunc(comments, IsMetadataComment) + numBlocks := util.Count(IsMetadataComment, comments...) if numBlocks == 0 { return nil, nil } @@ -811,6 +777,94 @@ func scanAheadRef(p *Parser) bool { return false } +// keywordRuleNameFollowers maps a keyword token to the tokens that, following it, +// make the statement unambiguously a rule declaration. +var ( + ruleNameFollowers = []tokens.Token{tokens.Assign, tokens.Unify, tokens.If, tokens.Contains, tokens.LParen} + // `not`/`and`/`or` drop '(': at the start of a statement, `not (x)` is a negated + // group and `or(x, y)` a call to the set union built-in, not rule heads. + operatorRuleNameFollowers = []tokens.Token{tokens.Assign, tokens.Unify, tokens.If, tokens.Contains} + // `package`/`import` drop `if` and `contains`: both take a path that may itself + // be named after a keyword, as in `package contains` or `import if.foo`. + pathRuleNameFollowers = []tokens.Token{tokens.Assign, tokens.Unify, tokens.LParen} + keywordRuleNameFollowers = map[tokens.Token][]tokens.Token{ + tokens.Every: ruleNameFollowers, + tokens.If: ruleNameFollowers, + tokens.In: ruleNameFollowers, + tokens.Some: ruleNameFollowers, + tokens.As: ruleNameFollowers, + tokens.Package: pathRuleNameFollowers, + tokens.Import: pathRuleNameFollowers, + tokens.Not: operatorRuleNameFollowers, + tokens.LogicalAnd: operatorRuleNameFollowers, + tokens.LogicalOr: operatorRuleNameFollowers, + // `contains` outside of a rule head parses as a plain var, so `contains := x` + // is still a legal query; as a rule it's caught by the rego-v1 check. + tokens.Contains: {tokens.If, tokens.Contains}, + } +) + +// reclassifyKeyword re-tags the lookahead token after an import that registered +// new keywords with the scanner. The parser reads one token ahead, so the first +// token of the statement following the import was scanned before the scanner +// knew about the keyword, and would otherwise be treated as a plain identifier. +func (p *Parser) reclassifyKeyword() { + if p.s.tok != tokens.Ident { + return + } + + if tok, ok := allFutureKeywords[p.s.lit]; ok && p.s.s.IsKeyword(p.s.lit) { + p.s.tok = tok + } +} + +// errKeywordRuleName reports an error if the current token is a keyword used as a +// rule name, e.g. `every := 1`, and returns whether it did. A statement that began +// with `default` can only be a rule, so no lookahead is needed there. +func (p *Parser) errKeywordRuleName(isDefault bool) bool { + followers, ok := keywordRuleNameFollowers[p.s.tok] + if !ok { + return false + } + + keyword, loc := p.s.tok, p.s.Loc() + + if !isDefault { + s := p.save() + p.scan() + next := p.s.tok + p.restore(s) + + if !slices.Contains(followers, next) { + return false + } + } + + p.errorf(loc, "%s keyword cannot be used for rule name", keyword) + + return true +} + +// scanAheadLogicalCall rewrites an `and`/`or` keyword token to tokens.Ident when +// it's immediately followed by `(`. Only valid where a term is expected: there, +// the operator reading is impossible, so it must be a function (`&`/`|` set built-ins). +// Operator position is decided before any term is parsed, which is what keeps `x and (b)` a keyword. +func scanAheadLogicalCall(p *Parser) { + if p.s.tok != tokens.LogicalAnd && p.s.tok != tokens.LogicalOr { + return + } + + s := p.save() + p.scanWS() + tok := p.s.tok + p.restore(s) + + if tok == tokens.LParen { + // This is a call to a function named `and`/`or` + p.s.tok = tokens.Ident + } +} + func (p *Parser) parseRules() []*Rule { var rule Rule @@ -826,6 +880,9 @@ func (p *Parser) parseRules() []*Rule { } if p.s.tok != tokens.Ident { + if rule.Default { + p.errKeywordRuleName(true) + } return nil } @@ -897,13 +954,18 @@ func (p *Parser) parseRules() []*Rule { rule.Head.keywords = append(rule.Head.keywords, tokens.If) p.scan() s := p.save() + + // Only a set term with a leading '{' is ambiguous with a body; + // e.g.: 'not {...}' and 'set()' parses to a set literal, but have no ambiguous leading '{' + leadingBrace := p.s.tok == tokens.LBrace + if expr := p.parseLiteral(); expr != nil { // NOTE(sr): set literals are never false or undefined, so parsing this as // p if { true } // ^^^^^^^^ set of one element, `true` // isn't valid. isSetLiteral := false - if t, ok := expr.Terms.(*Term); ok { + if t, ok := expr.Terms.(*Term); ok && leadingBrace { _, isSetLiteral = t.Value.(Set) } // expr.Term is []*Term or Every @@ -913,6 +975,12 @@ func (p *Parser) parseRules() []*Rule { } } + if !leadingBrace { + // Without a leading '{' there is no '{ BODY }' rule body to fall back to, + // so the literal's own error is the useful one; restoring would drop it. + return nil + } + // parsing as literal didn't work out, expect '{ BODY }' p.restore(s) fallthrough @@ -1178,13 +1246,13 @@ func (p *Parser) parseBody(end tokens.Token) Body { } func (p *Parser) parseQuery(requireSemi bool, end tokens.Token) Body { - body := Body{} - if p.s.tok == end { p.error(p.s.Loc(), "found empty body") return nil } + body := Body{} + for { expr := p.parseLiteral() if expr == nil { @@ -1205,6 +1273,7 @@ func (p *Parser) parseQuery(requireSemi bool, end tokens.Token) Body { if !p.s.skippedNL { // If there was already an error then don't pile this one on if len(p.s.errors) == 0 { + p.hintMissingInfixKeyword() p.illegal(`expected \n or %s or %s`, tokens.Semicolon, end) } return nil @@ -1236,12 +1305,20 @@ func (p *Parser) parseLiteral() (expr *Expr) { // binary. Otherwise, restore and fall through to regular handling. if p.s.tok == tokens.LBrace && p.logicalKeywordsActive() { s := p.save() + cache := p.cache.m + braceOffset := p.s.loc.Offset bodyLoc := p.s.Loc() p.scan() body := p.parseBody(tokens.RBrace) if body != nil { p.scan() // consume `}` if p.s.tok == tokens.LogicalAnd || p.s.tok == tokens.LogicalOr { + // Only now are the braces known to be an operand rather than a rule body. + if isAmbiguousUnionBody(body) { + p.errorAmbiguousUnionBody(bodyLoc, braceOffset, body, "") + return nil + } + outer := p.parseLogicalOrChain(body, true, bodyLoc) if outer == nil { return nil @@ -1250,6 +1327,7 @@ func (p *Parser) parseLiteral() (expr *Expr) { } } p.restore(s) + p.cache.m = cache } // LHS/whole parenthesized group at statement start: `(a or b)`, @@ -1257,7 +1335,7 @@ func (p *Parser) parseLiteral() (expr *Expr) { // parens hold or precede an and/or; otherwise (`({})`, `({a})`, `(a == b)`) it // restores and we fall through so parseExpr handles the term. if p.s.tok == tokens.LParen && p.logicalKeywordsActive() { - if body, explicit, loc, committed := p.parseLogicalGroup(false); committed { + if body, explicit, loc, committed := p.parseLogicalGroup(false, ""); committed { if body == nil { return nil } @@ -1291,7 +1369,9 @@ func (p *Parser) parseLiteral() (expr *Expr) { if nb == nil { return nil } - return p.attachWith(nb) + // A not-body is a complete operand, so it may lead an and/or chain: + // `not { x } and y`. + return p.foldLogicalTail(NewBody(nb), false, nb.Location) } switch p.s.tok { @@ -1325,14 +1405,12 @@ func (p *Parser) isAllowedRefKeywordStr(s string) bool { } func (p *Parser) parseLiteralExpr(negated bool, notLoc *Location) *Expr { - startOffset := p.s.loc.Offset - startLoc := p.s.Loc() s := p.save() // Negated parenthesized group: `not (a or b)`. The parens are an operand of // `not`, so any `{...}` inside is a body. - if negated && p.notBodies && p.s.tok == tokens.LParen && p.logicalKeywordsActive() { - if body, explicit, _, committed := p.parseLogicalGroup(true); committed { + if negated && p.notBodies && p.s.tok == tokens.LParen { + if body, explicit, _, committed := p.parseLogicalGroup(true, "not "); committed { if body == nil { return nil } @@ -1395,10 +1473,22 @@ func (p *Parser) parseLiteralExpr(negated bool, notLoc *Location) *Expr { } if expr.Location == nil { - startLoc.Text = p.s.Text(startOffset, p.s.lastEnd) + startLoc := s.Loc() + startLoc.Text = p.s.Text(startLoc.Offset, p.s.lastEnd) expr.SetLoc(startLoc) } + if notLoc == nil && bytes.HasPrefix(expr.Location.Text, []byte("{")) { + // `{}` on its own is an empty body + if isEmptyObjectTerm(expr) { + p.error(expr.Location, "found empty body") + return nil + } + + p.errorBraceLedOperand(expr.Location, expr.Location.Text, p.s.tok.String()) + return nil + } + outer := p.parseLogicalOrChain(NewBody(expr), false, expr.Location) if outer == nil { return nil @@ -1466,6 +1556,60 @@ func (p *Parser) attachWith(e *Expr) *Expr { return e } +// infixFutureKeywords are the future keywords usable as infix operators in a +// rule body, mapped to an example of the expression each enables. +var infixFutureKeywords = map[string]string{ + "in": "x in xs", + "and": "x and y", + "or": "x or y", +} + +// hintMissingInfixKeyword hints at the import for a body expression trailed by +// a plain `in`/`and`/`or` identifier, or by the comma of `k, v in xs`. Only +// call it when an error is about to be reported: an unconsumed hint would end +// up attached to a later, unrelated error. +func (p *Parser) hintMissingInfixKeyword() { + // Something more specific, like `some x in xs`, already hinted. + if len(p.s.hints) > 0 { + return + } + + kw := "in" + + switch p.s.tok { + case tokens.Ident: + // An active keyword scans as its own token, so an Ident means it's + // the import that's missing. + kw = p.s.lit + if _, ok := infixFutureKeywords[kw]; !ok { + return + } + case tokens.Comma: + // Only hint on `k, v in xs`, so require a membership expr after the comma. + s := p.save() + p.scan() + term := p.futureParser().parseTermInfixCall() + p.restore(s) + + if term == nil { + return + } + call, ok := term.Value.(Call) + if !ok || len(call) == 0 { + return + } + switch call[0].String() { + case Member.Name, MemberWithKey.Name: + default: + return + } + default: + return + } + + p.hint(fmt.Sprintf("`import future.keywords.%s` for `%s` expressions", kw, infixFutureKeywords[kw])) +} + func (p *Parser) errWithOnOperand(loc *Location, kw string) { p.hint(fmt.Sprintf( "Wrap the operand in `(...)` or `{...}` to scope, or move `with` after the `%s` expression to apply it to the whole expression", @@ -1567,14 +1711,44 @@ func (p *Parser) parseSome() *Expr { } func (p *Parser) parseNotBody(notLoc *Location) *Expr { + braceOffset := p.s.loc.Offset + braceLoc := p.s.Loc() + s := p.save() p.scan() // consume `{` + // `not {}` is an empty body, which parseBody reports precisely; only non-empty + // braces are worth re-reading as a value. + empty := p.s.tok == tokens.RBrace + body := p.parseBody(tokens.RBrace) if body == nil { + if empty { + return nil + } + + // The braces may hold a value rather than a body. If so, report the + // contract and its escapes; if not, keep the body error. + failed := p.save() + p.restore(s) + + // The operand can extend past the braces (`{1, 2} & input.s == set()`), + // and parens group rather than delimit, so it is the whole operand that has to be wrapped. + if term := p.parseTermInfixCall(); term != nil { + p.errorOperandBraceNeedsBody(braceLoc, p.s.Text(braceOffset, p.s.lastEnd), term, "not ") + return nil + } + + p.restore(failed) + return nil } p.scan() // consume `}` + if isAmbiguousUnionBody(body) { + p.errorAmbiguousUnionBody(braceLoc, braceOffset, body, "not ") + return nil + } + // Extend the location to also include the 'not ' prefix spanned := p.extendLoc(notLoc) not := &Not{Body: body, ExplicitBody: true, Location: spanned} @@ -1616,7 +1790,7 @@ func (p *Parser) parseLogicalOrChain(lhsBody Body, lhsExplicit bool, lhsLoc *Loc for p.s.tok == tokens.LogicalOr { p.scan() - rhsBody, rhsExplicit, rhsLoc := p.parseLogicalOperand() + rhsBody, rhsExplicit, rhsLoc := p.parseLogicalOperand("or") if rhsBody == nil { return nil } @@ -1631,6 +1805,8 @@ func (p *Parser) parseLogicalOrChain(lhsBody Body, lhsExplicit bool, lhsLoc *Loc rhsExplicit = false } + p.checkVoidCallOperands(lhsBody, rhsBody, "or") + exprLoc := p.extendLoc(lhsLoc) node := &LogicalOr{ Lhs: lhsBody, @@ -1663,11 +1839,13 @@ func (p *Parser) parseLogicalAndChain(lhsBody Body, lhsExplicit bool, lhsLoc *Lo for p.s.tok == tokens.LogicalAnd { p.scan() - rhsBody, rhsExplicit, _ := p.parseLogicalOperand() + rhsBody, rhsExplicit, _ := p.parseLogicalOperand("and") if rhsBody == nil { return nil } + p.checkVoidCallOperands(lhsBody, rhsBody, "and") + exprLoc := p.extendLoc(lhsLoc) node := &LogicalAnd{ Lhs: lhsBody, @@ -1705,16 +1883,49 @@ func isNegated(p *Parser) bool { return tok != tokens.Dot && tok != tokens.LBrack } -// parseLogicalOperand parses a single operand of an `and`/`or` expression. -func (p *Parser) parseLogicalOperand() (Body, bool, *Location) { +// parseLogicalOperand parses a single operand of an `and`/`or` expression. op is +// the operator the operand belongs to, or "" when the caller is speculating and +// will restore on failure. +func (p *Parser) parseLogicalOperand(op string) (Body, bool, *Location) { if p.s.tok == tokens.LBrace { + braceOffset := p.s.loc.Offset loc := p.s.Loc() + s := p.save() p.scan() + + // `{}` is an empty body, which parseBody reports precisely; only non-empty + // braces are worth re-reading as a value. + empty := p.s.tok == tokens.RBrace + body := p.parseBody(tokens.RBrace) if body == nil { + if empty || op == "" { + return nil, false, nil + } + + // The braces may hold a value rather than a body. + failed := p.save() + p.restore(s) + + // The operand can extend past the braces (`{1, 2} & input.s == set()`), + // and parens group rather than delimit, so it is the whole operand that has to be wrapped. + if term := p.parseTermInfixCall(); term != nil { + p.errorBraceLedOperand(loc, p.s.Text(braceOffset, p.s.lastEnd), op) + return nil, false, nil + } + + p.restore(failed) + return nil, false, nil } p.scan() + + if isAmbiguousUnionBody(body) { + // Report, but hand the body back: if the caller is a paren group that + // restores, the error is rolled back with it. + p.errorAmbiguousUnionBody(loc, braceOffset, body, "") + } + return body, true, loc } @@ -1738,7 +1949,12 @@ func (p *Parser) parseLogicalOperand() (Body, bool, *Location) { // body. If the parens don't hold a logical group parseLogicalGroup restores // state and we fall through so parseExpr can handle `(a == b)` as a term. if p.s.tok == tokens.LParen && p.logicalKeywordsActive() && (!negated || p.notBodies) { - if body, explicit, loc, committed := p.parseLogicalGroup(true); committed { + prefix := "" + if negated { + prefix = "not " + } + + if body, explicit, loc, committed := p.parseLogicalGroup(true, prefix); committed { if body == nil { return nil, false, nil } @@ -1777,6 +1993,138 @@ func (p *Parser) parseLogicalOperand() (Body, bool, *Location) { return NewBody(expr), false, expr.Location } +// isAmbiguousUnionBody reports whether b is a single-expression body holding a +// bare infix `|` set union. Written that way, `{ ... | ... }` cannot be told apart +// from a set comprehension; the call form (`or(x, y)`) and the parenthesized form +// (`(x | y)`) can, and are left alone. +func isAmbiguousUnionBody(b Body) bool { + if len(b) == 0 { + return false + } + + // The first expression decides: `{A | B; C}` also reads as a comprehension with + // head A and body `B; C`, so trailing expressions don't disambiguate anything. + terms, ok := b[0].Terms.([]*Term) + if !ok || !Interned.Refs.Or.Equal(b[0].Operator()) { + return false + } + + // The operator's text is `|` for the infix form and `or` for the call form. + if terms[0].Location == nil || string(terms[0].Location.Text) != "|" { + return false + } + + return b[0].Location == nil || !bytes.HasPrefix(bytes.TrimSpace(b[0].Location.Text), []byte("(")) +} + +// errorOperandBraceNeedsBody reports `{...}` in an operand position holding a value instead of expressions. +func (p *Parser) errorOperandBraceNeedsBody(loc *Location, operand []byte, term *Term, prefix string) { + p.hint(fmt.Sprintf("write `%s(%s)` to negate the value, or `%s{%s}` for a body holding it", + prefix, operand, prefix, operand)) + p.errorf(loc, "`{...}` in an operand position must contain expression(s), got: %s", ValueName(braceLedValue(term))) +} + +// braceLedValue returns the value opened by the leading `{` of t. An infix call +// renders its lhs operand first, so `{1, 2} & s` is brace-led by the set; refs are +// left alone, as `{"a": 1}["a"]` is reported as the ref it is. +func braceLedValue(t *Term) Value { + if call, ok := t.Value.(Call); ok && len(call) > 0 { + if bi, ok := BuiltinMap[call[0].String()]; ok && bi.Infix != "" && len(call) == bi.Decl.Arity()+1 { + return braceLedValue(call[1]) + } + } + + return t.Value +} + +// isEmptyObjectTerm reports whether expr is exactly `{}`. In an operand position +// those braces open a body, so an empty one is an empty body - not the empty +// object the term parser read. +func isEmptyObjectTerm(expr *Expr) bool { + if len(expr.With) > 0 { + return false + } + + t, ok := expr.Terms.(*Term) + if !ok { + return false + } + + obj, ok := t.Value.(Object) + + return ok && obj.Len() == 0 +} + +// errorBraceLedOperand reports an `and`/`or` operand whose leading `{` opens a +// value rather than a body. In an operand position the braces are read as an +// explicit body, so the value form has to be parenthesized, on both sides of the +// operator. +func (p *Parser) errorBraceLedOperand(loc *Location, operand []byte, op string) { + p.hint(fmt.Sprintf("wrap the operand to keep the value: `(%s) %s ...`", operand, op)) + p.errorf(loc, "operand of `%s` cannot begin with `{` unless the braces hold a body", op) +} + +func (p *Parser) checkVoidCallOperands(lhs, rhs Body, op string) { + if name, loc := voidCallOperand(lhs); name != "" { + p.errorVoidCallOperand(loc, name, op) + } + + if name, loc := voidCallOperand(rhs); name != "" { + p.errorVoidCallOperand(loc, name, op) + } +} + +func (p *Parser) errorVoidCallOperand(loc *Location, name, op string) { + p.hint(fmt.Sprintf("`%s` produces no value and always succeeds, so the operand can never fail; move it out of the operand, or add an expression that can fail", name)) + p.errorf(loc, "operand of `%s` cannot consist only of calls to `%s`", op, name) +} + +// voidCallOperand returns the name and location of the first void builtin called by +// an operand whose body does nothing else; negated operands are left alone. +func voidCallOperand(body Body) (string, *Location) { + var name string + var loc *Location + + for _, expr := range body { + if expr.Negated || !expr.IsCall() { + return "", nil + } + + bi, ok := BuiltinMap[expr.Operator().String()] + if !ok || bi.Decl == nil || bi.Decl.Result() != nil { + return "", nil + } + + if name == "" { + name, loc = bi.Name, expr.Location + } + } + + return name, loc +} + +// errorParensCannotWrapBody reports `(...)` holding expressions rather than a value. +func (p *Parser) errorParensCannotWrapBody(loc *Location, braces []byte, prefix string) { + p.hint(fmt.Sprintf("drop the parens to keep the body: `%s%s`", prefix, braces)) + p.error(loc, "`(...)` in an operand position cannot contain a body") +} + +func (p *Parser) errorAmbiguousUnionBody(loc *Location, braceOffset int, body Body, prefix string) { + braces := p.s.Text(braceOffset, p.s.lastEnd) + + // Parenthesizing only the union keeps any trailing expressions of the body. + union := string(braces) + if e := body[0].Location; e != nil { + if rel := e.Offset - braceOffset; rel > 0 && rel+len(e.Text) <= len(braces) { + union = fmt.Sprintf("%s(%s)%s", braces[:rel], e.Text, braces[rel+len(e.Text):]) + } + } + + p.hint(fmt.Sprintf("write `%s(%s)` for the comprehension, or `%s%s` for the set union", + prefix, braces, prefix, union)) + p.error(loc, "ambiguous `{ ... | ... }` operand: read as a body holding a set-union expression, not as a comprehension") +} + // isLogicalBody reports whether b is a single-expression body wrapping a // LogicalAnd/LogicalOr node, i.e. the result of a parenthesized or nested group. func isLogicalBody(b Body) bool { @@ -1820,7 +2168,7 @@ func (p *Parser) expectRParen() bool { // operands starting at the current `(`. // // operandContext reports whether the `(` is already an operand of `and`/`or`/`not`. -func (p *Parser) parseLogicalGroup(operandContext bool) (Body, bool, *Location, bool) { +func (p *Parser) parseLogicalGroup(operandContext bool, prefix string) (Body, bool, *Location, bool) { if !p.enter() { return nil, false, nil, true } @@ -1839,17 +2187,10 @@ func (p *Parser) parseLogicalGroup(operandContext bool) (Body, bool, *Location, return nil, false, nil, false } - // A leading `{` is a body only in an operand context; otherwise it's an - // object/set literal and we backtrack to the term parser. - braceLead := p.s.tok == tokens.LBrace - - lhsBody, lhsExplicit, lhsLoc := p.parseLogicalOperand() + lhsBody, lhsExplicit, lhsLoc := p.parseLogicalOperand("") if lhsBody == nil { - // An empty `{}` operand (e.g. `not ({})`) is a body error. - if operandContext && braceLead { - return nil, false, nil, true - } - + // Parens are not an operand, so a `{...}` that can't be a body is a value: + // restore and let the term parser read it, e.g. `not ({})` is an empty object. p.restore(s) return nil, false, nil, false @@ -1899,16 +2240,22 @@ func (p *Parser) parseLogicalGroup(operandContext bool) (Body, bool, *Location, return nil, false, nil, false case lhsExplicit: - // `({ body })` - if !p.expectRParen() { + // `({ body })`: parens don't wrap a body. Without a top-level `and`/`or` + // -- handled above -- the braces are a value, so restore and let the term + // parser read them. + braces := p.s.Text(lhsLoc.Offset, p.s.lastEnd) + p.restore(s) + + probe := p.save() + p.scan() // consume `(` + term := p.parseTerm() + p.restore(probe) + + if term == nil { + p.errorParensCannotWrapBody(openLoc, braces, prefix) return nil, false, nil, true } - if operandContext || p.s.tok == tokens.LogicalAnd || p.s.tok == tokens.LogicalOr { - return lhsBody, true, p.extendLoc(openLoc), true - } - - p.restore(s) return nil, false, nil, false case isLogicalBody(lhsBody): @@ -2064,7 +2411,7 @@ func (p *Parser) parseTermIn(lhs *Term, keyVal bool, offset int) *Term { p.scan() if mhs := p.parseTermRelation(nil, offset); mhs != nil { - if op := p.parseTermOpName(memberWithKeyRef, tokens.In); op != nil { + if op := p.parseTermOpName(Interned.Refs.MemberWithKey, tokens.In); op != nil { if rhs := p.parseTermRelation(nil, p.s.loc.Offset); rhs != nil { call := p.setLoc(CallTerm(op, lhs, mhs, rhs), lhs.Location, offset, p.s.lastEnd) switch p.s.tok { @@ -2081,7 +2428,7 @@ func (p *Parser) parseTermIn(lhs *Term, keyVal bool, offset int) *Term { _ = scanAheadRef(p) - if op := p.parseTermOpName(memberRef, tokens.In); op != nil { + if op := p.parseTermOpName(Interned.Refs.Member, tokens.In); op != nil { if rhs := p.parseTermRelation(nil, p.s.loc.Offset); rhs != nil { call := p.setLoc(CallTerm(op, lhs, rhs), lhs.Location, offset, p.s.lastEnd) switch p.s.tok { @@ -2237,6 +2584,10 @@ func (p *Parser) parseTerm() *Term { var term *Term var unaryMinusLoc *Location + + // Check if an `and`/`or` token is actually a function call (`&`/`|` set built-ins). + scanAheadLogicalCall(p) + switch p.s.tok { case tokens.Null: term = NullTerm().SetLocation(p.s.Loc()) @@ -2864,12 +3215,14 @@ func (p *Parser) parseObject(k *Term, potentialComprehension bool) *Term { return nil } - potentialRelation := true if potentialComprehension { switch p.s.tok { case tokens.RBrace, tokens.Comma: - potentialRelation = false - fallthrough + // This is the only parse available, so return its result as-is: + // backtracking would drop the errors reported here in favour of a + // "non-terminated object" pointing at the value we just parsed + // rather than at the offending token. + return p.parseObjectFinish(k, v, true) case tokens.Or: if term := p.parseObjectFinish(k, v, true); term != nil { return term @@ -2879,16 +3232,14 @@ func (p *Parser) parseObject(k *Term, potentialComprehension bool) *Term { p.restore(s) - if potentialRelation { - v := p.parseTermInfixCallInList() - if v == nil { - return nil - } + v = p.parseTermInfixCallInList() + if v == nil { + return nil + } - switch p.s.tok { - case tokens.RBrace, tokens.Comma: - return p.parseObjectFinish(k, v, false) - } + switch p.s.tok { + case tokens.RBrace, tokens.Comma: + return p.parseObjectFinish(k, v, false) } p.illegal("non-terminated object") @@ -3014,7 +3365,7 @@ func (p *Parser) parseVar() *Term { return NewTerm(p.genwildcard()).SetLocation(p.s.Loc()) } - return VarTerm(p.s.lit).SetLocation(p.s.Loc()) + return NewTerm(InternedVarValue(p.s.lit)).SetLocation(p.s.Loc()) } func (p *Parser) genwildcard() Value { @@ -3049,17 +3400,16 @@ func writeHints(msg *strings.Builder, hints []string) { } func (p *Parser) error(loc *location.Location, reason string) { - msg := reason if len(p.s.hints) > 0 { sb := &strings.Builder{} sb.WriteString(reason) writeHints(sb, p.s.hints) - msg = sb.String() + reason = sb.String() } p.s.errors = append(p.s.errors, &Error{ Code: ParseErr, - Message: msg, + Message: reason, Location: loc, Details: newParserErrorDetail(p.s.s.Bytes(), loc.Offset), }) @@ -3111,8 +3461,6 @@ func (p *Parser) illegalToken() { p.illegal("") } -var noScanOptions []scanner.ScanOption - func (p *Parser) scan() { p.doScan(true, noScanOptions...) } @@ -3143,11 +3491,10 @@ func (p *Parser) doScan(skipws bool, scanOpts ...scanner.ScanOption) { p.s.loc.Text = p.s.Text(pos.Offset, pos.End) p.s.loc.Tabs = pos.Tabs - for _, err := range errs { - p.error(p.s.Loc(), err.Message) - } - if len(errs) > 0 { + for _, err := range errs { + p.error(p.s.Loc(), err.Message) + } p.s.tok = tokens.Illegal } @@ -3164,19 +3511,30 @@ func (p *Parser) doScan(skipws bool, scanOpts ...scanner.ScanOption) { break } - // For backwards compatibility leave a nil - // Text value if there is no text rather than - // an empty string. - var commentText []byte - if len(p.s.lit) > 1 { - commentText = []byte(p.s.lit[1:]) + var comment *Comment + if len(p.s.loc.Text) != 0 { + // if location has text, use that to avoid allocating for string->[]byte + comment = NewComment(commentFromLocText(p.s.loc.Text[1:])) + } else { + comment = NewComment([]byte(p.s.lit[1:])) } - comment := NewComment(commentText) comment.SetLoc(p.s.Loc()) p.s.comments = append(p.s.comments, comment) } } +func commentFromLocText(commentText []byte) []byte { + l := len(commentText) + if l == 1 && commentText[0] == '\r' { + commentText, l = nil, 0 // special case - remove lone '\r' + } + for l > 1 && commentText[l-1] == '\r' { // trim trailing '\r' until the last char + commentText = commentText[:l-1] + l-- + } + return commentText +} + func (p *Parser) save() *state { cpy := *p.s s := *cpy.s @@ -3448,13 +3806,13 @@ func (b *metadataParser) Parse() (result *Annotations, err error) { result.Location = b.loc // recreate original text of entire metadata block for location text attribute - original := bytes.TrimSuffix(b.buf.Bytes(), newlineBytes) - numLines := bytes.Count(original, newlineBytes) + 1 + original := bytes.TrimSuffix(b.buf.Bytes(), []byte("\n")) + numLines := bytes.Count(original, []byte("\n")) + 1 preAlloc := len("# METADATA\n") + len(original) + numLines*2 // '# ' prefix added per line result.Location.Text = append(make([]byte, 0, preAlloc), "# METADATA\n"...) - for line := range bytes.SplitAfterSeq(original, newlineBytes) { + for line := range bytes.SplitAfterSeq(original, []byte("\n")) { result.Location.Text = append(result.Location.Text, "# "...) result.Location.Text = append(result.Location.Text, line...) } @@ -3672,11 +4030,8 @@ var allFutureKeywords map[string]tokens.Token // experimentalFutureKeywords are future keywords that exist in the parser but are // intentionally hidden from the default capabilities advertisement. // They are only activated when a policy imports them AND the active -// capabilities explicitly list them. -var experimentalFutureKeywords = map[string]struct{}{ - "and": {}, - "or": {}, -} +// capabilities explicitly list them. There are currently none. +var experimentalFutureKeywords = map[string]struct{}{} var allFutureKeywordTokens map[tokens.Token]struct{} @@ -3731,21 +4086,18 @@ func (p *Parser) futureImport(imp *Import, allowedFutureKeywords map[string]toke return } keyword := string(kw) - _, ok = allowedFutureKeywords[keyword] - if !ok { - sort.Strings(kwds) // so the error message is stable - p.errorf(imp.Path.Location, "unexpected keyword, must be one of %v", kwds) + if _, ok = allowedFutureKeywords[keyword]; !ok { + p.errorf(imp.Path.Location, "unexpected keyword, must be one of %v", util.Sorted(kwds)) return } - if keyword == "not" { - p.notBodies = true - } - kwds = []string{keyword} // overwrite } for _, kw := range kwds { + if kw == "not" { + p.notBodies = true + } p.s.s.AddKeyword(kw, allowedFutureKeywords[kw]) } } diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/parser_ext.go b/vendor/github.com/open-policy-agent/opa/v1/ast/parser_ext.go index 8b63182c0f..6c8537c222 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/parser_ext.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/parser_ext.go @@ -163,7 +163,6 @@ func MustParseTerm(input string) *Term { // ParseRuleFromBody returns a rule if the body can be interpreted as a rule // definition. Otherwise, an error is returned. func ParseRuleFromBody(module *Module, body Body) (*Rule, error) { - if len(body) != 1 { return nil, errors.New("multiple expressions cannot be used for rule head") } @@ -174,7 +173,6 @@ func ParseRuleFromBody(module *Module, body Body) (*Rule, error) { // ParseRuleFromExpr returns a rule if the expression can be interpreted as a // rule definition. func ParseRuleFromExpr(module *Module, expr *Expr) (*Rule, error) { - if len(expr.With) > 0 { return nil, errors.New("expressions using with keyword cannot be used for rule head") } @@ -224,7 +222,6 @@ func ParseRuleFromExpr(module *Module, expr *Expr) (*Rule, error) { } func parseCompleteRuleFromEq(module *Module, expr *Expr) (rule *Rule, err error) { - // ensure the rule location is set to the expr location // the helper functions called below try to set the location based // on the terms they've been provided but that is not as accurate. @@ -257,15 +254,11 @@ func parseCompleteRuleFromEq(module *Module, expr *Expr) (rule *Rule, err error) // be interpreted as a complete document definition declared with the assignment // operator. func ParseCompleteDocRuleFromAssignmentExpr(module *Module, lhs, rhs *Term) (*Rule, error) { - rule, err := ParseCompleteDocRuleFromEqExpr(module, lhs, rhs) - if err != nil { - return nil, err + if err == nil { + rule.Head.Assign = true } - - rule.Head.Assign = true - - return rule, nil + return rule, err } // ParseCompleteDocRuleFromEqExpr returns a rule if the expression can be @@ -346,10 +339,11 @@ func ParsePartialObjectDocRuleFromEqExpr(module *Module, lhs, rhs *Term) (*Rule, body := NewBody(NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location)) rule := &Rule{ - Location: rhs.Location, - Head: head, - Body: body, - Module: module, + Location: rhs.Location, + Head: head, + Body: body, + Module: module, + generatedBody: true, } return rule, nil @@ -458,7 +452,7 @@ func ParseImports(input string) ([]*Import, error) { if err != nil { return nil, err } - result := []*Import{} + result := make([]*Import, 0, len(stmts)) for _, stmt := range stmts { if imp, ok := stmt.(*Import); ok { result = append(result, imp) @@ -496,12 +490,17 @@ func ParseBody(input string) (Body, error) { // ParseBodyWithOpts returns exactly one body. It does _not_ set SkipRules: true on its own, // but respects whatever ParserOptions it's been given. func ParseBodyWithOpts(input string, popts ParserOptions) (Body, error) { - stmts, _, err := ParseStatementsWithOpts("", input, popts) if err != nil { return nil, err } + if len(stmts) == 1 { + if body, ok := stmts[0].(Body); ok { + return body, nil + } + } + result := Body{} for _, stmt := range stmts { @@ -619,14 +618,7 @@ func ParseRule(input string) (*Rule, error) { // this function expects *exactly* one statement. If multiple // statements are parsed, an error is returned. func ParseStatement(input string) (Statement, error) { - stmts, _, err := ParseStatements("", input) - if err != nil { - return nil, err - } - if len(stmts) != 1 { - return nil, errors.New("expected exactly one statement") - } - return stmts[0], nil + return ParseStatementWithOpts(input, ParserOptions{}) } func ParseStatementWithOpts(input string, popts ParserOptions) (Statement, error) { @@ -640,17 +632,24 @@ func ParseStatementWithOpts(input string, popts ParserOptions) (Statement, error return stmts[0], nil } -// ParseStatements is deprecated. Use ParseStatementWithOpts instead. +// ParseStatements returns a slice of parsed statements. +// +// Deprecated: Use [ParseStatementsWithOpts] instead. func ParseStatements(filename, input string) ([]Statement, []*Comment, error) { return ParseStatementsWithOpts(filename, input, ParserOptions{}) } -// ParseStatementsWithOpts returns a slice of parsed statements. This is the -// default return value from the parser. +// ParseStatementsWithOpts returns a slice of parsed statements. +// This is the default return value from [*Parser.Parse]. func ParseStatementsWithOpts(filename, input string, popts ParserOptions) ([]Statement, []*Comment, error) { + sr := StringReaderPool.Get() + defer StringReaderPool.Put(sr) + + sr.Reset(input) + parser := NewParser(). WithFilename(filename). - WithReader(strings.NewReader(input)). + WithReader(sr). WithProcessAnnotation(popts.ProcessAnnotation). WithFutureKeywords(popts.FutureKeywords...). WithAllFutureKeywords(popts.AllFutureKeywords). @@ -704,7 +703,12 @@ func parseModule(filename string, stmts []Statement, comments []*Comment, regoCo case Body: rule, err := ParseRuleFromBody(mod, stmt) if err != nil { - errs = append(errs, NewError(ParseErr, stmt[0].Location, "%s", err.Error())) + msg := err.Error() + if kw, ok := missingHeadKeyword(mod.regoVersion, stmt, stmts, i+1); ok { + msg = fmt.Sprintf("%s (hint: `import future.keywords.%s` for `%s` rules)", + msg, kw, headFutureKeywords[kw]) + } + errs = append(errs, NewError(ParseErr, stmt[0].Location, "%s", msg)) continue } rule.generatedBody = true @@ -748,6 +752,66 @@ func parseModule(filename string, stmts []Statement, comments []*Comment, regoCo return mod, nil } +// headFutureKeywords are the future keywords used in a rule head, mapped to an +// example of the rule form each enables. +var headFutureKeywords = map[string]string{ + "if": "p if { ... }", + "contains": "p contains x", +} + +// missingHeadKeyword reports the rule-head future keyword a statement was +// misparsed around. Unimported, the keyword is just a var, so `p if { ... }` +// parses as the body `p` followed by a rule named `if`. idx is stmt's index +// within stmts. +func missingHeadKeyword(v RegoVersion, stmt Body, stmts []Statement, idx int) (string, bool) { + // From v1 on these are ordinary keywords. + if v != RegoV0 { + return "", false + } + + if kw, ok := statementHeadKeyword(stmt); ok { + return kw, true + } + + // The keyword starts its own statement; check the next one on the same line. + if idx+1 >= len(stmts) { + return "", false + } + next := stmts[idx+1] + if next.Loc() == nil || stmt.Loc() == nil || next.Loc().Row != stmt.Loc().Row { + return "", false + } + + return statementHeadKeyword(next) +} + +// statementHeadKeyword returns the rule-head future keyword a statement was +// reduced to: a rule named after it, or a body holding only it as a var. +func statementHeadKeyword(stmt Statement) (string, bool) { + var name Var + + switch stmt := stmt.(type) { + case *Rule: + name = stmt.Head.Name + case Body: + if len(stmt) != 1 { + return "", false + } + term, ok := stmt[0].Terms.(*Term) + if !ok { + return "", false + } + if name, ok = term.Value.(Var); !ok { + return "", false + } + default: + return "", false + } + + _, ok := headFutureKeywords[string(name)] + return string(name), ok +} + func ruleDeclarationHasKeyword(rule *Rule, keyword tokens.Token) bool { return slices.Contains(rule.Head.keywords, keyword) } diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/performance.go b/vendor/github.com/open-policy-agent/opa/v1/ast/performance.go index 564ee255d1..bea48c701c 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/performance.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/performance.go @@ -5,18 +5,36 @@ package ast import ( "encoding" + "slices" "strings" "sync" ) -var builtinNamesByNumParts = sync.OnceValue(func() map[int][]string { - m := map[int][]string{} +// builtinNameShape packs the two properties of a dotted built-in name that are cheap +// to derive from a ref without allocating — its number of parts and its total length — +// into a single key, which keeps map lookups on the runtime's fast path for 64-bit +// keys. uint64 rather than int so that the shift is well defined on 32-bit platforms. +func builtinNameShape(parts, totalLen int) uint64 { + return uint64(parts)<<32 | uint64(totalLen) +} + +// builtinNamesByShape groups multi-part built-in names by shape, so that +// BuiltinNameFromRef only has to compare against names that could possibly match. +// Bucketing on length as well as part count narrows the candidates from ~100 names +// to a handful, and sorting keeps the scan order deterministic: ranging over +// BuiltinMap yields a fresh random order in every process, which would otherwise +// make the cost of a lookup vary several-fold from one run to the next. +var builtinNamesByShape = sync.OnceValue(func() map[uint64][]string { + m := map[uint64][]string{} for name := range BuiltinMap { - parts := strings.Count(name, ".") + 1 - if parts > 1 { - m[parts] = append(m[parts], name) + if parts := strings.Count(name, ".") + 1; parts > 1 { + shape := builtinNameShape(parts, len(name)) + m[shape] = append(m[shape], name) } } + for _, names := range m { + slices.Sort(names) + } return m }) @@ -50,17 +68,12 @@ func BuiltinNameFromRef(ref Ref) (string, bool) { totalLen += 1 + len(term.Value.(String)) // account for dot } - matched, ok := builtinNamesByNumParts()[reflen] + matched, ok := builtinNamesByShape()[builtinNameShape(reflen, totalLen)] if !ok { return "", false } for _, name := range matched { - // This check saves us a huge amount of work, as only very few built-in - // names will have the exact same length as the ref we are checking. - if len(name) != totalLen { - continue - } // Example: `name` is "io.jwt.decode" (and so is ref) // The first part is varName, which have already been established to be 'io': // io, jwt.decode io == io diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/policy.go b/vendor/github.com/open-policy-agent/opa/v1/ast/policy.go index c5592d99b8..f66e188b8a 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/policy.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/policy.go @@ -36,6 +36,8 @@ var FunctionArgRootDocument = VarTerm("args") // features. var FutureRootDocument = VarTerm("future") +var FutureKeywordsRef = Ref{FutureRootDocument, InternedTerm("keywords")} + // RegoRootDocument names the document containing new, to-become-default, // features in a future versioned release. var RegoRootDocument = VarTerm("rego") @@ -55,11 +57,15 @@ var RootDocumentNames = NewSet( // All refs to data in the policy engine's storage layer are prefixed with this ref. var DefaultRootRef = Ref{DefaultRootDocument} +var DefaultRootRefTerm = NewTerm(DefaultRootRef) + // InputRootRef is a reference to the root of the input document. // // All refs to query arguments are prefixed with this ref. var InputRootRef = Ref{InputRootDocument} +var InputRootRefTerm = NewTerm(InputRootRef) + // SchemaRootRef is a reference to the root of the schema document. // // All refs to schema documents are prefixed with this ref. Note, the schema @@ -69,10 +75,7 @@ var SchemaRootRef = Ref{SchemaRootDocument} // RootDocumentRefs contains the prefixes of top-level documents that all // non-local references start with. -var RootDocumentRefs = NewSet( - NewTerm(DefaultRootRef), - NewTerm(InputRootRef), -) +var RootDocumentRefs = NewSet(DefaultRootRefTerm, InputRootRefTerm) // SystemDocumentKey is the name of the top-level key that identifies the system // document. @@ -224,7 +227,6 @@ type ( // Rule represents a rule as defined in the language. Rules define the // content of documents that represent policy decisions. Rule struct { - Default bool `json:"default,omitempty"` Head *Head `json:"head"` Body Body `json:"body"` Else *Rule `json:"else,omitempty"` @@ -237,6 +239,7 @@ type ( // on the rule (e.g., printing, comparison, visiting, etc.) Module *Module `json:"-"` + Default bool `json:"default,omitempty"` generatedBody bool } @@ -338,13 +341,13 @@ func (mod *Module) Compare(other *Module) int { if cmp := mod.Package.Compare(other.Package); cmp != 0 { return cmp } - if cmp := importsCompare(mod.Imports, other.Imports); cmp != 0 { + if cmp := slices.CompareFunc(mod.Imports, other.Imports, (*Import).Compare); cmp != 0 { return cmp } - if cmp := annotationsCompare(mod.Annotations, other.Annotations); cmp != 0 { + if cmp := slices.CompareFunc(mod.Annotations, other.Annotations, (*Annotations).Compare); cmp != 0 { return cmp } - return rulesCompare(mod.Rules, other.Rules) + return slices.CompareFunc(mod.Rules, other.Rules, (*Rule).Compare) } // Copy returns a deep copy of mod. @@ -389,7 +392,7 @@ func (mod *Module) Copy() *Module { // Equal returns true if mod equals other. func (mod *Module) Equal(other *Module) bool { - return mod.Compare(other) == 0 + return mod == other || mod.Compare(other) == 0 } func (mod *Module) String() string { @@ -450,8 +453,7 @@ func (c *Comment) String() string { // Copy returns a deep copy of c. func (c *Comment) Copy() *Comment { cpy := *c - cpy.Text = make([]byte, len(c.Text)) - copy(cpy.Text, c.Text) + cpy.Text = slices.Clone(c.Text) return &cpy } @@ -459,13 +461,13 @@ func (c *Comment) Copy() *Comment { // Unlike other equality checks on AST nodes, comment equality // depends on location. func (c *Comment) Equal(other *Comment) bool { - return c.Location.Equal(other.Location) && bytes.Equal(c.Text, other.Text) + return c == other || (c.Location.Equal(other.Location) && bytes.Equal(c.Text, other.Text)) } // Compare returns an integer indicating whether pkg is less than, equal to, // or greater than other. func (pkg *Package) Compare(other *Package) int { - return termSliceCompare(pkg.Path, other.Path) + return slices.CompareFunc(pkg.Path, other.Path, TermValueCompare) } // Copy returns a deep copy of pkg. @@ -477,7 +479,7 @@ func (pkg *Package) Copy() *Package { // Equal returns true if pkg is equal to other. func (pkg *Package) Equal(other *Package) bool { - return pkg.Compare(other) == 0 + return pkg == other || pkg.Compare(other) == 0 } // Loc returns the location of the Package in the definition. @@ -510,10 +512,8 @@ func IsValidImportPath(v Value) (err error) { if err := IsValidImportPath(v[0].Value); err != nil { return fmt.Errorf("invalid path %v: path must begin with input or data", v) } - for _, e := range v[1:] { - if _, ok := e.Value.(String); !ok { - return fmt.Errorf("invalid path %v: path elements must be strings", v) - } + if !util.Every(v[1:], TermValueIs[String]) { + return fmt.Errorf("invalid path %v: path elements must be strings", v) } default: return fmt.Errorf("invalid path %v: path must be ref or var", v) @@ -548,7 +548,7 @@ func (imp *Import) Copy() *Import { // Equal returns true if imp is equal to other. func (imp *Import) Equal(other *Import) bool { - return imp.Compare(other) == 0 + return imp == other || imp.Compare(other) == 0 } // Loc returns the location of the Import in the definition. @@ -612,7 +612,7 @@ func (rule *Rule) Compare(other *Rule) int { return cmp } - if cmp := annotationsCompare(rule.Annotations, other.Annotations); cmp != 0 { + if cmp := slices.CompareFunc(rule.Annotations, other.Annotations, (*Annotations).Compare); cmp != 0 { return cmp } @@ -640,7 +640,7 @@ func (rule *Rule) Copy() *Rule { // Equal returns true if rule is equal to other. func (rule *Rule) Equal(other *Rule) bool { - return rule.Compare(other) == 0 + return rule == other || rule.Compare(other) == 0 } // Loc returns the location of the Rule in the definition. @@ -822,19 +822,19 @@ func (head *Head) Compare(other *Head) int { } else if !head.Assign && other.Assign { return 1 } - if cmp := termSliceCompare(head.Args, other.Args); cmp != 0 { + if cmp := slices.CompareFunc(head.Args, other.Args, TermValueCompare); cmp != 0 { return cmp } - if cmp := termSliceCompare(head.Reference, other.Reference); cmp != 0 { + if cmp := slices.CompareFunc(head.Reference, other.Reference, TermValueCompare); cmp != 0 { return cmp } if cmp := VarCompare(head.Name, other.Name); cmp != 0 { return cmp } - if cmp := Compare(head.Key, other.Key); cmp != 0 { + if cmp := TermValueCompare(head.Key, other.Key); cmp != 0 { return cmp } - return Compare(head.Value, other.Value) + return TermValueCompare(head.Value, other.Value) } // Copy returns a deep copy of head. @@ -851,7 +851,7 @@ func (head *Head) Copy() *Head { // Equal returns true if this head equals other. func (head *Head) Equal(other *Head) bool { - return head.Compare(other) == 0 + return head == other || head.Compare(other) == 0 } func (head *Head) String() string { @@ -965,11 +965,7 @@ func (body Body) Compare(other Body) int { // Copy returns a deep copy of body. func (body Body) Copy() Body { - cpy := make(Body, len(body)) - for i := range body { - cpy[i] = body[i].Copy() - } - return cpy + return util.Map(body, (*Expr).Copy) } // Contains returns true if this body contains the given expression. @@ -979,7 +975,7 @@ func (body Body) Contains(x *Expr) bool { // Equal returns true if this Body is equal to the other Body. func (body Body) Equal(other Body) bool { - return body.Compare(other) == 0 + return slices.EqualFunc(body, other, (*Expr).Equal) } // Hash returns the hash code for the Body. @@ -993,12 +989,7 @@ func (body Body) Hash() int { // IsGround returns true if all of the expressions in the Body are ground. func (body Body) IsGround() bool { - for _, e := range body { - if !e.IsGround() { - return false - } - } - return true + return util.Every(body, (*Expr).IsGround) } // Loc returns the location of the Body in the definition. @@ -1070,7 +1061,7 @@ func (expr *Expr) ComplementNoWith() *Expr { // Equal returns true if this Expr equals the other Expr. func (expr *Expr) Equal(other *Expr) bool { - return expr.Compare(other) == 0 + return expr == other || expr.Compare(other) == 0 } // Compare returns an integer indicating whether expr is less than, equal to, @@ -1085,13 +1076,13 @@ func (expr *Expr) Equal(other *Expr) bool { // // Otherwise, the expression terms are compared normally. If both expressions // have the same terms, the modifiers are compared. -func (expr *Expr) Compare(other *Expr) int { - if expr == nil { - if other == nil { - return 0 - } +func (expr *Expr) Compare(other *Expr) (c int) { + switch { + case expr == other: + return 0 + case expr == nil: return -1 - } else if other == nil { + case other == nil: return 1 } @@ -1119,36 +1110,25 @@ func (expr *Expr) Compare(other *Expr) int { switch t := expr.Terms.(type) { case *Term: - if cmp := t.Value.Compare(other.Terms.(*Term).Value); cmp != 0 { - return cmp - } + c = TermValueCompare(t, other.Terms.(*Term)) case []*Term: - if cmp := termSliceCompare(t, other.Terms.([]*Term)); cmp != 0 { - return cmp - } + c = slices.CompareFunc(t, other.Terms.([]*Term), TermValueCompare) case *SomeDecl: - if cmp := Compare(t, other.Terms.(*SomeDecl)); cmp != 0 { - return cmp - } + c = t.Compare(other.Terms.(*SomeDecl)) case *Every: - if cmp := Compare(t, other.Terms.(*Every)); cmp != 0 { - return cmp - } + c = t.Compare(other.Terms.(*Every)) case *Not: - if cmp := t.Compare(other.Terms.(*Not)); cmp != 0 { - return cmp - } + c = t.Compare(other.Terms.(*Not)) case *LogicalAnd: - if cmp := Compare(t, other.Terms.(*LogicalAnd)); cmp != 0 { - return cmp - } + c = t.Compare(other.Terms.(*LogicalAnd)) case *LogicalOr: - if cmp := Compare(t, other.Terms.(*LogicalOr)); cmp != 0 { - return cmp - } + c = t.Compare(other.Terms.(*LogicalOr)) } - return withSliceCompare(expr.With, other.With) + if c == 0 { + c = slices.CompareFunc(expr.With, other.With, (*With).Compare) + } + return c } func (expr *Expr) sortOrder() int { @@ -1187,7 +1167,6 @@ func (expr *Expr) CopyWithoutTerms() *Expr { // Copy returns a deep copy of expr. func (expr *Expr) Copy() *Expr { - cpy := expr.CopyWithoutTerms() switch ts := expr.Terms.(type) { @@ -1217,9 +1196,7 @@ func (expr *Expr) Hash() int { case *SomeDecl: s += ts.Hash() case []*Term: - for _, t := range ts { - s += t.Value.Hash() - } + s += termSliceHash(ts) case *Term: s += ts.Value.Hash() case *LogicalAnd: @@ -1308,7 +1285,11 @@ func (expr *Expr) Operator() Ref { if op == nil { return nil } - return op.Value.(Ref) + ref, ok := op.Value.(Ref) + if !ok { + return nil + } + return ref } // OperatorTerm returns the name of the function or built-in this expression @@ -1348,10 +1329,8 @@ func (expr *Expr) Operands() []*Term { func (expr *Expr) IsGround() bool { switch ts := expr.Terms.(type) { case []*Term: - for _, t := range ts[1:] { - if !t.IsGround() { - return false - } + if !util.Every(ts[1:], (*Term).IsGround) { + return false } case *Term: return ts.IsGround() @@ -1476,7 +1455,7 @@ func (d *SomeDecl) Copy() *SomeDecl { // Compare returns an integer indicating whether d is less than, equal to, or // greater than other. func (d *SomeDecl) Compare(other *SomeDecl) int { - return termSliceCompare(d.Symbols, other.Symbols) + return slices.CompareFunc(d.Symbols, other.Symbols, TermValueCompare) } // Hash returns a hash code of d. @@ -1485,17 +1464,19 @@ func (d *SomeDecl) Hash() int { } func (q *Every) String() string { + b := bytes.NewBufferString("every ") if q.Key != nil { - return fmt.Sprintf("every %s, %s in %s { %s }", - q.Key, - q.Value, - q.Domain, - q.Body) + util.WriteAppender(b, q.Key) + b.WriteString(", ") } - return fmt.Sprintf("every %s in %s { %s }", - q.Value, - q.Domain, - q.Body) + util.WriteAppender(b, q.Value) + b.WriteString(" in ") + util.WriteAppender(b, q.Domain) + b.WriteString(" { ") + util.WriteAppender(b, q.Body) + b.WriteString(" }") + + return b.String() } func (q *Every) Loc() *Location { @@ -1522,7 +1503,7 @@ func (q *Every) Compare(other *Every) int { {q.Value, other.Value}, {q.Domain, other.Domain}, } { - if d := Compare(terms[0], terms[1]); d != 0 { + if d := TermValueCompare(terms[0], terms[1]); d != 0 { return d } } @@ -1632,7 +1613,7 @@ func logicalOperandNeedsParens(b Body, parentOp string, rhs bool) bool { return true } - switch e.Terms.(type) { + switch t := e.Terms.(type) { case *LogicalOr: // `or` binds looser than `and`: always parenthesize under `and`; under // `or`, parenthesize only the rhs to preserve right-nesting. @@ -1641,6 +1622,8 @@ func logicalOperandNeedsParens(b Body, parentOp string, rhs bool) bool { // `and` binds tighter: no parens under `or`; under `and`, parenthesize // only the rhs to preserve right-nesting. return parentOp == "and" && rhs + case *Term: + return rendersWithLeadingBrace(t.Value) } return false } @@ -1655,10 +1638,33 @@ func notBodyNeedsParens(b Body) bool { return true } - switch e.Terms.(type) { + switch t := e.Terms.(type) { case *LogicalOr, *LogicalAnd: // `not` binds tighter than `and`/`or` return true + case *Not: + // `not not x` doesn't parse: the operand of a `not` must be parenthesized + // for the inner negation to be read back as a body. + return true + case *Term: + return rendersWithLeadingBrace(t.Value) + } + + return false +} + +// rendersWithLeadingBrace reports whether v renders starting with a `{`. Such a +// value needs parens in an operand position, as bare braces there are read as an +// explicit body. +func rendersWithLeadingBrace(v Value) bool { + switch t := v.(type) { + case Set: + // The empty set renders as `set()`. + return t.Len() > 0 + case Object, *SetComprehension, *ObjectComprehension: + return true + case Ref: + return len(t) > 0 && rendersWithLeadingBrace(t[0].Value) } return false @@ -1671,24 +1677,25 @@ func (w *With) String() string { // Equal returns true if this With is equals the other With. func (w *With) Equal(other *With) bool { - return Compare(w, other) == 0 + return w == other || w.Compare(other) == 0 } // Compare returns an integer indicating whether w is less than, equal to, or // greater than other. func (w *With) Compare(other *With) int { + if w == other { + return 0 + } if w == nil { - if other == nil { - return 0 - } return -1 - } else if other == nil { + } + if other == nil { return 1 } - if cmp := Compare(w.Target, other.Target); cmp != 0 { + if cmp := TermValueCompare(w.Target, other.Target); cmp != 0 { return cmp } - return Compare(w.Value, other.Value) + return TermValueCompare(w.Value, other.Value) } // Copy returns a deep copy of w. @@ -1758,6 +1765,12 @@ func Copy(x any) any { return x.Copy() case *ObjectComprehension: return x.Copy() + case *LogicalAnd: + return x.Copy() + case *LogicalOr: + return x.Copy() + case *TemplateString: + return x.Copy() case Set: return x.Copy() case *object: @@ -1798,12 +1811,7 @@ func (rs *RuleSet) Add(rule *Rule) { // Contains returns true if rs contains rule. func (rs RuleSet) Contains(rule *Rule) bool { - for i := range rs { - if rs[i].Equal(rule) { - return true - } - } - return false + return slices.ContainsFunc(rs, rule.Equal) } // Diff returns a new RuleSet containing rules in rs that are not in other. @@ -1824,10 +1832,7 @@ func (rs RuleSet) Equal(other RuleSet) bool { // Merge returns a ruleset containing the union of rules from rs an other. func (rs RuleSet) Merge(other RuleSet) RuleSet { - result := NewRuleSet() - for i := range rs { - result.Add(rs[i]) - } + result := NewRuleSet(rs...) for i := range other { result.Add(other[i]) } @@ -1835,11 +1840,7 @@ func (rs RuleSet) Merge(other RuleSet) RuleSet { } func (rs RuleSet) String() string { - buf := make([]string, 0, len(rs)) - for _, rule := range rs { - buf = append(buf, rule.String()) - } - return "{" + strings.Join(buf, ", ") + "}" + return "{" + strings.Join(util.Map(rs, (*Rule).String), ", ") + "}" } // Returns true if the equality or assignment expression referred to by expr diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/policy_appenders.go b/vendor/github.com/open-policy-agent/opa/v1/ast/policy_appenders.go index 63b260e0a5..2373d8a89b 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/policy_appenders.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/policy_appenders.go @@ -7,8 +7,8 @@ import ( "github.com/open-policy-agent/opa/v1/util" ) -func (m *Module) AppendText(buf []byte) ([]byte, error) { - if m == nil { +func (mod *Module) AppendText(buf []byte) ([]byte, error) { + if mod == nil { return append(buf, ""...), nil } @@ -17,22 +17,24 @@ func (m *Module) AppendText(buf []byte) ([]byte, error) { // NOTE(anderseknert): this DOES allocate still, and while that's unfortunate, // we'll be better off dealing with that when we have v2 JSON in the stdlib than // doing manual JSON marshalling (and string length calculations) here. - for _, annotations := range m.Annotations { + for _, annotations := range mod.Annotations { // rule annotations are attached to rules, so only check for package scoped ones here if annotations.Scope == "package" || annotations.Scope == "subpackages" { buf = append(buf, "# METADATA\n# "...) - buf = append(buf, annotations.String()...) + if buf, err = annotations.AppendText(buf); err != nil { + return nil, err + } buf = append(buf, '\n') } } - if buf, err = m.Package.AppendText(buf); err != nil { + if buf, err = mod.Package.AppendText(buf); err != nil { return nil, err } buf = append(buf, '\n') - if len(m.Imports) > 0 { - for _, imp := range m.Imports { + if len(mod.Imports) > 0 { + for _, imp := range mod.Imports { buf = append(buf, '\n') if buf, err = imp.AppendText(buf); err != nil { return nil, err @@ -41,10 +43,10 @@ func (m *Module) AppendText(buf []byte) ([]byte, error) { buf = append(buf, '\n') } - if len(m.Rules) > 0 { - for _, rule := range m.Rules { + if len(mod.Rules) > 0 { + for _, rule := range mod.Rules { buf = append(buf, '\n') - if buf, err = rule.appendWithOpts(toStringOpts{regoVersion: m.regoVersion}, buf); err != nil { + if buf, err = rule.appendWithOpts(toStringOpts{regoVersion: mod.regoVersion}, buf); err != nil { return nil, err } } @@ -86,45 +88,48 @@ func (imp *Import) AppendText(buf []byte) ([]byte, error) { return buf, nil } -func (r *Rule) AppendText(buf []byte) ([]byte, error) { +func (rule *Rule) AppendText(buf []byte) ([]byte, error) { regoVersion := DefaultRegoVersion - if r.Module != nil { - regoVersion = r.Module.RegoVersion() + if rule.Module != nil { + regoVersion = rule.Module.RegoVersion() } - return r.appendWithOpts(toStringOpts{regoVersion: regoVersion}, buf) + return rule.appendWithOpts(toStringOpts{regoVersion: regoVersion}, buf) } -func (r *Rule) appendWithOpts(opts toStringOpts, buf []byte) ([]byte, error) { +func (rule *Rule) appendWithOpts(opts toStringOpts, buf []byte) ([]byte, error) { // See note in [Module.AppendText] regarding annotations. - for _, annotations := range r.Annotations { + for _, annotations := range rule.Annotations { buf = append(buf, "# METADATA\n# "...) - buf = append(buf, annotations.String()...) + var err error + if buf, err = annotations.AppendText(buf); err != nil { + return nil, err + } buf = append(buf, '\n') } - if r.Default { + if rule.Default { buf = append(buf, "default "...) } var err error - if buf, err = r.Head.appendWithOpts(opts, buf); err != nil { + if buf, err = rule.Head.appendWithOpts(opts, buf); err != nil { return nil, err } - if !r.Default { + if !rule.Default { switch opts.RegoVersion() { case RegoV1, RegoV0CompatV1: buf = append(buf, " if { "...) default: buf = append(buf, " { "...) } - if buf, err = r.Body.AppendText(buf); err != nil { + if buf, err = rule.Body.AppendText(buf); err != nil { return nil, err } buf = append(buf, " }"...) } - if r.Else != nil { - if buf, err = r.Else.appendElse(opts, buf); err != nil { + if rule.Else != nil { + if buf, err = rule.Else.appendElse(opts, buf); err != nil { return nil, err } } @@ -132,13 +137,13 @@ func (r *Rule) appendWithOpts(opts toStringOpts, buf []byte) ([]byte, error) { return buf, nil } -func (r *Rule) appendElse(opts toStringOpts, buf []byte) ([]byte, error) { +func (rule *Rule) appendElse(opts toStringOpts, buf []byte) ([]byte, error) { buf = append(buf, " else "...) var err error - if r.Head.Value != nil { + if rule.Head.Value != nil { buf = append(buf, "= "...) - if buf, err = r.Head.Value.AppendText(buf); err != nil { + if buf, err = rule.Head.Value.AppendText(buf); err != nil { return nil, err } } @@ -148,13 +153,13 @@ func (r *Rule) appendElse(opts toStringOpts, buf []byte) ([]byte, error) { } else { buf = append(buf, " { "...) } - if buf, err = r.Body.AppendText(buf); err != nil { + if buf, err = rule.Body.AppendText(buf); err != nil { return nil, err } buf = append(buf, " }"...) - if r.Else != nil { - if buf, err = r.Else.appendElse(opts, buf); err != nil { + if rule.Else != nil { + if buf, err = rule.Else.appendElse(opts, buf); err != nil { return nil, err } } @@ -162,52 +167,52 @@ func (r *Rule) appendElse(opts toStringOpts, buf []byte) ([]byte, error) { return buf, nil } -func (h *Head) AppendText(buf []byte) ([]byte, error) { - return h.appendWithOpts(toStringOpts{}, buf) +func (head *Head) AppendText(buf []byte) ([]byte, error) { + return head.appendWithOpts(toStringOpts{}, buf) } -func (h *Head) appendWithOpts(opts toStringOpts, buf []byte) ([]byte, error) { +func (head *Head) appendWithOpts(opts toStringOpts, buf []byte) ([]byte, error) { var err error - if h.Reference == nil { - buf = append(buf, h.Name...) + if head.Reference == nil { + buf = append(buf, head.Name...) } else { - if buf, err = h.Reference.AppendText(buf); err != nil { + if buf, err = head.Reference.AppendText(buf); err != nil { return nil, err } } containsAdded := false switch { - case len(h.Args) != 0: - if buf, err = h.Args.AppendText(buf); err != nil { + case len(head.Args) != 0: + if buf, err = head.Args.AppendText(buf); err != nil { return nil, err } - case len(h.Reference) == 1 && h.Key != nil: + case len(head.Reference) == 1 && head.Key != nil: switch opts.RegoVersion() { case RegoV0: buf = append(buf, '[') - if buf, err = h.Key.AppendText(buf); err != nil { + if buf, err = head.Key.AppendText(buf); err != nil { return nil, err } buf = append(buf, ']') default: - if buf, err = h.Key.AppendText(append(buf, " contains "...)); err != nil { + if buf, err = head.Key.AppendText(append(buf, " contains "...)); err != nil { return nil, err } containsAdded = true } } - if h.Value != nil { - if h.Assign { + if head.Value != nil { + if head.Assign { buf = append(buf, " := "...) } else { buf = append(buf, " = "...) } - if buf, err = h.Value.AppendText(buf); err != nil { + if buf, err = head.Value.AppendText(buf); err != nil { return nil, err } - } else if !containsAdded && h.Name == "" && h.Key != nil { - if buf, err = h.Key.AppendText(append(buf, " contains "...)); err != nil { + } else if !containsAdded && head.Name == "" && head.Key != nil { + if buf, err = head.Key.AppendText(append(buf, " contains "...)); err != nil { return nil, err } } @@ -275,20 +280,20 @@ func (w *With) AppendText(buf []byte) ([]byte, error) { return buf, nil } -func (w *Every) AppendText(buf []byte) ([]byte, error) { +func (q *Every) AppendText(buf []byte) ([]byte, error) { buf = append(buf, "every "...) var err error - if w.Key != nil { - if buf, err = w.Key.AppendText(buf); err != nil { + if q.Key != nil { + if buf, err = q.Key.AppendText(buf); err != nil { return nil, err } buf = append(buf, ", "...) } - if buf, err = w.Value.AppendText(buf); err == nil { + if buf, err = q.Value.AppendText(buf); err == nil { buf = append(buf, " in "...) - if buf, err = w.Domain.AppendText(buf); err == nil { + if buf, err = q.Domain.AppendText(buf); err == nil { buf = append(buf, " { "...) - if buf, err = w.Body.AppendText(buf); err == nil { + if buf, err = q.Body.AppendText(buf); err == nil { buf = append(buf, " }"...) } } diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/policy_jsonv2.go b/vendor/github.com/open-policy-agent/opa/v1/ast/policy_jsonv2.go index faf1b55df7..cb2b6fac5c 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/policy_jsonv2.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/policy_jsonv2.go @@ -66,13 +66,13 @@ func (a Args) MarshalJSONTo(e *jsontext.Encoder) error { // Rego source rather than as JSON. Module's own fields are fully described by // their struct tags, so the encoding is left to them, as it is pre-1.27. The // field types provide their own MarshalJSONTo where one is needed. -func (m *Module) MarshalJSONTo(e *jsontext.Encoder) error { +func (mod *Module) MarshalJSONTo(e *jsontext.Encoder) error { // Declare a new type and use a type conversion to avoid recursively calling // Module#MarshalJSONTo. It's the highest precedence marshaller, so there is // nothing below it to fall to, and the new type has no methods of its own. type module Module - return json.MarshalEncode(e, (*module)(m)) + return json.MarshalEncode(e, (*module)(mod)) } func (pkg *Package) MarshalJSONTo(e *jsontext.Encoder) error { @@ -91,57 +91,57 @@ func (pkg *Package) MarshalJSONTo(e *jsontext.Encoder) error { return e.WriteToken(jsontext.EndObject) } -func (i *Import) MarshalJSONTo(e *jsontext.Encoder) error { +func (imp *Import) MarshalJSONTo(e *jsontext.Encoder) error { e.WriteToken(jsontext.BeginObject) - if err := jsonv2.WriteField(e, "path", i.Path); err != nil { + if err := jsonv2.WriteField(e, "path", imp.Path); err != nil { return err } - if astJSON.GetOptions().MarshalOptions.IncludeLocation.Import && i.Location != nil { - if err := jsonv2.WriteField(e, "location", i.Location); err != nil { + if astJSON.GetOptions().MarshalOptions.IncludeLocation.Import && imp.Location != nil { + if err := jsonv2.WriteField(e, "location", imp.Location); err != nil { return err } } - if len(i.Alias) > 0 { + if len(imp.Alias) > 0 { e.WriteToken(jsontext.String("alias")) - e.WriteToken(jsontext.String(string(i.Alias))) + e.WriteToken(jsontext.String(string(imp.Alias))) } return e.WriteToken(jsontext.EndObject) } -func (r *Rule) MarshalJSONTo(e *jsontext.Encoder) error { +func (rule *Rule) MarshalJSONTo(e *jsontext.Encoder) error { e.WriteToken(jsontext.BeginObject) - if r.Default { + if rule.Default { e.WriteToken(jsontext.String("default")) e.WriteToken(jsontext.True) } - if r.Else != nil { - if err := jsonv2.WriteField(e, "else", r.Else); err != nil { + if rule.Else != nil { + if err := jsonv2.WriteField(e, "else", rule.Else); err != nil { return err } } - if err := jsonv2.WriteField(e, "head", r.Head); err != nil { + if err := jsonv2.WriteField(e, "head", rule.Head); err != nil { return err } - if err := jsonv2.WriteField(e, "body", r.Body); err != nil { + if err := jsonv2.WriteField(e, "body", rule.Body); err != nil { return err } - if len(r.Annotations) > 0 { - if err := jsonv2.WriteFieldArray(e, "annotations", r.Annotations); err != nil { + if len(rule.Annotations) > 0 { + if err := jsonv2.WriteFieldArray(e, "annotations", rule.Annotations); err != nil { return err } } - if astJSON.GetOptions().MarshalOptions.IncludeLocation.Rule && r.Location != nil { - if err := jsonv2.WriteField(e, "location", r.Location); err != nil { + if astJSON.GetOptions().MarshalOptions.IncludeLocation.Rule && rule.Location != nil { + if err := jsonv2.WriteField(e, "location", rule.Location); err != nil { return err } } @@ -149,43 +149,43 @@ func (r *Rule) MarshalJSONTo(e *jsontext.Encoder) error { return e.WriteToken(jsontext.EndObject) } -func (h *Head) MarshalJSONTo(e *jsontext.Encoder) error { +func (head *Head) MarshalJSONTo(e *jsontext.Encoder) error { e.WriteToken(jsontext.BeginObject) - if h.Name != "" { + if head.Name != "" { e.WriteToken(jsontext.String("name")) - e.WriteToken(jsontext.String(string(h.Name))) + e.WriteToken(jsontext.String(string(head.Name))) } - if err := jsonv2.WriteField(e, "ref", h.Ref()); err != nil { + if err := jsonv2.WriteField(e, "ref", head.Ref()); err != nil { return err } - if len(h.Args) > 0 { - if err := jsonv2.WriteFieldArray(e, "args", h.Args); err != nil { + if len(head.Args) > 0 { + if err := jsonv2.WriteFieldArray(e, "args", head.Args); err != nil { return err } } - if h.Key != nil { - if err := jsonv2.WriteField(e, "key", h.Key); err != nil { + if head.Key != nil { + if err := jsonv2.WriteField(e, "key", head.Key); err != nil { return err } } - if h.Value != nil { - if err := jsonv2.WriteField(e, "value", h.Value); err != nil { + if head.Value != nil { + if err := jsonv2.WriteField(e, "value", head.Value); err != nil { return err } } - if h.Assign { + if head.Assign { e.WriteToken(jsontext.String("assign")) e.WriteToken(jsontext.True) } - if astJSON.GetOptions().MarshalOptions.IncludeLocation.Head && h.Location != nil { - if err := jsonv2.WriteField(e, "location", h.Location); err != nil { + if astJSON.GetOptions().MarshalOptions.IncludeLocation.Head && head.Location != nil { + if err := jsonv2.WriteField(e, "location", head.Location); err != nil { return err } } @@ -258,8 +258,8 @@ func (q *Every) MarshalJSONTo(e *jsontext.Encoder) error { return e.WriteToken(jsontext.EndObject) } -func (b Body) MarshalJSONTo(e *jsontext.Encoder) error { - return jsonv2.WriteMarshalerToArray(e, b) +func (body Body) MarshalJSONTo(e *jsontext.Encoder) error { + return jsonv2.WriteMarshalerToArray(e, body) } // MarshalJSON returns JSON encoded bytes representing body. @@ -280,46 +280,46 @@ func (expr *Expr) UnmarshalJSON(bs []byte) error { return unmarshalExpr(expr, v) } -func (e *Expr) MarshalJSONTo(enc *jsontext.Encoder) error { +func (expr *Expr) MarshalJSONTo(enc *jsontext.Encoder) error { enc.WriteToken(jsontext.BeginObject) enc.WriteToken(jsontext.String("index")) - enc.WriteToken(jsontext.Int(int64(e.Index))) + enc.WriteToken(jsontext.Int(int64(expr.Index))) includeLocation := astJSON.GetOptions().MarshalOptions.IncludeLocation - if e.Location != nil && includeLocation.Expr { - if err := jsonv2.WriteField(enc, "location", e.Location); err != nil { + if expr.Location != nil && includeLocation.Expr { + if err := jsonv2.WriteField(enc, "location", expr.Location); err != nil { return err } } - if e.Negated { + if expr.Negated { enc.WriteToken(jsontext.String("negated")) enc.WriteToken(jsontext.True) } - if e.Generated { + if expr.Generated { enc.WriteToken(jsontext.String("generated")) enc.WriteToken(jsontext.True) } enc.WriteToken(jsontext.String("terms")) var err error - switch t := e.Terms.(type) { + switch t := expr.Terms.(type) { case []*Term: err = jsonv2.WriteMarshalerToArrayOrNull(enc, t) case json.MarshalerTo: err = t.MarshalJSONTo(enc) default: - return fmt.Errorf("unsupported expr terms type: %T", e.Terms) + return fmt.Errorf("unsupported expr terms type: %T", expr.Terms) } if err != nil { return fmt.Errorf("failed to marshal expr terms: %w", err) } - if len(e.With) > 0 { - if err := jsonv2.WriteFieldArray(enc, "with", e.With); err != nil { + if len(expr.With) > 0 { + if err := jsonv2.WriteFieldArray(enc, "with", expr.With); err != nil { return err } } diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/rego_v1.go b/vendor/github.com/open-policy-agent/opa/v1/ast/rego_v1.go index db9e0f722c..64fe23bb50 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/rego_v1.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/rego_v1.go @@ -33,14 +33,15 @@ func checkRootDocumentOverrides(node any) Errors { } if ReservedVars.Contains(name) { - errors = append(errors, NewError(CompileErr, rule.Location, "rules must not shadow %v (use a different rule name)", name)) + errors = append(errors, + NewError(CompileErr, rule.Location, "rules must not shadow %v (use a different rule name)", name)) } for _, arg := range rule.Head.Args { - if _, ok := arg.Value.(Ref); ok { - if RootDocumentRefs.Contains(arg) { - errors = append(errors, NewError(CompileErr, arg.Location, "args must not shadow %v (use a different variable name)", arg)) - } + if _, ok := arg.Value.(Ref); ok && RootDocumentRefs.Contains(arg) { + errors = append(errors, NewError( + CompileErr, arg.Location, "args must not shadow %v (use a different variable name)", arg, + )) } } @@ -49,11 +50,12 @@ func checkRootDocumentOverrides(node any) Errors { WalkExprs(node, func(expr *Expr) bool { if expr.IsAssignment() { - // assign() can be called directly, so we need to assert its given first operand exists before checking its name. + // assign() can be called directly, so assert its given first operand exists before checking its name. if nameOp := expr.Operand(0); nameOp != nil { - name := Var(nameOp.String()) - if ReservedVars.Contains(name) { - errors = append(errors, NewError(CompileErr, expr.Location, "variables must not shadow %v (use a different variable name)", name)) + if name := Var(nameOp.String()); ReservedVars.Contains(name) { + errors = append(errors, NewError( + CompileErr, expr.Location, "variables must not shadow %v (use a different variable name)", name, + )) } } } diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/slices.go b/vendor/github.com/open-policy-agent/opa/v1/ast/slices.go deleted file mode 100644 index 5921ec0ca1..0000000000 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/slices.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2026 The OPA Authors. All rights reserved. -// Use of this source code is governed by an Apache2 -// license that can be found in the LICENSE file. - -package ast - -// CountFunc counts the number of items in a slice S that satisfy predicate function f. -func CountFunc[T any, S ~[]T](items S, f func(T) bool) (n int) { - for i := range items { - if f(items[i]) { - n++ - } - } - return n -} diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/string_length.go b/vendor/github.com/open-policy-agent/opa/v1/ast/string_length.go index 09247effa6..b72079ecb4 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/string_length.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/string_length.go @@ -27,17 +27,16 @@ func TermSliceStringLength(terms []*Term, delimLen int) (n int) { return max(n-delimLen, 0) } -func (t *Term) StringLength() int { - if sl, ok := t.Value.(StringLengther); ok { +func (term *Term) StringLength() int { + if sl, ok := term.Value.(StringLengther); ok { return sl.StringLength() } - - panic("expected all ast.Value types to implement StringLenghter interface, got: " + ValueName(t.Value)) + panic("expected all ast.Value types to implement StringLenghter interface, got: " + ValueName(term.Value)) } -func (s String) StringLength() int { +func (str String) StringLength() int { n := 2 // surrounding quotes - bs := util.StringToByteSlice(s) + bs := util.StringToByteSlice(str) for i := 0; i < len(bs); { r, size := utf8.DecodeRune(bs[i:]) switch r { @@ -57,12 +56,12 @@ func (s String) StringLength() int { return n } -func (n Number) StringLength() int { - return len(n) +func (num Number) StringLength() int { + return len(num) } -func (b Boolean) StringLength() int { - if b { +func (bol Boolean) StringLength() int { + if bol { return 4 } return 5 @@ -80,27 +79,27 @@ func (s *set) StringLength() int { return TermSliceStringLength(s.Slice(), 2) + 2 } -func (a *Array) StringLength() int { - if a.Len() == 0 { +func (arr *Array) StringLength() int { + if arr.Len() == 0 { return 2 // [] } // surrounding brackets + ", " for every element - 1 - return TermSliceStringLength(a.elems, 2) + 2 + return TermSliceStringLength(arr.elems, 2) + 2 } -func (o *object) StringLength() (n int) { - if o.Len() == 0 { +func (obj *object) StringLength() (n int) { + if obj.Len() == 0 { return 2 // {} } // ": " for every item + ", " for every item - 1 - o.Foreach(func(key, value *Term) { + obj.Foreach(func(key, value *Term) { n += key.StringLength() + 4 + value.StringLength() // ": " and ", " }) return n // surrounding {} but also minus last ", " } -func (l *lazyObj) StringLength() int { - return l.force().(*object).StringLength() +func (lob *lazyObj) StringLength() int { + return lob.force().(*object).StringLength() } func (ts *TemplateString) StringLength() (n int) { @@ -138,23 +137,23 @@ func comprehensionTermStringLength(t *Term) int { return t.StringLength() } -func (r Ref) StringLength() (n int) { - rlen := len(r) +func (ref Ref) StringLength() (n int) { + rlen := len(ref) if rlen == 0 { return 0 } - if s, ok := r[0].Value.(String); ok { + if s, ok := ref[0].Value.(String); ok { n = len(s) // first term should never be quoted } else { - n = r[0].StringLength() + n = ref[0].StringLength() } if rlen == 1 { return n } - for _, p := range r[1:] { + for _, p := range ref[1:] { switch v := p.Value.(type) { case String: str := string(v) @@ -177,113 +176,113 @@ func (v Var) StringLength() int { return len(v) } -func (s *SetComprehension) StringLength() int { - return comprehensionTermStringLength(s.Term) + s.Body.StringLength() + 5 // {} and " | " +func (sc *SetComprehension) StringLength() int { + return comprehensionTermStringLength(sc.Term) + sc.Body.StringLength() + 5 // {} and " | " } -func (a *ArrayComprehension) StringLength() int { - return comprehensionTermStringLength(a.Term) + a.Body.StringLength() + 5 // [] and " | " +func (ac *ArrayComprehension) StringLength() int { + return comprehensionTermStringLength(ac.Term) + ac.Body.StringLength() + 5 // [] and " | " } -func (o *ObjectComprehension) StringLength() (n int) { - n += comprehensionTermStringLength(o.Key) - n += comprehensionTermStringLength(o.Value) - n += o.Body.StringLength() +func (oc *ObjectComprehension) StringLength() (n int) { + n += comprehensionTermStringLength(oc.Key) + n += comprehensionTermStringLength(oc.Value) + n += oc.Body.StringLength() return n + 7 // "{}"", " | ", and ": " } -func (m *Module) StringLength() (n int) { - if m.Package != nil { - n += m.Package.StringLength() + 2 // newlines +func (mod *Module) StringLength() (n int) { + if mod.Package != nil { + n += mod.Package.StringLength() + 2 // newlines } - if len(m.Imports) > 0 { - for _, imp := range m.Imports { + if len(mod.Imports) > 0 { + for _, imp := range mod.Imports { n += imp.StringLength() + 1 // newline } } - if len(m.Rules) > 0 { - for _, rule := range m.Rules { - n += rule.stringLengthWithOpts(toStringOpts{regoVersion: m.regoVersion}) + 1 // newline + if len(mod.Rules) > 0 { + for _, rule := range mod.Rules { + n += rule.stringLengthWithOpts(toStringOpts{regoVersion: mod.regoVersion}) + 1 // newline } } return n } -func (p *Package) StringLength() int { - if p == nil { +func (pkg *Package) StringLength() int { + if pkg == nil { return 21 // } - if len(p.Path) <= 1 { - return 25 + p.Path.StringLength() // // package + if len(pkg.Path) <= 1 { + return 25 + pkg.Path.StringLength() // // package } - return 8 + p.Path[1:].StringLength() // "package ..." + return 8 + pkg.Path[1:].StringLength() // "package ..." } -func (i *Import) StringLength() (n int) { - n = 7 + i.Path.StringLength() // "import " and path - if i.Alias != "" { - n += 4 + i.Alias.StringLength() // " as " and alias +func (imp *Import) StringLength() (n int) { + n = 7 + imp.Path.StringLength() // "import " and path + if imp.Alias != "" { + n += 4 + imp.Alias.StringLength() // " as " and alias } return n } -func (r *Rule) StringLength() int { - return r.stringLengthWithOpts(toStringOpts{}) +func (rule *Rule) StringLength() int { + return rule.stringLengthWithOpts(toStringOpts{}) } -func (r *Rule) stringLengthWithOpts(opts toStringOpts) int { +func (rule *Rule) stringLengthWithOpts(opts toStringOpts) int { n := 0 - if r.Default { + if rule.Default { n += 8 // "default " } - n += r.Head.stringLengthWithOpts(opts) - if !r.Default { + n += rule.Head.stringLengthWithOpts(opts) + if !rule.Default { switch opts.RegoVersion() { case RegoV1, RegoV0CompatV1: n += 6 // " if { " default: n += 3 // " { " } - n += r.Body.StringLength() + 2 // body and closing " }" + n += rule.Body.StringLength() + 2 // body and closing " }" } - if r.Else != nil { - n += r.Else.stringLengthWithOpts(opts) + if rule.Else != nil { + n += rule.Else.stringLengthWithOpts(opts) } return n } -func (h *Head) StringLength() int { - return h.stringLengthWithOpts(toStringOpts{}) +func (head *Head) StringLength() int { + return head.stringLengthWithOpts(toStringOpts{}) } -func (h *Head) stringLengthWithOpts(opts toStringOpts) int { - n := h.Reference.StringLength() +func (head *Head) stringLengthWithOpts(opts toStringOpts) int { + n := head.Reference.StringLength() containsAdded := false switch { - case len(h.Args) != 0: - n += h.Args.StringLength() - case len(h.Reference) == 1 && h.Key != nil: + case len(head.Args) != 0: + n += head.Args.StringLength() + case len(head.Reference) == 1 && head.Key != nil: switch opts.RegoVersion() { case RegoV0: - n += 2 + h.Key.StringLength() // for [] + n += 2 + head.Key.StringLength() // for [] default: - n += 10 + h.Key.StringLength() // " contains " + n += 10 + head.Key.StringLength() // " contains " containsAdded = true } } - if h.Value != nil { - if h.Assign { + if head.Value != nil { + if head.Assign { n += 4 // " := " } else { n += 3 // " = " } - n += h.Value.StringLength() - } else if !containsAdded && h.Name == "" && h.Key != nil { - n += 10 + h.Key.StringLength() // " contains " + n += head.Value.StringLength() + } else if !containsAdded && head.Name == "" && head.Key != nil { + n += 10 + head.Key.StringLength() // " contains " } return n } @@ -296,20 +295,20 @@ func (a Args) StringLength() (n int) { return n - 2 // minus last ", " } -func (b Body) StringLength() (n int) { - for _, expr := range b { +func (body Body) StringLength() (n int) { + for _, expr := range body { n += expr.StringLength() + 2 // "; " } return max(n-2, 0) // minus last "; " (if `n` isn't 0) } -func (e *Expr) StringLength() (n int) { - if e.Negated { +func (expr *Expr) StringLength() (n int) { + if expr.Negated { n += 4 // "not " } - switch terms := e.Terms.(type) { + switch terms := expr.Terms.(type) { case []*Term: - if e.IsEquality() && validEqAssignArgCount(e) { + if expr.IsEquality() && validEqAssignArgCount(expr) { n += terms[1].StringLength() + len(Equality.Infix) + terms[2].StringLength() + 2 // spaces around = } else { n += Call(terms).StringLength() @@ -317,10 +316,10 @@ func (e *Expr) StringLength() (n int) { case StringLengther: n += terms.StringLength() default: - panic(fmt.Sprintf("string length estimation not implemented for type: %T", e.Terms)) + panic(fmt.Sprintf("string length estimation not implemented for type: %T", expr.Terms)) } - for _, w := range e.With { + for _, w := range expr.With { n += w.StringLength() + 1 // space before with } @@ -331,20 +330,20 @@ func (w *With) StringLength() int { return w.Target.StringLength() + w.Value.StringLength() + 9 // "with " and " as " } -func (e *Every) StringLength() int { +func (q *Every) StringLength() int { n := 6 // "every " - if e.Key != nil { - n += e.Key.StringLength() + 2 // ", " + if q.Key != nil { + n += q.Key.StringLength() + 2 // ", " } - n += e.Value.StringLength() + 4 // " in " - n += e.Domain.StringLength() + 3 // " { " - n += e.Body.StringLength() + 2 // " }" + n += q.Value.StringLength() + 4 // " in " + n += q.Domain.StringLength() + 3 // " { " + n += q.Body.StringLength() + 2 // " }" return n } -func (s *SomeDecl) StringLength() int { +func (d *SomeDecl) StringLength() int { n := 5 // "some " - if call, ok := s.Symbols[0].Value.(Call); ok { + if call, ok := d.Symbols[0].Value.(Call); ok { n += 4 // " in " n += call[1].StringLength() if len(call) == 4 { @@ -356,24 +355,24 @@ func (s *SomeDecl) StringLength() int { } return n } - return n + TermSliceStringLength(s.Symbols, 2) + return n + TermSliceStringLength(d.Symbols, 2) } func (c *Comment) StringLength() int { return 1 + len(c.Text) // '#' + text } -func (not *Not) StringLength() int { - if !not.ExplicitBody && len(not.Body) == 1 { - if notBodyNeedsParens(not.Body) { +func (n *Not) StringLength() int { + if !n.ExplicitBody && len(n.Body) == 1 { + if notBodyNeedsParens(n.Body) { // "not (...)" - return 6 + not.Body.StringLength() + return 6 + n.Body.StringLength() } // "not ..." - return 4 + not.Body.StringLength() + return 4 + n.Body.StringLength() } // "not {...}" - return 6 + not.Body.StringLength() + return 6 + n.Body.StringLength() } func (a *LogicalAnd) StringLength() int { diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/syncpools.go b/vendor/github.com/open-policy-agent/opa/v1/ast/syncpools.go index 500bb073cf..f540f4a65e 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/syncpools.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/syncpools.go @@ -2,15 +2,17 @@ package ast import ( "bytes" + "strings" "sync" "github.com/open-policy-agent/opa/v1/util" ) var ( - TermPtrPool = util.NewSyncPool[Term]() - BytesReaderPool = util.NewSyncPool[bytes.Reader]() - IndexResultPool = util.NewSyncPool[IndexResult]() + TermPtrPool = util.NewSyncPool[Term]() + BytesReaderPool = util.NewSyncPool[bytes.Reader]() + StringReaderPool = util.NewSyncPool[strings.Reader]() + IndexResultPool = util.NewSyncPool[IndexResult]() // Needs custom pool because of custom Put logic. varVisitorPool = &vvPool{ diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/term.go b/vendor/github.com/open-policy-agent/opa/v1/ast/term.go index 23820f13bb..5a29c628e0 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/term.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/term.go @@ -75,7 +75,7 @@ func InterfaceToValue(x any) (Value, error) { case nil: return NullValue, nil case bool: - return InternedValue(x), nil + return internedBooleanValue(x), nil case json.Number: if interned := InternedIntNumberTermFromString(string(x)); interned != nil { return interned.Value, nil @@ -90,7 +90,7 @@ func InterfaceToValue(x any) (Value, error) { case float64: return floatNumber(x), nil case string: - return String(x), nil + return internedStringValue(x), nil case []any: r := util.NewPtrSlice[Term](len(x)) for i, e := range x { @@ -104,14 +104,14 @@ func InterfaceToValue(x any) (Value, error) { case []string: r := util.NewPtrSlice[Term](len(x)) for i, e := range x { - r[i].Value = String(e) + r[i].Value = internedStringValue(e) } return NewArray(r...), nil case map[string]any: kvs := util.NewPtrSlice[Term](len(x) * 2) idx := 0 for k, v := range x { - kvs[idx].Value = String(k) + kvs[idx].Value = internedStringValue(k) v, err := InterfaceToValue(v) if err != nil { return nil, err @@ -125,11 +125,7 @@ func InterfaceToValue(x any) (Value, error) { } return NewObject(tuples...), nil case map[string]string: - r := newobject(len(x)) - for k, v := range x { - r.Insert(StringTerm(k), StringTerm(v)) - } - return r, nil + return MapToObject(x, nil, InternedTerm), nil default: ptr := util.Reference(x) if err := util.RoundTrip(ptr); err != nil { @@ -150,7 +146,12 @@ func ValueFromReader(r io.Reader) (Value, error) { // As converts v into a Go native type referred to by x. func As(v Value, x any) error { - return util.NewJSONDecoder(strings.NewReader(v.String())).Decode(x) + sr := StringReaderPool.Get() + defer StringReaderPool.Put(sr) + + sr.Reset(v.String()) + + return util.NewJSONDecoder(sr).Decode(x) } // Resolver defines the interface for resolving references to native Go values. @@ -201,7 +202,7 @@ func valueToInterface(v Value, resolver Resolver, opt JSONOpt) (any, error) { case String: return string(v), nil case *Array: - buf := []any{} + buf := make([]any, 0, v.Len()) for i := range v.Len() { x1, err := valueToInterface(v.Elem(i).Value, resolver, opt) if err != nil { @@ -243,7 +244,7 @@ func valueToInterface(v Value, resolver Resolver, opt JSONOpt) (any, error) { } return v.native, nil case Set: - buf := []any{} + buf := make([]any, 0, v.Len()) iter := func(x *Term) error { x1, err := valueToInterface(x.Value, resolver, opt) if err != nil { @@ -349,44 +350,46 @@ func (term *Term) Copy() *Term { } cpy := *term - - switch v := term.Value.(type) { - case Null, Boolean, Number, String, Var: - cpy.Value = v - case Ref: - cpy.Value = v.Copy() - case *Array: - cpy.Value = v.Copy() - case Set: - cpy.Value = v.Copy() - case *object: - cpy.Value = v.Copy() - case *ArrayComprehension: - cpy.Value = v.Copy() - case *ObjectComprehension: - cpy.Value = v.Copy() - case *SetComprehension: - cpy.Value = v.Copy() - case *TemplateString: - cpy.Value = v.Copy() - case Call: - cpy.Value = v.Copy() - } + cpy.Value = CopyValue(term.Value) return &cpy } +// CopyValue returns a deep copy of v. The Value interface doesn't require a +// Copy method, so this dispatches on the known value types. Values of any other +// type are returned as-is. +func CopyValue(v Value) Value { + switch v := v.(type) { + case Null, Boolean, Number, String, Var: + // Scalars are immutable, no copy needed. + return v + case Ref: + return v.Copy() + case *Array: + return v.Copy() + case Set: + return v.Copy() + case *object: + return v.Copy() + case *ArrayComprehension: + return v.Copy() + case *ObjectComprehension: + return v.Copy() + case *SetComprehension: + return v.Copy() + case *TemplateString: + return v.Copy() + case Call: + return v.Copy() + } + + return v +} + // Equal returns true if this term equals the other term. Equality is // defined for each kind of term, and does not compare the Location. func (term *Term) Equal(other *Term) bool { - if term == other { - return true - } - if term == nil || other == nil { - return false - } - - return ValueEqual(term.Value, other.Value) + return term == other || (term != nil && other != nil && ValueEqual(term.Value, other.Value)) } // Get returns a value referred to by name from the term. @@ -430,6 +433,20 @@ func (term *Term) Vars() VarSet { return vis.vars } +// TermValueIs is a functional predicate to check if the term's Value is of type T. +func TermValueIs[T Value](term *Term) (ok bool) { + if ok = term != nil; ok { + _, ok = term.Value.(T) + } + return ok +} + +// ToTerm exists solely to be able to map concrete Value +// implementations to *Term in util.Map, util.MapKeys, etc. +func ToTerm[T Value](v T) *Term { + return NewTerm(v) +} + // IsConstant returns true if the AST value is constant. // Note that this is only a shallow check as we currently don't have a real // notion of constant "vars" in the AST implementation. Meaning that while we could @@ -624,12 +641,8 @@ func NullTerm() *Term { // Equal returns true if the other term Value is also Null. func (Null) Equal(other Value) bool { - switch other.(type) { - case Null: - return true - default: - return false - } + _, ok := other.(Null) + return ok } // Compare compares null to other, return <0, 0, or >0 if it is less than, equal to, @@ -673,12 +686,8 @@ func BooleanTerm(b bool) *Term { // Equal returns true if the other Value is a Boolean and is equal. func (bol Boolean) Equal(other Value) bool { - switch other := other.(type) { - case Boolean: - return bol == other - default: - return false - } + _, ok := other.(Boolean) + return ok && bol == other } // Compare compares bol to other, return <0, 0, or >0 if it is less than, equal to, @@ -854,9 +863,6 @@ func (str String) Equal(other Value) bool { // Compare compares str to other, return <0, 0, or >0 if it is less than, equal to, // or greater than other. func (str String) Compare(other Value) int { - // Optimize for the common case of one string being compared to another by - // using a direct comparison of values. This avoids the allocation performed - // when calling Compare and its any argument conversion. if otherStr, ok := other.(String); ok { if str == otherStr { return 0 @@ -1197,8 +1203,7 @@ func (ref Ref) Concat(terms []*Term) Ref { // Dynamic returns the offset of the first non-constant operand of ref. func (ref Ref) Dynamic() int { - switch ref[0].Value.(type) { - case Call: + if TermValueIs[Call](ref[0]) { return 0 } for i := 1; i < len(ref); i++ { @@ -1235,27 +1240,15 @@ func (ref Ref) CopyNonGround() Ref { // Equal returns true if ref is equal to other. func (ref Ref) Equal(other Value) bool { - switch o := other.(type) { - case Ref: - if len(ref) == len(o) { - for i := range ref { - if !ref[i].Equal(o[i]) { - return false - } - } - - return true - } - } - - return false + o, ok := other.(Ref) + return ok && slices.EqualFunc(ref, o, (*Term).Equal) } // Compare compares ref to other, return <0, 0, or >0 if it is less than, equal to, // or greater than other. func (ref Ref) Compare(other Value) int { if o, ok := other.(Ref); ok { - return termSliceCompare(ref, o) + return slices.CompareFunc(ref, o, TermValueCompare) } return valueTypeCompare(ref, other) } @@ -1332,20 +1325,12 @@ func (ref Ref) DynamicSuffix() Ref { // IsGround returns true if all of the parts of the Ref are ground. func (ref Ref) IsGround() bool { - if len(ref) < 2 { - return true - } - return termSliceIsGround(ref[1:]) + return len(ref) < 2 || util.Every(ref[1:], (*Term).IsGround) } // IsNested returns true if this ref contains other Refs. func (ref Ref) IsNested() bool { - for _, x := range ref { - if _, ok := x.Value.(Ref); ok { - return true - } - } - return false + return slices.ContainsFunc(ref, TermValueIs[Ref]) } // Ptr returns a slash-separated path string for this ref. If the ref @@ -1467,7 +1452,7 @@ func NewArray(a ...*Term) *Array { for i, e := range a { hs[i] = e.Value.Hash() } - arr := &Array{elems: a, hashs: hs, ground: termSliceIsGround(a)} + arr := &Array{elems: a, hashs: hs, ground: util.Every(a, (*Term).IsGround)} arr.rehash() return arr } @@ -1523,7 +1508,7 @@ func (arr *Array) Equal(other Value) bool { // or greater than other. func (arr *Array) Compare(other Value) int { if b, ok := other.(*Array); ok { - return termSliceCompare(arr.elems, b.elems) + return slices.CompareFunc(arr.elems, b.elems, TermValueCompare) } return valueTypeCompare(arr, other) @@ -1576,9 +1561,11 @@ func (arr *Array) Sorted() *Array { slices.SortFunc(cpy, TermValueCompare) - a := NewArray(cpy...) - a.hashs = arr.hashs - return a + // NewArray has already hashed cpy in its own order. Taking arr.hashs over + // that would leave hashs[i] holding the hash of some other element, which + // Array.set relies on, and would share the slice with arr so that setting + // an element here corrupted arr. + return NewArray(cpy...) } // Hash returns the hash code for the Value. @@ -1623,14 +1610,25 @@ func (arr *Array) rehash() { func (arr *Array) set(i int, v *Term) { arr.ground = arr.ground && v.IsGround() arr.elems[i] = v - arr.hashs[i] = v.Value.Hash() - arr.rehash() + + // arr.hash is the sum of arr.hashs, so swapping one element's hash in is + // enough -- rehashing the whole array here makes building an array of n + // elements O(n^2), which is felt on the large arrays that appear in + // generated policies. + h := v.Value.Hash() + arr.hash += h - arr.hashs[i] + arr.hashs[i] = h } // Slice returns a slice of arr starting from i index to j. -1 // indicates the end of the array. The returned value array is not a // copy and any modifications to either of arrays may be reflected to // the other. +// +// Set on the returned slice writes through to arr's element and to its +// hash, but not to arr's cached sum of those hashes, so arr.Hash() is +// stale from then on and anything holding arr as a map key or set member +// stops finding it. Copy the slice before writing to it. func (arr *Array) Slice(i, j int) *Array { var elems []*Term var hashs []int @@ -1643,7 +1641,7 @@ func (arr *Array) Slice(i, j int) *Array { } // If arr is ground, the slice is, too. // If it's not, the slice could still be. - gr := arr.ground || termSliceIsGround(elems) + gr := arr.ground || util.Every(elems, (*Term).IsGround) s := &Array{elems: elems, hashs: hashs, ground: gr} s.rehash() @@ -1675,10 +1673,11 @@ func (arr *Array) Foreach(f func(*Term)) { // Append appends a term to arr, returning the appended array. func (arr *Array) Append(v *Term) *Array { + vhs := v.Value.Hash() cpy := *arr cpy.elems = append(arr.elems, v) - cpy.hashs = append(arr.hashs, v.Value.Hash()) - cpy.hash = arr.hash + v.Value.Hash() + cpy.hashs = append(arr.hashs, vhs) + cpy.hash += vhs cpy.ground = arr.ground && v.IsGround() return &cpy } @@ -1821,9 +1820,11 @@ func (s *set) Find(path Ref) (Value, error) { } // Diff returns elements in s that are not in other. +// A returned empty set will be an interned representation that +// should not be modified without copying. func (s *set) Diff(other Set) Set { if s.Compare(other) == 0 { - return NewSet() + return InternedEmptySetValue.(Set) } result := newset(len(s.keys)) @@ -1903,13 +1904,9 @@ func (s *set) Foreach(f func(*Term)) { // Map returns a new Set obtained by applying f to each value in s. func (s *set) Map(f func(*Term) (*Term, error)) (Set, error) { - mapped := make([]*Term, 0, len(s.keys)) - for _, x := range s.sortedKeys() { - term, err := f(x) - if err != nil { - return nil, err - } - mapped = append(mapped, term) + mapped, err := util.TryMap(s.sortedKeys(), f) + if err != nil { + return nil, err } return NewSet(mapped...), nil } @@ -1918,13 +1915,9 @@ func (s *set) Map(f func(*Term) (*Term, error)) (Set, error) { // argument to f is the reduced value (starting with i) and the second argument // to f is the element in s. func (s *set) Reduce(i *Term, f func(*Term, *Term) (*Term, error)) (*Term, error) { - err := s.Iter(func(x *Term) error { - var err error + err := s.Iter(func(x *Term) (err error) { i, err = f(i, x) - if err != nil { - return err - } - return nil + return err }) return i, err } @@ -2028,10 +2021,44 @@ type Object interface { // NewObject creates a new Object with t. func NewObject(t ...[2]*Term) Object { - obj := newobject(len(t)) - for i := range t { - obj.insert(t[i][0], t[i][1], false) + var keys []*objectElem + n := len(t) + if n > 0 { + keys = make([]*objectElem, n) } + obj := &object{ + elems: make(map[int]*objectElem, n), + keys: keys, + sortGuard: sync.Once{}, + } + + // NOTE(anders): The code below is convoluted, but necessary + // since creating objects is something we do a lot and often + // on hot paths, this avoids allocating one objectElem per + // key-value pair, in favor of a single contiguous block of + // memory. The same technique is used in (*object).Copy(), + // for the same reasons. + elems := make([]objectElem, n) + for i, kv := range t { + key, val := kv[0], kv[1] + elems[i] = objectElem{key: key, value: val} + obj.keys[i] = &elems[i] + + keyHash := key.Hash() + if head, ok := obj.elems[keyHash]; ok { + elems[i].next = head + } + obj.hash += keyHash + val.Hash() + + if key.IsGround() { + obj.ground++ + } + if val.IsGround() { + obj.ground++ + } + obj.elems[keyHash] = &elems[i] + } + return obj } @@ -2055,72 +2082,72 @@ type lazyObj struct { native map[string]any } -func (l *lazyObj) force() Object { - if l.strict == nil { - l.strict = MustInterfaceToValue(l.native).(Object) +func (lob *lazyObj) force() Object { + if lob.strict == nil { + lob.strict = MustInterfaceToValue(lob.native).(Object) // NOTE(jf): a possible performance improvement here would be to check how many // entries have been realized to AST in the cache, and if some threshold compared to the // total number of keys is exceeded, realize the remaining entries and set l.strict to l.cache. - l.cache = map[string]Value{} // We don't need the cache anymore; drop it to free up memory. + lob.cache = map[string]Value{} // We don't need the cache anymore; drop it to free up memory. } - return l.strict + return lob.strict } -func (l *lazyObj) Compare(other Value) int { - if c := valueTypeCompare(l, other); c != 0 { +func (lob *lazyObj) Compare(other Value) int { + if c := valueTypeCompare(lob, other); c != 0 { return c } - return l.force().Compare(other) + return lob.force().Compare(other) } -func (l *lazyObj) Copy() Object { - return l +func (lob *lazyObj) Copy() Object { + return lob } -func (l *lazyObj) Diff(other Object) Object { - return l.force().Diff(other) +func (lob *lazyObj) Diff(other Object) Object { + return lob.force().Diff(other) } -func (l *lazyObj) Intersect(other Object) [][3]*Term { - return l.force().Intersect(other) +func (lob *lazyObj) Intersect(other Object) [][3]*Term { + return lob.force().Intersect(other) } -func (l *lazyObj) Iter(f func(*Term, *Term) error) error { - return l.force().Iter(f) +func (lob *lazyObj) Iter(f func(*Term, *Term) error) error { + return lob.force().Iter(f) } -func (l *lazyObj) Until(f func(*Term, *Term) bool) bool { +func (lob *lazyObj) Until(f func(*Term, *Term) bool) bool { // NOTE(sr): there could be benefits in not forcing here -- if we abort because // `f` returns true, we could save us from converting the rest of the object. - return l.force().Until(f) + return lob.force().Until(f) } -func (l *lazyObj) Foreach(f func(*Term, *Term)) { - l.force().Foreach(f) +func (lob *lazyObj) Foreach(f func(*Term, *Term)) { + lob.force().Foreach(f) } -func (l *lazyObj) Filter(filter Object) (Object, error) { - return l.force().Filter(filter) +func (lob *lazyObj) Filter(filter Object) (Object, error) { + return lob.force().Filter(filter) } -func (l *lazyObj) Map(f func(*Term, *Term) (*Term, *Term, error)) (Object, error) { - return l.force().Map(f) +func (lob *lazyObj) Map(f func(*Term, *Term) (*Term, *Term, error)) (Object, error) { + return lob.force().Map(f) } -func (l *lazyObj) Merge(other Object) (Object, bool) { - return l.force().Merge(other) +func (lob *lazyObj) Merge(other Object) (Object, bool) { + return lob.force().Merge(other) } -func (l *lazyObj) MergeWith(other Object, conflictResolver func(v1, v2 *Term) (*Term, bool)) (Object, bool) { - return l.force().MergeWith(other, conflictResolver) +func (lob *lazyObj) MergeWith(other Object, conflictResolver func(v1, v2 *Term) (*Term, bool)) (Object, bool) { + return lob.force().MergeWith(other, conflictResolver) } -func (l *lazyObj) Len() int { - return len(l.native) +func (lob *lazyObj) Len() int { + return len(lob.native) } -func (l *lazyObj) String() string { - return l.force().String() +func (lob *lazyObj) String() string { + return lob.force().String() } // get is merely there to implement the Object interface -- `get` there serves the @@ -2129,16 +2156,16 @@ func (*lazyObj) get(*Term) *objectElem { return nil } -func (l *lazyObj) Get(k *Term) *Term { - if l.strict != nil { - return l.strict.Get(k) +func (lob *lazyObj) Get(k *Term) *Term { + if lob.strict != nil { + return lob.strict.Get(k) } if s, ok := k.Value.(String); ok { - if v, ok := l.cache[string(s)]; ok { + if v, ok := lob.cache[string(s)]; ok { return NewTerm(v) } - if val, ok := l.native[string(s)]; ok { + if val, ok := lob.native[string(s)]; ok { var converted Value switch val := val.(type) { case map[string]any: @@ -2146,40 +2173,34 @@ func (l *lazyObj) Get(k *Term) *Term { default: converted = MustInterfaceToValue(val) } - l.cache[string(s)] = converted + lob.cache[string(s)] = converted return NewTerm(converted) } } return nil } -func (l *lazyObj) Insert(k, v *Term) { - l.force().Insert(k, v) +func (lob *lazyObj) Insert(k, v *Term) { + lob.force().Insert(k, v) } func (*lazyObj) IsGround() bool { return true } -func (l *lazyObj) Hash() int { - return l.force().Hash() +func (lob *lazyObj) Hash() int { + return lob.force().Hash() } -func (l *lazyObj) Keys() []*Term { - if l.strict != nil { - return l.strict.Keys() +func (lob *lazyObj) Keys() []*Term { + if lob.strict != nil { + return lob.strict.Keys() } - ret := make([]*Term, 0, len(l.native)) - for k := range l.native { - ret = append(ret, StringTerm(k)) - } - slices.SortFunc(ret, TermValueCompare) - - return ret + return util.SortedFunc(util.MapKeys(lob.native, InternedTerm), TermValueCompare) } -func (l *lazyObj) KeysIterator() ObjectKeysIterator { - return &lazyObjKeysIterator{keys: l.Keys()} +func (lob *lazyObj) KeysIterator() ObjectKeysIterator { + return &lazyObjKeysIterator{keys: lob.Keys()} } type lazyObjKeysIterator struct { @@ -2195,19 +2216,19 @@ func (ki *lazyObjKeysIterator) Next() (*Term, bool) { return ki.keys[ki.current-1], true } -func (l *lazyObj) Find(path Ref) (Value, error) { - if l.strict != nil { - return l.strict.Find(path) +func (lob *lazyObj) Find(path Ref) (Value, error) { + if lob.strict != nil { + return lob.strict.Find(path) } if len(path) == 0 { - return l, nil + return lob, nil } if p0, ok := path[0].Value.(String); ok { - if v, ok := l.cache[string(p0)]; ok { + if v, ok := lob.cache[string(p0)]; ok { return v.Find(path[1:]) } - if v, ok := l.native[string(p0)]; ok { + if v, ok := lob.native[string(p0)]; ok { var converted Value switch v := v.(type) { case map[string]any: @@ -2215,7 +2236,7 @@ func (l *lazyObj) Find(path Ref) (Value, error) { default: converted = MustInterfaceToValue(v) } - l.cache[string(p0)] = converted + lob.cache[string(p0)] = converted return converted.Find(path[1:]) } } @@ -2292,6 +2313,38 @@ func (obj *object) Compare(other Value) int { return len(akeys) - len(bkeys) } +func (obj *object) Equal(other Value) bool { + var ob2 *object + switch v := other.(type) { + case *object: + ob2 = v + case *lazyObj: + return obj.Equal(v.force()) + } + + if obj == ob2 { + return true + } + if obj == nil || ob2 == nil || len(obj.keys) != len(ob2.keys) { + return false + } + elems1, elems2 := obj.sortedKeys(), ob2.sortedKeys() + // Note(anderseknert): + // Go can't (easily) know that the above calls don't modify the length + // checked before. Doing it once more here is cheap and ensures that the + // loop is evaluated without additional nil and bounds checks + if len(elems1) != len(elems2) { + return false + } + + for i, elem := range elems1 { + if !elem.key.Equal(elems2[i].key) || !elem.value.Equal(elems2[i].value) { + return false + } + } + return true +} + // Find returns the value at the key or undefined. func (obj *object) Find(path Ref) (Value, error) { if len(path) == 0 { @@ -2371,11 +2424,13 @@ func (obj *object) Copy() Object { return cpy } - // Batch-allocate all objectElems, keys, and values in contiguous blocks - // (3 allocations instead of 3N). + // Batch-allocate all objectElems and keys/value pairs in contiguous blocks + // (2 allocations instead of 3N). elems := make([]objectElem, n) - keys := make([]Term, n) - vals := make([]Term, n) + pairs := make([]Term, n*2) + keys := pairs[:n] + vals := pairs[n:] + cpy.keys = make([]*objectElem, n) for i, srcElem := range obj.keys { @@ -2925,7 +2980,7 @@ func (c Call) Copy() Call { // or greater than other. func (c Call) Compare(other Value) int { if oc, ok := other.(Call); ok { - return termSliceCompare(c, oc) + return slices.CompareFunc(c, oc, TermValueCompare) } return valueTypeCompare(c, other) } @@ -2942,7 +2997,7 @@ func (c Call) Hash() int { // IsGround returns true if the Value is ground. func (c Call) IsGround() bool { - return termSliceIsGround(c) + return util.Every(c, (*Term).IsGround) } // MakeExpr returns a new Expr from this call. @@ -3015,18 +3070,6 @@ func termSliceCopy(a []*Term) []*Term { return cpy } -func termSliceEqual(a, b []*Term) bool { - if len(a) == len(b) { - for i := range a { - if !a[i].Equal(b[i]) { - return false - } - } - return true - } - return false -} - func termSliceHash(a []*Term) int { var hash int for _, v := range a { @@ -3035,15 +3078,6 @@ func termSliceHash(a []*Term) int { return hash } -func termSliceIsGround(a []*Term) bool { - for _, v := range a { - if !v.IsGround() { - return false - } - } - return true -} - // Detect when String() need to use expensive JSON‐escaped form func isControlOrBackslash(r rune) bool { return r == '\\' || unicode.IsControl(r) diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/term_appenders.go b/vendor/github.com/open-policy-agent/opa/v1/ast/term_appenders.go index 60a9a088d7..1ae32fb201 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/term_appenders.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/term_appenders.go @@ -26,8 +26,8 @@ func (v Var) AppendText(buf []byte) ([]byte, error) { return append(buf, v...), nil } -func (b Boolean) AppendText(buf []byte) ([]byte, error) { - if b { +func (bol Boolean) AppendText(buf []byte) ([]byte, error) { + if bol { return append(buf, "true"...), nil } return append(buf, "false"...), nil @@ -92,8 +92,8 @@ func (obj *object) AppendText(buf []byte) ([]byte, error) { return append(buf, '}'), nil } -func (obj *lazyObj) AppendText(buf []byte) ([]byte, error) { - return append(buf, obj.force().String()...), nil +func (lob *lazyObj) AppendText(buf []byte) ([]byte, error) { + return append(buf, lob.force().String()...), nil } func (s *set) AppendText(buf []byte) ([]byte, error) { @@ -173,30 +173,30 @@ func (ts *TemplateString) AppendText(buf []byte) ([]byte, error) { return append(buf, '"'), nil } -func (r Ref) AppendText(buf []byte) ([]byte, error) { - reflen := len(r) +func (ref Ref) AppendText(buf []byte) ([]byte, error) { + reflen := len(ref) if reflen == 0 { return buf, nil } if reflen == 1 { - if s, ok := r[0].Value.(String); ok { + if s, ok := ref[0].Value.(String); ok { // While a ref head is typically a Var, a lone String term should not be quoted return append(buf, s...), nil } - return r[0].AppendText(buf) + return ref[0].AppendText(buf) } - if name, ok := BuiltinNameFromRef(r); ok { + if name, ok := BuiltinNameFromRef(ref); ok { return append(buf, name...), nil } var err error - if s, ok := r[0].Value.(String); ok { + if s, ok := ref[0].Value.(String); ok { buf = append(buf, s...) - } else if buf, err = r[0].AppendText(buf); err != nil { + } else if buf, err = ref[0].AppendText(buf); err != nil { return nil, err } - for _, p := range r[1:] { + for _, p := range ref[1:] { switch v := p.Value.(type) { case String: str := string(v) @@ -287,23 +287,23 @@ func appendComprehensionTerm(buf []byte, term *Term) ([]byte, error) { return term.AppendText(buf) } -func (not *Not) AppendText(buf []byte) ([]byte, error) { - if !not.ExplicitBody && len(not.Body) == 1 { - if notBodyNeedsParens(not.Body) { +func (n *Not) AppendText(buf []byte) ([]byte, error) { + if !n.ExplicitBody && len(n.Body) == 1 { + if notBodyNeedsParens(n.Body) { buf = append(buf, "not ("...) var err error - if buf, err = not.Body.AppendText(buf); err != nil { + if buf, err = n.Body.AppendText(buf); err != nil { return nil, err } return append(buf, ')'), nil } buf = append(buf, "not "...) - return not.Body.AppendText(buf) + return n.Body.AppendText(buf) } buf = append(buf, "not {"...) var err error - if buf, err = not.Body.AppendText(buf); err != nil { + if buf, err = n.Body.AppendText(buf); err != nil { return nil, err } return append(buf, '}'), nil diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/term_json.go b/vendor/github.com/open-policy-agent/opa/v1/ast/term_json.go index 685801b66d..463b5d9702 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/term_json.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/term_json.go @@ -65,8 +65,8 @@ func (s *set) MarshalJSON() ([]byte, error) { return json.Marshal(s.sortedKeys()) } -func (l *lazyObj) MarshalJSON() ([]byte, error) { - return l.force().(*object).MarshalJSON() +func (lob *lazyObj) MarshalJSON() ([]byte, error) { + return lob.force().(*object).MarshalJSON() } func (n *Not) MarshalJSON() ([]byte, error) { diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/term_jsonv2.go b/vendor/github.com/open-policy-agent/opa/v1/ast/term_jsonv2.go index 62189c3729..462c8bdee2 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/term_jsonv2.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/term_jsonv2.go @@ -47,8 +47,8 @@ var ( // These are here to ensure that we do not fall down to TextAppender, which // Go 1.27's encoding/json would otherwise use, encoding these as JSON strings. -func (b Boolean) MarshalJSONTo(e *jsontext.Encoder) error { - return e.WriteToken(jsontext.Bool(bool(b))) +func (bol Boolean) MarshalJSONTo(e *jsontext.Encoder) error { + return e.WriteToken(jsontext.Bool(bool(bol))) } func (Null) MarshalJSONTo(e *jsontext.Encoder) error { @@ -80,24 +80,24 @@ func (str String) MarshalJSONTo(e *jsontext.Encoder) error { return e.WriteToken(jsontext.String(string(str))) } -func (t *Term) MarshalJSONTo(e *jsontext.Encoder) (err error) { +func (term *Term) MarshalJSONTo(e *jsontext.Encoder) (err error) { // Token write errors are unchecked: an unbalanced value fails at the closing // token. A marshaller can fail having written a balanced value, so is checked. e.WriteToken(jsontext.BeginObject) includeLocation := astJSON.GetOptions().MarshalOptions.IncludeLocation - if t.Location != nil && includeLocation.Term { - if err := jsonv2.WriteField(e, "location", t.Location); err != nil { + if term.Location != nil && includeLocation.Term { + if err := jsonv2.WriteField(e, "location", term.Location); err != nil { return err } } e.WriteToken(jsontext.String("type")) - e.WriteToken(jsontext.String(ValueName(t.Value))) + e.WriteToken(jsontext.String(ValueName(term.Value))) e.WriteToken(jsontext.String("value")) - if err = marshalValueTo(e, t.Value); err != nil { - return fmt.Errorf("failed to marshal term of %s: %w", ValueName(t.Value), err) + if err = marshalValueTo(e, term.Value); err != nil { + return fmt.Errorf("failed to marshal term of %s: %w", ValueName(term.Value), err) } return e.WriteToken(jsontext.EndObject) @@ -108,23 +108,23 @@ func (term *Term) MarshalJSON() ([]byte, error) { return jsonv2.MarshalMarshalerTo(term) } -func (r Ref) MarshalJSONTo(e *jsontext.Encoder) (err error) { - return jsonv2.WriteMarshalerToArrayOrNull(e, r) +func (ref Ref) MarshalJSONTo(e *jsontext.Encoder) (err error) { + return jsonv2.WriteMarshalerToArrayOrNull(e, ref) } -func (t *TemplateString) MarshalJSONTo(e *jsontext.Encoder) (err error) { +func (ts *TemplateString) MarshalJSONTo(e *jsontext.Encoder) (err error) { // Token write errors are unchecked: an unbalanced value fails at the closing // token. A marshaller can fail having written a balanced value, so is checked. e.WriteToken(jsontext.BeginObject) e.WriteToken(jsontext.String("parts")) - if t.Parts == nil { + if ts.Parts == nil { // Parts has no omitempty tag, so it's always written. Matches // encoding/json v1, which encodes a nil slice as null rather than as an // empty array. e.WriteToken(jsontext.Null) } else { e.WriteToken(jsontext.BeginArray) - for _, p := range t.Parts { + for _, p := range ts.Parts { switch v := p.(type) { case *Expr: if err := v.MarshalJSONTo(e); err != nil { @@ -140,7 +140,7 @@ func (t *TemplateString) MarshalJSONTo(e *jsontext.Encoder) (err error) { } e.WriteToken(jsontext.String("multi_line")) - e.WriteToken(jsontext.Bool(t.MultiLine)) + e.WriteToken(jsontext.Bool(ts.MultiLine)) return e.WriteToken(jsontext.EndObject) } @@ -197,12 +197,12 @@ func (obj *object) MarshalJSONTo(e *jsontext.Encoder) error { return e.WriteToken(jsontext.EndArray) } -func (l *lazyObj) MarshalJSONTo(e *jsontext.Encoder) error { - return l.force().(*object).MarshalJSONTo(e) +func (lob *lazyObj) MarshalJSONTo(e *jsontext.Encoder) error { + return lob.force().(*object).MarshalJSONTo(e) } -func (l *lazyObj) MarshalJSON() ([]byte, error) { - return l.force().(*object).MarshalJSON() +func (lob *lazyObj) MarshalJSON() ([]byte, error) { + return lob.force().(*object).MarshalJSON() } // MarshalJSON returns JSON encoded bytes representing obj. @@ -210,8 +210,8 @@ func (obj *object) MarshalJSON() ([]byte, error) { return jsonv2.MarshalMarshalerTo(obj) } -func (a *Array) MarshalJSONTo(e *jsontext.Encoder) error { - return jsonv2.WriteMarshalerToArray(e, a.elems) +func (arr *Array) MarshalJSONTo(e *jsontext.Encoder) error { + return jsonv2.WriteMarshalerToArray(e, arr.elems) } // MarshalJSON returns JSON encoded bytes representing arr. diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/transform.go b/vendor/github.com/open-policy-agent/opa/v1/ast/transform.go index 7c0c54e3d7..36f414e549 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/transform.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/transform.go @@ -32,6 +32,11 @@ func Transform(t Transformer, x any) (any, error) { return nil, nil } + // The cases below that hold a slice mutate it in place, so this interface + // value goes on describing what they produce. Returning it costs nothing, + // where returning the case variable would box a slice header afresh. + orig := y + var ok bool switch y := y.(type) { case *Module: @@ -89,14 +94,10 @@ func Transform(t Transformer, x any) (any, error) { } return y, nil case *Import: - y.Path, err = transformTerm(t, y.Path) - if err != nil { - return nil, err + if y.Path, err = transformTerm(t, y.Path); err == nil { + y.Alias, err = transformVar(t, y.Alias) } - if y.Alias, err = transformVar(t, y.Alias); err != nil { - return nil, err - } - return y, nil + return y, err case *Rule: if y.Head, err = transformHead(t, y.Head); err != nil { return nil, err @@ -141,7 +142,7 @@ func Transform(t Transformer, x any) (any, error) { return nil, err } } - return y, nil + return orig, nil case Body: for i, e := range y { e, err := Transform(t, e) @@ -152,7 +153,7 @@ func Transform(t Transformer, x any) (any, error) { return nil, fmt.Errorf("illegal transform: %T != %T", y[i], e) } } - return y, nil + return orig, nil case *Expr: switch ts := y.Terms.(type) { case *SomeDecl: @@ -242,7 +243,7 @@ func Transform(t Transformer, x any) (any, error) { return nil, err } } - return y, nil + return orig, nil case *object: return y.Map(func(k, v *Term) (*Term, *Term, error) { k, err := transformTerm(t, k) @@ -264,14 +265,10 @@ func Transform(t Transformer, x any) (any, error) { y.set(i, v) } return y, nil - case Set: - y, err = y.Map(func(term *Term) (*Term, error) { + case *set: + return y.Map(func(term *Term) (*Term, error) { return transformTerm(t, term) }) - if err != nil { - return nil, err - } - return y, nil case *ArrayComprehension: if y.Term, err = transformTerm(t, y.Term); err != nil { return nil, err @@ -305,7 +302,7 @@ func Transform(t Transformer, x any) (any, error) { return nil, err } } - return y, nil + return orig, nil case *TemplateString: for i := range y.Parts { if expr, ok := y.Parts[i].(*Expr); ok { @@ -326,29 +323,29 @@ func Transform(t Transformer, x any) (any, error) { // TransformRefs calls the function f on all references under x. func TransformRefs(x any, f func(Ref) (Value, error)) (any, error) { - t := NewGenericTransformer(func(x any) (any, error) { + t := GenericTransformer{f: func(x any) (any, error) { if r, ok := x.(Ref); ok { return f(r) } return x, nil - }) + }} return Transform(t, x) } // TransformVars calls the function f on all vars under x. func TransformVars(x any, f func(Var) (Value, error)) (any, error) { - t := NewGenericTransformer(func(x any) (any, error) { + t := GenericTransformer{f: func(x any) (any, error) { if v, ok := x.(Var); ok { return f(v) } return x, nil - }) + }} return Transform(t, x) } // TransformComprehensions calls the function f on all comprehensions under x. func TransformComprehensions(x any, f func(any) (Value, error)) (any, error) { - t := NewGenericTransformer(func(x any) (any, error) { + t := GenericTransformer{f: func(x any) (any, error) { switch x := x.(type) { case *ArrayComprehension: return f(x) @@ -358,7 +355,7 @@ func TransformComprehensions(x any, f func(any) (Value, error)) (any, error) { return f(x) } return x, nil - }) + }} return Transform(t, x) } @@ -371,13 +368,11 @@ type GenericTransformer struct { // NewGenericTransformer returns a new GenericTransformer that will transform // AST nodes using the function f. func NewGenericTransformer(f func(x any) (any, error)) *GenericTransformer { - return &GenericTransformer{ - f: f, - } + return &GenericTransformer{f: f} } // Transform calls the function f on the GenericTransformer. -func (t *GenericTransformer) Transform(x any) (any, error) { +func (t GenericTransformer) Transform(x any) (any, error) { return t.f(x) } @@ -418,11 +413,24 @@ func transformBody(t Transformer, body Body) (Body, error) { } func transformTerm(t Transformer, term *Term) (*Term, error) { - v, err := transformValue(t, term.Value) + tv, err := transformValue(t, term.Value) if err != nil { return nil, err } - return &Term{Value: v, Location: term.Location}, nil + + // If the term was interned, make sure to return a new one instead + // of replacing the value of the interned term, as that'll be used + // elsewhere, leading to data races + if s, ok := tv.(String); ok { + if it, ok := internedStringTerms[string(s)]; ok && term == it { + return &Term{Value: tv, Location: term.Location}, nil + } + } + + // Not interned = modify the value in place + term.Value = tv + + return term, err } func transformValue(t Transformer, v Value) (Value, error) { diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/treenode_dump.go b/vendor/github.com/open-policy-agent/opa/v1/ast/treenode_dump.go index f22367bc81..0b7c5a6e60 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/treenode_dump.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/treenode_dump.go @@ -2,8 +2,9 @@ package ast import ( "fmt" - "sort" "strings" + + "github.com/open-policy-agent/opa/v1/util" ) // Dump returns a string representation of the tree structure rooted at this node. @@ -24,21 +25,16 @@ func (n *TreeNode) dumpRecursive(sb *strings.Builder, prefix, childPrefix string fmt.Fprintf(sb, " ext:%v", n.External.Ref) } if len(n.Values) > 0 { - fmt.Fprintf(sb, " rules:%d", len(n.Values)) + sb.WriteString(" rules:") + util.WriteInt(sb, len(n.Values)) } - sb.WriteString("\n") + sb.WriteByte('\n') if len(n.Children) == 0 { return } - keys := make([]Value, 0, len(n.Children)) - for k := range n.Children { - keys = append(keys, k) - } - sort.Slice(keys, func(i, j int) bool { - return Compare(keys[i], keys[j]) < 0 - }) + keys := util.SortedFunc(util.Keys(n.Children), Value.Compare) for i, key := range keys { child := n.Children[key] diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/unify.go b/vendor/github.com/open-policy-agent/opa/v1/ast/unify.go index 67b89a2a93..877b576eb2 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/unify.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/unify.go @@ -115,8 +115,7 @@ func (u *unifier) unify(a *Term, b *Term) { u.markAllSafe(b) } case *SetComprehension: - switch b := b.Value.(type) { - case Var: + if b, ok := b.Value.(Var); ok { u.markSafe(b) } @@ -166,9 +165,8 @@ func (u *unifier) unify(a *Term, b *Term) { } default: - switch b := b.Value.(type) { - case Var: - u.markSafe(b) + if v, ok := b.Value.(Var); ok { + u.markSafe(v) } } } diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/varset.go b/vendor/github.com/open-policy-agent/opa/v1/ast/varset.go index 55bbea80d0..582a9982ec 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/varset.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/varset.go @@ -6,7 +6,6 @@ package ast import ( "fmt" - "slices" "github.com/open-policy-agent/opa/v1/util" ) @@ -111,12 +110,7 @@ func (s VarSet) Intersect(vs VarSet) VarSet { // Sorted returns a new sorted slice of vars from s. func (s VarSet) Sorted() []Var { - sorted := make([]Var, 0, len(s)) - for v := range s { - sorted = append(sorted, v) - } - slices.SortFunc(sorted, VarCompare) - return sorted + return util.SortedFunc(util.Keys(s), VarCompare) } // Update merges the other VarSet into this VarSet. diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/version_index.json b/vendor/github.com/open-policy-agent/opa/v1/ast/version_index.json index 91e4d09ff3..d6d298cbb9 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/version_index.json +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/version_index.json @@ -1070,6 +1070,11 @@ } }, "keywords": { + "and": { + "Major": 1, + "Minor": 20, + "Patch": 0 + }, "contains": { "Major": 0, "Minor": 42, @@ -1094,6 +1099,11 @@ "Major": 1, "Minor": 17, "Patch": 0 + }, + "or": { + "Major": 1, + "Minor": 20, + "Patch": 0 } } } diff --git a/vendor/github.com/open-policy-agent/opa/v1/ast/visit.go b/vendor/github.com/open-policy-agent/opa/v1/ast/visit.go index c054b92b29..c291bcdc50 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ast/visit.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ast/visit.go @@ -48,6 +48,7 @@ type ( SkipWithTarget bool SkipSets bool SkipTemplateStrings bool + SkipWildcardVars bool customVisit func(vis *VarVisitor, v any) bool } @@ -191,9 +192,9 @@ func walk(v Visitor, x any) { Walk(w, t) }) case Set: - x.Foreach(func(t *Term) { + for _, t := range x.Slice() { Walk(w, t) - }) + } case *ArrayComprehension: Walk(w, x.Term) Walk(w, x.Body) @@ -425,10 +426,10 @@ func (tv *typeVisitor[T]) walk(x any, visit func(x T) bool) { tv.walk(x[i], visit) } case *object: - x.Foreach(func(k, v *Term) { - tv.walk(k, visit) - tv.walk(v, visit) - }) + for _, node := range x.sortedKeys() { + tv.walk(node.key, visit) + tv.walk(node.value, visit) + } case Object: for _, k := range x.Keys() { tv.walk(k, visit) @@ -570,10 +571,10 @@ func (vis *GenericVisitor) Walk(x any) { vis.Walk(x[i]) } case *object: - x.Foreach(func(k, _ *Term) { - vis.Walk(k) - vis.Walk(x.Get(k)) - }) + for _, node := range x.sortedKeys() { + vis.Walk(node.key) + vis.Walk(node.value) + } case Object: for _, k := range x.Keys() { vis.Walk(k) @@ -822,6 +823,13 @@ func (vis *VarVisitor) Vars() VarSet { // the visitor will _skip_ that branch of the AST func (vis *VarVisitor) visit(v any) bool { if vis.params.SkipObjectKeys { + if o, ok := v.(*object); ok { + // doesn't allocate / escape + for _, node := range o.sortedKeys() { + vis.Walk(node.value) + } + return true + } if o, ok := v.(Object); ok { o.Foreach(func(_, v *Term) { vis.Walk(v) @@ -910,6 +918,9 @@ func (vis *VarVisitor) visit(v any) bool { } } if v, ok := v.(Var); ok { + if vis.params.SkipWildcardVars && v.IsWildcard() { + return true + } vis.Add(v) return true } @@ -1032,7 +1043,7 @@ func (vis *VarVisitor) Walk(x any) { vis.Walk(x.Parts[i]) } case *Not: - vis.Walk(x.Body) + vis.WalkBody(x.Body) case *LogicalAnd: vis.WalkBody(x.Lhs) vis.WalkBody(x.Rhs) diff --git a/vendor/github.com/open-policy-agent/opa/v1/bundle/bundle.go b/vendor/github.com/open-policy-agent/opa/v1/bundle/bundle.go index f697e336d5..289ba274dc 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/bundle/bundle.go +++ b/vendor/github.com/open-policy-agent/opa/v1/bundle/bundle.go @@ -18,6 +18,7 @@ import ( "path" "path/filepath" "reflect" + "slices" "strings" "sync" @@ -165,7 +166,7 @@ type Manifest struct { } type fileRegoVersion struct { - path glob.Glob + path *glob.Pattern version int } @@ -339,7 +340,7 @@ func (ss stringSet) Equal(other stringSet) bool { return true } -func (m *Manifest) validateAndInjectDefaults(b Bundle) error { +func (m *Manifest) validateAndInjectDefaults(b *Bundle) error { m.Init() // Validate roots in bundle. @@ -361,8 +362,8 @@ func (m *Manifest) validateAndInjectDefaults(b Bundle) error { // Validate modules in bundle. for _, module := range b.Modules { found := false - if path, err := module.Parsed.Package.Path.Ptr(); err == nil { - found = RootPathsContain(roots, path) + if path, err := storage.NewPathForRef(module.Parsed.Package.Path); err == nil { + found = rootPathsContainSegments(roots, path) } if !found { return fmt.Errorf("manifest roots %v do not permit '%v' in module '%s'", roots, module.Parsed.Package, module.Path) @@ -680,7 +681,7 @@ func (r *Reader) Read() (Bundle, error) { // Normalize the paths to use `/` separators path := filepath.ToSlash(f.Path()) - if strings.HasSuffix(path, RegoExt) { + if strings.HasSuffix(path, RegoExt) { //nolint: gocritic // ifElseChain fullPath := r.fullPath(path) bs := buf.Bytes() @@ -837,7 +838,7 @@ func (r *Reader) Read() (Bundle, error) { "file(s) %v specified in bundle signatures but not found in the target bundle", util.Keys(r.files)) } - if err := bundle.Manifest.validateAndInjectDefaults(*bundle); err != nil { + if err := bundle.Manifest.validateAndInjectDefaults(bundle); err != nil { return empty, err } @@ -1411,23 +1412,18 @@ func (b Bundle) Equal(other Bundle) bool { // Copy returns a deep copy of the bundle. func (b Bundle) Copy() Bundle { - // Copy data. var x any = b.Data - - if err := util.RoundTrip(&x); err != nil { + if err := util.RoundTripFast(&x); err != nil { panic(err) } - if x != nil { b.Data = x.(map[string]any) } // Copy modules. for i := range b.Modules { - bs := make([]byte, len(b.Modules[i].Raw)) - copy(bs, b.Modules[i].Raw) - b.Modules[i].Raw = bs + b.Modules[i].Raw = slices.Clone(b.Modules[i].Raw) b.Modules[i].Parsed = b.Modules[i].Parsed.Copy() } @@ -1556,12 +1552,14 @@ func MergeWithRegoVersion(bundles []*Bundle, regoVersion ast.RegoVersion, usePat return result, nil } - var roots []string - var result Bundle + var ( + roots []string + planFile string + manifestProto bool + manifestProtoSet bool + ) - var planFile string - var manifestProto bool - var manifestProtoSet bool + result := &Bundle{} for _, b := range bundles { if b.Manifest.Roots == nil { @@ -1632,7 +1630,7 @@ func MergeWithRegoVersion(bundles []*Bundle, regoVersion ast.RegoVersion, usePat return nil, err } - return &result, nil + return result, nil } func bundleRegoVersions(bundle *Bundle, regoVersion ast.RegoVersion, usePath bool) (map[string]int, error) { @@ -1695,7 +1693,14 @@ func RootPathsOverlap(pathA string, pathB string) bool { // RootPathsContain takes a set of bundle root paths and returns true if the path is contained. func RootPathsContain(roots []string, path string) bool { - segments := rootPathSegments(path) + return rootPathsContainSegments(roots, rootPathSegments(path)) +} + +// rootPathsContainSegments is RootPathsContain for a path that's already split +// into segments. Manifest roots are raw, unescaped strings, so callers holding +// a ref or storage path must pass its unescaped segments rather than the +// percent-encoded form produced by ast.Ref.Ptr or storage.Path.String. +func rootPathsContainSegments(roots []string, segments []string) bool { for i := range roots { if rootContains(rootPathSegments(roots[i]), segments) { return true @@ -1815,10 +1820,8 @@ func preProcessBundle(loader DirectoryLoader, skipVerify bool, sizeLimitBytes in base := filepath.Base(f.Path()) if base == patchFile { - - var b bytes.Buffer - tee := io.TeeReader(f.reader, &b) - f.reader = tee + b := new(bytes.Buffer) + f.reader = io.TeeReader(f.reader, b) buf, err := readFile(f, sizeLimitBytes) if err != nil { @@ -1829,7 +1832,7 @@ func preProcessBundle(loader DirectoryLoader, skipVerify bool, sizeLimitBytes in return bundle, nil, fmt.Errorf("bundle load failed on patch decode: %w", err) } - f.reader = &b + f.reader = b } } } diff --git a/vendor/github.com/open-policy-agent/opa/v1/bundle/file.go b/vendor/github.com/open-policy-agent/opa/v1/bundle/file.go index e4f74cc0e4..44a0a77976 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/bundle/file.go +++ b/vendor/github.com/open-policy-agent/opa/v1/bundle/file.go @@ -3,17 +3,19 @@ package bundle import ( "archive/tar" "bytes" + "cmp" "compress/gzip" "fmt" "io" "io/fs" "os" "path/filepath" - "sort" + "slices" "strings" "sync" "github.com/open-policy-agent/opa/v1/loader/filter" + "github.com/open-policy-agent/opa/v1/util" "github.com/open-policy-agent/opa/v1/storage" ) @@ -193,10 +195,7 @@ func (d *dirLoader) WithFollowSymlinks(followSymlinks bool) DirectoryLoader { func formatPath(fileName string, root string, pathFormat PathFormat) string { switch pathFormat { case SlashRooted: - if !strings.HasPrefix(fileName, string(filepath.Separator)) { - return string(filepath.Separator) + fileName - } - return fileName + return util.WithPrefix(fileName, string(filepath.Separator)) case Chrooted: // Trim off the root directory and return path as if chrooted result := strings.TrimPrefix(fileName, filepath.FromSlash(root)) @@ -206,10 +205,7 @@ func formatPath(fileName string, root string, pathFormat PathFormat) string { if root == "." && (filepath.Base(fileName) == ManifestExt || filepath.Base(fileName) == ManifestProtoExt) { result = fileName } - if !strings.HasPrefix(result, string(filepath.Separator)) { - result = string(filepath.Separator) + result - } - return result + return util.WithPrefix(result, string(filepath.Separator)) case Passthrough: fallthrough default: @@ -444,13 +440,13 @@ func (it *iterator) Next() (*storage.Update, error) { } f.path = p - f.raw = item.Value - it.files = append(it.files, f) } - sortFilePathAscend(it.files) + slices.SortFunc(it.files, func(a, b file) int { + return cmp.Compare(len(a.path), len(b.path)) + }) } // If done reading files then just return io.EOF @@ -487,20 +483,19 @@ func NewIterator(raw []Raw) storage.Iterator { return &it } -func sortFilePathAscend(files []file) { - sort.Slice(files, func(i, j int) bool { - return len(files[i].path) < len(files[j].path) - }) -} - func getdepth(path string, isDir bool) int { if isDir { cleanedPath := strings.Trim(filepath.ToSlash(path), "/") - return len(strings.Split(cleanedPath, "/")) + return segmentCount(cleanedPath) } basePath := strings.Trim(filepath.Dir(filepath.ToSlash(path)), "/") - return len(strings.Split(basePath, "/")) + return segmentCount(basePath) +} + +// segmentCount avoids the []string allocation of len(strings.Split(path, "/")). +func segmentCount(path string) int { + return strings.Count(path, "/") + 1 } func getFileStoragePath(path string) (storage.Path, error) { diff --git a/vendor/github.com/open-policy-agent/opa/v1/bundle/hash.go b/vendor/github.com/open-policy-agent/opa/v1/bundle/hash.go index dd9dfe5149..66ce6a9ced 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/bundle/hash.go +++ b/vendor/github.com/open-policy-agent/opa/v1/bundle/hash.go @@ -78,7 +78,7 @@ func NewSignatureHasher(alg HashingAlgorithm) (SignatureHasher, error) { // HashFile hashes the file content, JSON or binary, both in golang native format. func (h *hasher) HashFile(v any) ([]byte, error) { hf := h.h() - walk(v, hf) + walk(v, hf, newPrimitiveEncoder()) return hf.Sum(nil), nil } @@ -91,7 +91,7 @@ func (h *hasher) HashFile(v any) ([]byte, error) { // object: Hash {, then each key (in alphabetical order) and digest of the value, then comma (between items) and finally }. // // array: Hash [, then digest of the value, then comma (between items) and finally ]. -func walk(v any, h io.Writer) { +func walk(v any, h io.Writer, pe *primitiveEncoder) { switch x := v.(type) { case map[string]any: @@ -102,9 +102,9 @@ func walk(v any, h io.Writer) { _, _ = h.Write([]byte(",")) } - _, _ = h.Write(encodePrimitive(key)) + _, _ = h.Write(pe.encode(key)) _, _ = h.Write([]byte(":")) - walk(x[key], h) + walk(x[key], h, pe) } _, _ = h.Write([]byte("}")) @@ -115,21 +115,38 @@ func walk(v any, h io.Writer) { if i > 0 { _, _ = h.Write([]byte(",")) } - walk(e, h) + walk(e, h, pe) } _, _ = h.Write([]byte("]")) case []byte: _, _ = h.Write(x) default: - _, _ = h.Write(encodePrimitive(x)) + _, _ = h.Write(pe.encode(x)) } } -func encodePrimitive(v any) []byte { - var buf bytes.Buffer - encoder := json.NewEncoder(&buf) - encoder.SetEscapeHTML(false) - _ = encoder.Encode(v) - return bytes.Trim(buf.Bytes(), "\n") +// primitiveEncoder reuses its buffer across encode calls. +type primitiveEncoder struct { + buf *bytes.Buffer + enc *json.Encoder +} + +func newPrimitiveEncoder() *primitiveEncoder { + buf := new(bytes.Buffer) + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + return &primitiveEncoder{buf: buf, enc: enc} +} + +// encode's return value aliases the encoder's buffer and is only valid +// until the next call. +func (pe *primitiveEncoder) encode(v any) []byte { + pe.buf.Reset() + _ = pe.enc.Encode(v) + return bytes.Trim(pe.buf.Bytes(), "\n") +} + +func encodePrimitive(v any) []byte { + return newPrimitiveEncoder().encode(v) } diff --git a/vendor/github.com/open-policy-agent/opa/v1/bundle/proto.go b/vendor/github.com/open-policy-agent/opa/v1/bundle/proto.go index 500be418d5..fb700aba32 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/bundle/proto.go +++ b/vendor/github.com/open-policy-agent/opa/v1/bundle/proto.go @@ -9,7 +9,6 @@ import ( "fmt" "net/url" - "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "github.com/open-policy-agent/opa/v1/ast" @@ -26,11 +25,11 @@ func ManifestToProto(m *Manifest) (*pb.Manifest, error) { return nil, nil } out := &pb.Manifest{ - Revision: proto.String(m.Revision), + Revision: new(m.Revision), } if m.Roots != nil { out.Roots = append([]string(nil), (*m.Roots)...) - out.RootsSet = proto.Bool(true) + out.RootsSet = new(true) } if len(m.WasmResolvers) > 0 { out.Wasm = make([]*pb.WasmResolver, len(m.WasmResolvers)) @@ -43,7 +42,7 @@ func ManifestToProto(m *Manifest) (*pb.Manifest, error) { } } if m.RegoVersion != nil { - out.RegoVersion = proto.Int32(int32(*m.RegoVersion)) + out.RegoVersion = new(int32(*m.RegoVersion)) } if len(m.FileRegoVersions) > 0 { out.FileRegoVersions = make(map[string]int32, len(m.FileRegoVersions)) @@ -66,8 +65,8 @@ func wasmResolverToProto(w *WasmResolver) (*pb.WasmResolver, error) { return nil, nil } out := &pb.WasmResolver{ - Entrypoint: proto.String(w.Entrypoint), - Module: proto.String(w.Module), + Entrypoint: new(w.Entrypoint), + Module: new(w.Module), } if len(w.Annotations) > 0 { out.Annotations = make([]*pb.Annotations, len(w.Annotations)) @@ -87,10 +86,10 @@ func annotationsToProto(a *ast.Annotations) (*pb.Annotations, error) { return nil, nil } out := &pb.Annotations{ - Scope: proto.String(a.Scope), - Title: proto.String(a.Title), - Entrypoint: proto.Bool(a.Entrypoint), - Description: proto.String(a.Description), + Scope: new(a.Scope), + Title: new(a.Title), + Entrypoint: new(a.Entrypoint), + Description: new(a.Description), Organizations: append([]string(nil), a.Organizations...), } if len(a.RelatedResources) > 0 { @@ -143,8 +142,8 @@ func relatedResourceToProto(r *ast.RelatedResourceAnnotation) *pb.RelatedResourc return nil } return &pb.RelatedResourceAnnotation{ - Ref: proto.String(r.Ref.String()), - Description: proto.String(r.Description), + Ref: new(r.Ref.String()), + Description: new(r.Description), } } @@ -153,8 +152,8 @@ func authorToProto(a *ast.AuthorAnnotation) *pb.AuthorAnnotation { return nil } return &pb.AuthorAnnotation{ - Name: proto.String(a.Name), - Email: proto.String(a.Email), + Name: new(a.Name), + Email: new(a.Email), } } @@ -163,8 +162,8 @@ func schemaToProto(s *ast.SchemaAnnotation) (*pb.SchemaAnnotation, error) { return nil, nil } out := &pb.SchemaAnnotation{ - Path: proto.String(s.Path.String()), - Schema: proto.String(s.Schema.String()), + Path: new(s.Path.String()), + Schema: new(s.Schema.String()), } if s.Definition != nil { v, err := jsonNormalizeValue(*s.Definition) @@ -181,7 +180,7 @@ func compileToProto(c *ast.CompileAnnotation) *pb.CompileAnnotation { return nil } out := &pb.CompileAnnotation{ - MaskRule: proto.String(c.MaskRule.String()), + MaskRule: new(c.MaskRule.String()), } if len(c.Unknowns) > 0 { out.Unknowns = make([]string, len(c.Unknowns)) @@ -197,9 +196,9 @@ func locationToProto(l *location.Location) *pb.Location { return nil } return &pb.Location{ - File: proto.String(l.File), - Row: proto.Int32(int32(l.Row)), - Col: proto.Int32(int32(l.Col)), + File: new(l.File), + Row: new(int32(l.Row)), + Col: new(int32(l.Col)), } } diff --git a/vendor/github.com/open-policy-agent/opa/v1/bundle/store.go b/vendor/github.com/open-policy-agent/opa/v1/bundle/store.go index 1784c98f5f..7d283f87bc 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/bundle/store.go +++ b/vendor/github.com/open-policy-agent/opa/v1/bundle/store.go @@ -12,7 +12,7 @@ import ( "fmt" "maps" "path/filepath" - "sort" + "slices" "strings" "sync" @@ -528,7 +528,7 @@ func activateBundles(opts *ActivateOpts) error { } // Compile the modules all at once to avoid having to re-do work. - remainingAndExtra := make(map[string]*ast.Module) + remainingAndExtra := make(map[string]*ast.Module, len(remaining)+len(opts.ExtraModules)) maps.Copy(remainingAndExtra, remaining) maps.Copy(remainingAndExtra, opts.ExtraModules) @@ -805,13 +805,13 @@ func erasePolicies(ctx context.Context, store storage.Store, txn storage.Transac if err != nil { return nil, nil, err } - path, err := module.Package.Path.Ptr() + path, err := storage.NewPathForRef(module.Package.Path) if err != nil { return nil, nil, err } deleted := false for root := range roots { - if RootPathsContain([]string{root}, path) { + if rootPathsContainSegments([]string{root}, path) { if err := store.DeletePolicy(ctx, txn, id); err != nil { return nil, nil, err } @@ -833,25 +833,16 @@ func erasePolicies(ctx context.Context, store storage.Store, txn storage.Transac func writeManifestToStore(opts *ActivateOpts, name string, manifest Manifest) error { // Always write manifests to the named location. If the plugin is in the older style config // then also write to the old legacy unnamed location. - if err := WriteManifestToStore(opts.Ctx, opts.Store, opts.Txn, name, manifest); err != nil { - return err + err := WriteManifestToStore(opts.Ctx, opts.Store, opts.Txn, name, manifest) + if err == nil && opts.legacy { + err = LegacyWriteManifestToStore(opts.Ctx, opts.Store, opts.Txn, manifest) } - if opts.legacy { - if err := LegacyWriteManifestToStore(opts.Ctx, opts.Store, opts.Txn, manifest); err != nil { - return err - } - } - - return nil + return err } func writeEtagToStore(opts *ActivateOpts, name, etag string) error { - if err := WriteEtagToStore(opts.Ctx, opts.Store, opts.Txn, name, etag); err != nil { - return err - } - - return nil + return WriteEtagToStore(opts.Ctx, opts.Store, opts.Txn, name, etag) } func writeModuleRegoVersionToStore(ctx context.Context, store storage.Store, txn storage.Transaction, b *Bundle, @@ -927,7 +918,7 @@ func writeDataAndModules(ctx context.Context, store storage.Store, txn storage.T if m := f.module; m != nil { // 'f.module.Path' contains the module's path as it relates to the bundle root, and can be used for looking up the rego-version. // 'f.Path' can differ, based on how the bundle reader was initialized. - if err := writeModuleRegoVersionToStore(ctx, store, txn, b, *m, p.String(), runtimeRegoVersion); err != nil { + if err := writeModuleRegoVersionToStore(ctx, store, txn, b, *m, p.PolicyID(), runtimeRegoVersion); err != nil { return err } } @@ -994,67 +985,13 @@ func compileModules(compiler *ast.Compiler, m metrics.Metrics, bundles map[strin return compiler.Errors } - if authorizationDecisionRef.Equal(ast.EmptyRef()) { + if authorizationDecisionRef.Equal(ast.InternedEmptyRefValue) { return nil } return iCompiler.VerifyAuthorizationPolicySchema(compiler, authorizationDecisionRef) } -func writeModules(ctx context.Context, store storage.Store, txn storage.Transaction, compiler *ast.Compiler, m metrics.Metrics, bundles map[string]*Bundle, extraModules map[string]*ast.Module, legacy bool, externalSources *util.HasherMap[ast.Ref, ast.ExternalRuleSource]) error { - m.Timer(metrics.RegoModuleCompile).Start() - defer m.Timer(metrics.RegoModuleCompile).Stop() - - // Apply external sources before compilation - if externalSources != nil { - externalSources.Iter(func(ref ast.Ref, source ast.ExternalRuleSource) bool { - compiler = compiler.WithExternalSource(ref, source) - return false - }) - } - - modules := map[string]*ast.Module{} - - // preserve any modules already on the compiler - maps.Copy(modules, compiler.Modules) - - // preserve any modules passed in from the store - maps.Copy(modules, extraModules) - - // include all the new bundle modules - for bundleName, b := range bundles { - if legacy { - for _, mf := range b.Modules { - modules[mf.Path] = mf.Parsed - } - } else { - maps.Copy(modules, b.ParsedModules(bundleName)) - } - } - - if compiler.Compile(modules); compiler.Failed() { - return compiler.Errors - } - for bundleName, b := range bundles { - for _, mf := range b.Modules { - var path string - - // For backwards compatibility, in legacy mode, upsert policies to - // the unprefixed path. - if legacy { - path = mf.Path - } else { - path = modulePathWithPrefix(bundleName, mf.Path) - } - - if err := store.UpsertPolicy(ctx, txn, path, mf.Raw); err != nil { - return err - } - } - } - return nil -} - func lookup(path storage.Path, data map[string]any) (any, bool) { if len(path) == 0 { return data, true @@ -1163,11 +1100,11 @@ func hasRootsOverlap(ctx context.Context, store storage.Store, txn storage.Trans } // Sort the bundle roots list. - sort.Slice(entries, func(i, j int) bool { - if entries[i].canonical != entries[j].canonical { - return entries[i].canonical < entries[j].canonical + slices.SortFunc(entries, func(a, b rootEntry) int { + if c := strings.Compare(a.canonical, b.canonical); c != 0 { + return c } - return entries[i].bundle < entries[j].bundle + return strings.Compare(a.bundle, b.bundle) }) collidingBundles := map[string]bool{} @@ -1226,8 +1163,7 @@ func hasRootsOverlap(ctx context.Context, store storage.Store, txn storage.Trans // is allowed to declare overlapping roots in its own manifest. if sawCrossBundleConflict { collidingBundles[entries[d].bundle] = true - paths := []string{groupDisplay, entries[d].displayRoot()} - sort.Strings(paths) + paths := util.Sorted([]string{groupDisplay, entries[d].displayRoot()}) conflictSet[fmt.Sprintf("%s overlaps %s", paths[0], paths[1])] = true } } @@ -1314,11 +1250,7 @@ func LegacyWriteManifestToStore(ctx context.Context, store storage.Store, txn st // // Deprecated: Use WriteManifestToStore and named bundles instead. func LegacyEraseManifestFromStore(ctx context.Context, store storage.Store, txn storage.Transaction) error { - err := store.Write(ctx, txn, storage.RemoveOp, legacyManifestStoragePath, nil) - if err != nil { - return err - } - return nil + return store.Write(ctx, txn, storage.RemoveOp, legacyManifestStoragePath, nil) } // LegacyReadRevisionFromStore will read the bundle manifest revision from the older single (unnamed) bundle manifest location. diff --git a/vendor/github.com/open-policy-agent/opa/v1/bundle/v1pb/manifest.pb.go b/vendor/github.com/open-policy-agent/opa/v1/bundle/v1pb/manifest.pb.go index 018001f2d9..195e08eb00 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/bundle/v1pb/manifest.pb.go +++ b/vendor/github.com/open-policy-agent/opa/v1/bundle/v1pb/manifest.pb.go @@ -42,7 +42,7 @@ type Manifest struct { // Free-form metadata object. Modeled as `Struct` because the Go field // is `map[string]any`. Metadata *structpb.Struct `protobuf:"bytes,6,opt,name=metadata" json:"metadata,omitempty"` - // True iff `bundle.Manifest.Roots` was non-nil. `repeated string` can't + // True if `bundle.Manifest.Roots` was non-nil. `repeated string` can't // distinguish nil (default to [""]) from explicit-empty (owns no paths). RootsSet *bool `protobuf:"varint,7,opt,name=roots_set,json=rootsSet" json:"roots_set,omitempty"` unknownFields protoimpl.UnknownFields diff --git a/vendor/github.com/open-policy-agent/opa/v1/format/format.go b/vendor/github.com/open-policy-agent/opa/v1/format/format.go index 1f25938bae..b91ba6ff25 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/format/format.go +++ b/vendor/github.com/open-policy-agent/opa/v1/format/format.go @@ -10,12 +10,12 @@ import ( "errors" "fmt" "slices" - "sort" "strings" "unicode" "github.com/open-policy-agent/opa/internal/future" "github.com/open-policy-agent/opa/v1/ast" + "github.com/open-policy-agent/opa/v1/ast/location" "github.com/open-policy-agent/opa/v1/types" "github.com/open-policy-agent/opa/v1/util" ) @@ -28,6 +28,7 @@ const defaultLocationFile = "__format_default__" var ( expandedConst = ast.NewBody(ast.NewExpr(ast.InternedTerm(true))) commentsSlicePool = util.NewSlicePool[*ast.Comment](50) + negativeRow = &ast.Location{Row: -1} ) // Opts lets you control the code formatting via `AstWithOpts()`. @@ -169,7 +170,7 @@ type fmtOpts struct { func (o fmtOpts) keywords() []string { if o.regoV1 { - return ast.KeywordsV1[:] + return append(ast.KeywordsV1[:], o.futureKeywords...) } kws := ast.KeywordsV0[:] return append(kws, o.futureKeywords...) @@ -206,9 +207,6 @@ func AstWithOpts(x any, opts Opts) ([]byte, error) { } o.allowKeywordsInRefs = capabilities.ContainsFeature(ast.FeatureKeywordsInRefs) - memberRef := ast.Member.Ref() - memberWithKeyRef := ast.MemberWithKey.Ref() - // Preprocess the AST. Set any required defaults and calculate // values required for printing the formatted output. ast.WalkNodes(x, func(x ast.Node) bool { @@ -222,12 +220,23 @@ func AstWithOpts(x any, opts Opts) ([]byte, error) { case *ast.Expr: switch { - case n.IsCall() && memberRef.Equal(n.Operator()) || memberWithKeyRef.Equal(n.Operator()): + case n.IsCall() && ast.Interned.Refs.Member.Equal(n.Operator()) || + ast.Interned.Refs.MemberWithKey.Equal(n.Operator()): extraFutureKeywordImports["in"] = struct{}{} case n.IsEvery(): extraFutureKeywordImports["every"] = struct{}{} case n.IsNot(): extraFutureKeywordImports["not"] = struct{}{} + case n.IsAnd(): + extraFutureKeywordImports["and"] = struct{}{} + case n.IsOr(): + extraFutureKeywordImports["or"] = struct{}{} + } + + if n.Negated && isLogicalExpr(n) { + // A negated logical expression is written parenthesized + // (`not (a or b)`), which requires the `not` keyword. + extraFutureKeywordImports["not"] = struct{}{} } case *ast.Import: @@ -274,7 +283,7 @@ func AstWithOpts(x any, opts Opts) ([]byte, error) { switch x := x.(type) { case *ast.Module: if regoVersion == ast.RegoV1 && opts.DropV0Imports { - x.Imports = filterRegoV1Import(x.Imports) + x.Imports = slices.DeleteFunc(x.Imports, regoV1Import) } else if regoVersion == ast.RegoV0CompatV1 { x.Imports = ensureRegoV1Import(x.Imports) } @@ -409,6 +418,15 @@ func defaultLocation(x ast.Node) *ast.Location { } type writer struct { + // parenExpr, when set, is an expression whose terms must be wrapped in parens + // when written; consumed by the first writeExpr that sees it. Any `with` + // clauses stay outside the parens, as `(x | y with p as 1)` doesn't parse. + parenExpr *ast.Expr + + // parenTerm, when set, is a term that must be wrapped in parens when written; + // consumed by the first writeTermParens that sees it. + parenTerm *ast.Term + buf bytes.Buffer indent string @@ -442,29 +460,47 @@ func (w *writer) writeModule(module *ast.Module) error { }) visitor.Walk(module) - sort.Slice(comments, func(i, j int) bool { - l, err := locLess(comments[i], comments[j]) + slices.SortFunc(comments, func(a, b *ast.Comment) int { + al, bl, err := getLocs(a, b) if err != nil { w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error())) } - return l + return locCmp(al, bl) }) - sort.Slice(others, func(i, j int) bool { - l, err := locLess(others[i], others[j]) + slices.SortFunc(others, func(a, b any) int { + al, bl, err := getLocs(a, b) if err != nil { w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error())) } - return l + return locCmp(al, bl) }) comments = trimTrailingWhitespaceInComments(comments) + // Imports added by the formatter get an assigned line number, which can sort + // after a rule's. An import written after a rule has no effect. + var added []*ast.Import + if addedImportFollowsRule(others) { + others = slices.DeleteFunc(others, func(x any) bool { + imp, ok := x.(*ast.Import) + if !ok || !isAddedImport(imp) { + return false + } + added = append(added, imp) + return true + }) + } + var err error comments, err = w.writePackage(pkg, comments) if err != nil { return err } + comments, err = w.writeImports(added, comments) + if err != nil { + return err + } var imports []*ast.Import var rules []*ast.Rule for len(others) > 0 { @@ -474,10 +510,7 @@ func (w *writer) writeModule(module *ast.Module) error { return err } rules, others = gatherRules(others) - comments, err = w.writeRules(rules, comments) - if err != nil { - return err - } + comments = w.writeRules(rules, comments) } for i, c := range comments { @@ -532,7 +565,7 @@ func (w *writer) writeComments(comments []*ast.Comment) error { var inMetadataBlock bool for i := range comments { if i > 0 { - l, err := locCmp(comments[i], comments[i-1]) + l, err := locCmpOrError(comments[i], comments[i-1]) if err != nil { return err } @@ -558,17 +591,19 @@ func (w *writer) writeComments(comments []*ast.Comment) error { return nil } -func (w *writer) writeRules(rules []*ast.Rule, comments []*ast.Comment) ([]*ast.Comment, error) { +func (w *writer) writeRules(rules []*ast.Rule, comments []*ast.Comment) []*ast.Comment { for i, rule := range rules { var err error - comments, err = w.insertComments(comments, rule.Location) - if err != nil && !errors.As(err, &unexpectedCommentError{}) { - w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error())) + if comments, err = w.insertComments(comments, rule.Location); err != nil { + if _, ok := errors.AsType[unexpectedCommentError](err); !ok { + w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error())) + } } - comments, err = w.writeRule(rule, false, comments) - if err != nil && !errors.As(err, &unexpectedCommentError{}) { - w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error())) + if comments, err = w.writeRule(rule, false, comments); err != nil { + if _, ok := errors.AsType[unexpectedCommentError](err); !ok { + w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error())) + } } if i < len(rules)-1 && w.groupableOneLiner(rule) { @@ -581,9 +616,13 @@ func (w *writer) writeRules(rules []*ast.Rule, comments []*ast.Comment) ([]*ast. } w.blankLine() } - return comments, nil + return comments } +// groupableOneLiner reports whether rule is written on a single line, and so may +// be grouped with an adjacent rule instead of being followed by a blank line. +// These conditions must agree with the inline body branch of writeRule, which +// doesn't end the line after the closing brace of a multi-line body. func (w *writer) groupableOneLiner(rule *ast.Rule) bool { // Location required to determine if two rules are adjacent in the policy. // If not, we respect line breaks between rules. @@ -591,6 +630,18 @@ func (w *writer) groupableOneLiner(rule *ast.Rule) bool { return false } + // An else block is always written on a line of its own, so the rule spans + // multiple lines even when its own body is written inline. + if rule.Else != nil { + return false + } + + // A lone set term body keeps its enclosing braces, and so is written as a + // multi-line block. + if len(rule.Body) == 1 && isSetTerm(rule.Body[0]) { + return false + } + partialSetException := w.fmtOpts.contains || rule.Head.Value != nil return (w.fmtOpts.regoV1 || w.fmtOpts.ifs) && partialSetException @@ -620,9 +671,7 @@ func (w *writer) writeRule(rule *ast.Rule, isElse bool, comments []*ast.Comment) var unexpectedComment bool comments, err = w.writeHead(rule.Head, rule.Default, isExpandedConst, comments) if err != nil { - if errors.As(err, &unexpectedCommentError{}) { - unexpectedComment = true - } else { + if unexpectedComment = isUnexpectedCommentError(err); !unexpectedComment { return nil, err } } @@ -637,20 +686,32 @@ func (w *writer) writeRule(rule *ast.Rule, isElse bool, comments []*ast.Comment) // this excludes partial sets UNLESS `contains` is used partialSetException := w.fmtOpts.contains || rule.Head.Value != nil - if (w.fmtOpts.regoV1 || w.fmtOpts.ifs) && partialSetException { + usesIf := (w.fmtOpts.regoV1 || w.fmtOpts.ifs) && partialSetException + + if usesIf { w.write(" if") if len(rule.Body) == 1 { // Keep `if ` on one line when the single body term sits on the // same line as the end of the head. Comparing against the head's // start row would wrongly expand the condition into a block whenever // the head value spans multiple lines (e.g. a multi-line call). - headEndRow := rule.Head.Location.Row + strings.Count(string(rule.Head.Location.Text), "\n") - if rule.Body[0].Location.Row == headEndRow { + // + // Additionally, a single set term must not be stripped of the outer body + // braces, as that would semantically change the inner set to a body: + // `p if { { x } }` -> p if { x } + headEndRow, _ := location.EndOf(rule.Head.Location.Row, rule.Head.Location.Col, rule.Head.Location.Text) + if rule.Body[0].Location.Row == headEndRow && !isSetTerm(rule.Body[0]) { w.write(" ") var err error comments, err = w.writeExpr(rule.Body[0], comments) if err != nil { - return nil, err + // An unexpected comment isn't fatal: the expression was + // written as-is, and the comments returned still need + // writing. Dropping them would lose every comment after + // this rule. + if _, ok := errors.AsType[unexpectedCommentError](err); !ok { + return nil, err + } } w.endLine() if rule.Else != nil { @@ -670,12 +731,19 @@ func (w *writer) writeRule(rule *ast.Rule, isElse bool, comments []*ast.Comment) w.endLine() } + // A leading set union renders as `x | y`, which the parser reads as a + // comprehension at the brace of a `p if { ... }` body, so it is parenthesized. + // An `else` body has no such ambiguity: its braces always open a body. + if usesIf && !isElse { + w.markUnionLead(rule.Body[0]) + } + w.up() comments, err = w.writeBody(rule.Body, comments) if err != nil { // the unexpected comment error is passed up to be handled by writeHead - if !errors.As(err, &unexpectedCommentError{}) { + if _, ok := errors.AsType[unexpectedCommentError](err); !ok { return nil, err } } @@ -811,12 +879,9 @@ func (w *writer) writeHead(head *ast.Head, isDefault bool, isExpandedConst bool, if len(head.Args) > 0 { w.write("(") - var args []any - for _, arg := range head.Args { - args = append(args, arg) - } + args := util.ToSliceOf[any](head.Args) var err error - comments, err = w.writeIterable(args, head.Location, closingLoc(0, 0, '(', ')', head.Location), comments, w.listWriter()) + comments, err = w.writeIterable(args, head.Location, closingLoc(0, 0, '(', ')', head.Location), comments, w.listWriter(false)) w.write(")") if err != nil { return comments, err @@ -912,9 +977,10 @@ func (w *writer) writeBody(body ast.Body, comments []*ast.Comment) ([]*ast.Comme } w.startLine() - comments, err = w.writeExpr(expr, comments) - if err != nil && !errors.As(err, &unexpectedCommentError{}) { - w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error())) + if comments, err = w.writeExpr(expr, comments); err != nil { + if _, ok := errors.AsType[unexpectedCommentError](err); !ok { + w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error())) + } } w.endLine() } @@ -922,6 +988,11 @@ func (w *writer) writeBody(body ast.Body, comments []*ast.Comment) ([]*ast.Comme } func (w *writer) writeExpr(expr *ast.Expr, comments []*ast.Comment) ([]*ast.Comment, error) { + parenTerms := w.parenExpr == expr + if parenTerms { + w.parenExpr = nil + } + var err error comments, err = w.insertComments(comments, expr.Location) if err != nil { @@ -931,8 +1002,20 @@ func (w *writer) writeExpr(expr *ast.Expr, comments []*ast.Comment) ([]*ast.Comm w.startLine() } + // `not` binds tighter than `and`/`or`, so a negated logical expression is + // parenthesized. Only reachable through programmatically built ASTs; the + // parser represents `not (a or b)` as an *ast.Not. + negatedLogical := expr.Negated && isLogicalExpr(expr) + if expr.Negated { w.write("not ") + if negatedLogical { + w.write("(") + } + } + + if parenTerms { + w.write("(") } switch t := expr.Terms.(type) { @@ -951,6 +1034,11 @@ func (w *writer) writeExpr(expr *ast.Expr, comments []*ast.Comment) ([]*ast.Comm if err != nil { return nil, err } + case *ast.LogicalAnd, *ast.LogicalOr: + comments, err = w.writeLogical(expr, comments) + if err != nil { + return nil, err + } case []*ast.Term: comments, err = w.writeFunctionCall(expr, comments) if err != nil { @@ -963,6 +1051,14 @@ func (w *writer) writeExpr(expr *ast.Expr, comments []*ast.Comment) ([]*ast.Comm } } + if parenTerms { + w.write(")") + } + + if negatedLogical { + w.write(")") + } + if len(expr.With) == 0 { return comments, nil } @@ -1026,7 +1122,23 @@ func exprTermsEndRow(expr *ast.Expr) int { } } text = bytes.TrimRight(text, " \t\r\n") - return loc.Row + bytes.Count(text, []byte{'\n'}) + endRow, _ := location.EndOf(loc.Row, loc.Col, text) + return endRow +} + +// isSetTerm reports whether expr is a non-negated set term. +func isSetTerm(expr *ast.Expr) bool { + if expr.IsNegated() { + return false + } + + term, ok := expr.Terms.(*ast.Term) + if !ok { + return false + } + + _, ok = term.Value.(ast.Set) + return ok } func (w *writer) writeSomeDecl(decl *ast.SomeDecl, comments []*ast.Comment) ([]*ast.Comment, error) { @@ -1099,11 +1211,9 @@ func (w *writer) writeEvery(every *ast.Every, loc *ast.Location, comments []*ast } w.write(" {") comments, err = w.writeComprehensionBody('{', '}', every.Body, loc, loc, comments) - if err != nil { + if err != nil && !isUnexpectedCommentError(err) { // the unexpected comment error is passed up to be handled by writeHead - if !errors.As(err, &unexpectedCommentError{}) { - return nil, err - } + return nil, err } if len(every.Body) == 1 && @@ -1128,31 +1238,298 @@ func (w *writer) writeNot(not *ast.Not, loc *ast.Location, comments []*ast.Comme w.write("not ") if not.ExplicitBody || len(not.Body) > 1 { - w.write("{") - comments, err = w.writeComprehensionBody('{', '}', not.Body, loc, loc, comments) - if err != nil { - if !errors.As(err, &unexpectedCommentError{}) { - return nil, err - } + // A leading set union renders as `x | y`, which the parser reads as a + // comprehension at the brace, so it is parenthesized. + if isUnionExpr(not.Body[0]) { + w.parenExpr = not.Body[0] } - if len(not.Body) == 1 && - not.Body[0].Location.Row == loc.Row { + w.write("{") + comments, err = w.writeComprehensionBody('{', '}', not.Body, loc, loc, comments) + if err != nil && !isUnexpectedCommentError(err) { + return nil, err + } + + if last := not.Body[len(not.Body)-1]; last.Location != nil && last.Location.Row == loc.Row { w.write(" ") } w.write("}") } else { + parens := notBodyNeedsParens(not.Body[0]) + if parens { + w.write("(") + } + comments, err = w.writeExpr(not.Body[0], comments) - if err != nil { - if !errors.As(err, &unexpectedCommentError{}) { - return nil, err - } + if err != nil && !isUnexpectedCommentError(err) { + return nil, err + } + + if parens { + w.write(")") } } return comments, nil } +// notBodyNeedsParens reports whether the sole expression of an implicit `not` +// body must be parenthesized to be read back as that same expression. Mirrors +// notBodyNeedsParens in the ast package. +func notBodyNeedsParens(expr *ast.Expr) bool { + // A `with` on a bare operand of `not` binds to the whole `not` expression. + if len(expr.With) > 0 { + return true + } + + // `not` binds tighter than `and`/`or`. + if isLogicalExpr(expr) { + return true + } + + // `not not x` doesn't parse: a nested negation must be parenthesized to be + // read back as a body. + if _, ok := expr.Terms.(*ast.Not); ok { + return true + } + + // A value that renders brace-led would be re-read as an explicit body. + return exprRendersBraceLead(expr) +} + +// logicalOperand is one operand of an `and`/`or` chain. +type logicalOperand struct { + body ast.Body + + // explicit is set for `{...}` operands, which scope their contents and are + // always written braced. + explicit bool + + // parens is set for implicit operands that must be parenthesized to be read + // back as the same expression. + parens bool + + // brace is the location of the operand's opening `{`, for explicit operands. + brace *ast.Location +} + +// logicalStep is one operator application of an `and`/`or` chain. +type logicalStep struct { + op string + rhs logicalOperand + + // lhsEndRow is the row on which everything to the left of the operator ends. + lhsEndRow int +} + +// breaksLine reports whether the rhs operand is written on a line of its own, +// i.e. starts on a later row than the end of everything left of the operator. +// An explicit operand starts at its opening brace, an implicit one at its sole +// expression; a missing location leaves the row unknown, so no break. +func (s logicalStep) breaksLine() bool { + var start *ast.Location + if s.rhs.explicit { + start = s.rhs.brace + } else if len(s.rhs.body) > 0 { + start = s.rhs.body[0].Location + } + + return start != nil && start.Row > s.lhsEndRow +} + +func (w *writer) writeLogical(expr *ast.Expr, comments []*ast.Comment) ([]*ast.Comment, error) { + lhs, steps := flattenLogical(expr) + + comments, err := w.writeLogicalOperand(lhs, comments) + if err != nil && !isUnexpectedCommentError(err) { + return comments, err + } + + var indented bool + + for _, s := range steps { + w.write(" " + s.op) + + if s.breaksLine() { + if !indented { + w.up() + defer w.down() //nolint:errcheck + indented = true + } + w.endLine() + w.startLine() + } else { + w.write(" ") + } + + comments, err = w.writeLogicalOperand(s.rhs, comments) + if err != nil && !isUnexpectedCommentError(err) { + return comments, err + } + } + + return comments, nil +} + +func (w *writer) writeLogicalOperand(o logicalOperand, comments []*ast.Comment) ([]*ast.Comment, error) { + if !o.explicit { + if o.parens { + w.write("(") + defer w.write(")") + } + + return w.writeExpr(o.body[0], comments) + } + + if len(o.body) == 0 { + w.write("{}") + return comments, nil + } + + // A leading set union renders as `x | y`, which the parser reads as a + // comprehension at the brace, so it is parenthesized. + if isUnionExpr(o.body[0]) { + w.parenExpr = o.body[0] + } + + w.write("{") + comments, err := w.writeComprehensionBody('{', '}', o.body, o.brace, o.brace, comments) + if err != nil && !isUnexpectedCommentError(err) { + return comments, err + } + + if last := o.body[len(o.body)-1]; last.Location != nil && last.Location.Row == o.brace.Row { + w.write(" ") + } + w.write("}") + + return comments, nil +} + +// flattenLogical returns the leading operand and the operator applications of an +// `and`/`or` chain. Chains are left-associative, so the operands of +// `a and b and c` -- And{And{a, b}, c} -- are collected into a single chain, +// written with one level of continuation indent. A nested node that requires +// parens stays an operand of its own. +func flattenLogical(expr *ast.Expr) (logicalOperand, []logicalStep) { + op, lhs, rhs, explicitLhs, explicitRhs := logicalParts(expr) + + step := logicalStep{ + op: op, + rhs: newLogicalOperand(rhs, explicitRhs, op, true, expr.Location), + } + + if !explicitLhs && len(lhs) == 1 && isLogicalExpr(lhs[0]) && !logicalOperandNeedsParens(lhs[0], op, false) { + step.lhsEndRow = bodyEndRow(lhs) + first, steps := flattenLogical(lhs[0]) + + return first, append(steps, step) + } + + first := newLogicalOperand(lhs, explicitLhs, op, false, expr.Location) + step.lhsEndRow = logicalOperandEndRow(first) + + return first, []logicalStep{step} +} + +func logicalParts(expr *ast.Expr) (op string, lhs, rhs ast.Body, explicitLhs, explicitRhs bool) { + switch t := expr.Terms.(type) { + case *ast.LogicalAnd: + return "and", t.Lhs, t.Rhs, t.ExplicitLhs, t.ExplicitRhs + case *ast.LogicalOr: + return "or", t.Lhs, t.Rhs, t.ExplicitLhs, t.ExplicitRhs + } + + return "", nil, nil, false, false +} + +func newLogicalOperand(b ast.Body, explicit bool, parentOp string, rhs bool, node *ast.Location) logicalOperand { + if explicit || len(b) != 1 { + return logicalOperand{body: b, explicit: true, brace: operandBraceLoc(node, b)} + } + + return logicalOperand{body: b, parens: logicalOperandNeedsParens(b[0], parentOp, rhs)} +} + +// logicalOperandNeedsParens reports whether an implicit operand of parentOp must +// be parenthesized to be read back as that same expression. Mirrors +// logicalOperandNeedsParens in the ast package. +func logicalOperandNeedsParens(expr *ast.Expr, parentOp string, rhs bool) bool { + // A `with` on a bare operand binds to the whole and/or expression. + if len(expr.With) > 0 { + return true + } + + switch expr.Terms.(type) { + case *ast.LogicalOr: + // `or` binds looser than `and`: always parenthesize under `and`; under + // `or`, parenthesize only the rhs to preserve right-nesting. + return parentOp == "and" || rhs + case *ast.LogicalAnd: + // `and` binds tighter: no parens under `or`; under `and`, parenthesize + // only the rhs to preserve right-nesting. + return parentOp == "and" && rhs + } + + // A value that renders brace-led would be re-read as an explicit body. + return exprRendersBraceLead(expr) +} + +func logicalOperandEndRow(o logicalOperand) int { + if o.explicit { + if row := closingLoc(0, 0, '{', '}', o.brace).Row; row > 0 { + return row + } + } + + return bodyEndRow(o.body) +} + +// bodyEndRow returns the row of the last source line occupied by b. +func bodyEndRow(b ast.Body) int { + if len(b) == 0 { + return 0 + } + + loc := b[len(b)-1].Location + if loc == nil { + return 0 + } + + return loc.Row + bytes.Count(bytes.TrimRight(loc.Text, " \t\r\n"), []byte{'\n'}) +} + +// operandBraceLoc returns the location of the `{` opening an explicit operand +// body, derived from the location of the enclosing and/or node. The node +// location is returned as-is if the brace can't be located, e.g. for default +// locations. +func operandBraceLoc(node *ast.Location, b ast.Body) *ast.Location { + if node == nil || len(b) == 0 || b[0].Location == nil { + return node + } + + i := min(b[0].Location.Offset-node.Offset, len(node.Text)) + + for i--; i >= 0; i-- { + if node.Text[i] != '{' { + continue + } + + cpy := *node + cpy.Row = node.Row + bytes.Count(node.Text[:i], []byte{'\n'}) + cpy.Offset = node.Offset + i + cpy.Text = node.Text[i:] + + return &cpy + } + + return node +} + +func isLogicalExpr(expr *ast.Expr) bool { + return expr.IsAnd() || expr.IsOr() +} + func (w *writer) writeFunctionCall(expr *ast.Expr, comments []*ast.Comment) ([]*ast.Comment, error) { terms := expr.Terms.([]*ast.Term) @@ -1214,13 +1591,10 @@ func (w *writer) writeFunctionCallPlain(terms []*ast.Term, comments []*ast.Comme w.write("(") defer w.write(")") - args := make([]any, len(terms)-1) - for i, t := range terms[1:] { - args[i] = t - } + args := util.ToSliceOf[any](terms[1:]) loc := terms[0].Location var err error - comments, err = w.writeIterable(args, loc, closingLoc(0, 0, '(', ')', loc), comments, w.listWriter()) + comments, err = w.writeIterable(args, loc, closingLoc(0, 0, '(', ')', loc), comments, w.listWriter(false)) if err != nil { return nil, err } @@ -1243,14 +1617,12 @@ func (w *writer) writeWith(with *ast.With, comments []*ast.Comment, indented boo } w.write(" as ") comments, err = w.writeTerm(with.Value, comments) - if err != nil { + if err != nil && !isUnexpectedCommentError(err) { // An unexpectedCommentError from writeTerm signals that it fell // back to writing the term's original unformatted text — the value // was written successfully, so don't abort the surrounding chain // of `with` clauses (issue #8765). - if !errors.As(err, &unexpectedCommentError{}) { - return comments, err - } + return comments, err } return comments, nil } @@ -1279,14 +1651,14 @@ func (w *writer) writeTerm(term *ast.Term, comments []*ast.Comment) ([]*ast.Comm comments, err := w.writeTermParens(false, term, comments) if err != nil { - if errors.As(err, &unexpectedCommentError{}) { + if isUnexpectedCommentError(err) { w.buf.Truncate(currentLen) w.level = currentLevel // If beforeEnd refers to a comment within the source text range, clear it // This prevents the comment from being written twice if w.beforeEnd != nil && len(term.Location.Text) > 0 { - endRow := term.Location.Row + bytes.Count(term.Location.Text, []byte{'\n'}) + endRow, _ := location.EndOf(term.Location.Row, term.Location.Col, term.Location.Text) if w.beforeEnd.Location.Row >= term.Location.Row && w.beforeEnd.Location.Row <= endRow { w.beforeEnd = nil } @@ -1333,6 +1705,11 @@ func (w *writer) writeUnformatted(location *ast.Location, currentComments []*ast } func (w *writer) writeTermParens(parens bool, term *ast.Term, comments []*ast.Comment) ([]*ast.Comment, error) { + if w.parenTerm == term { + w.parenTerm = nil + parens = true + } + var err error comments, err = w.insertComments(comments, term.Location) if err != nil { @@ -1536,7 +1913,7 @@ func (w *writer) writeRef(x ast.Ref, comments []*ast.Comment) ([]*ast.Comment, e w.write("[") comments, err = w.writeTerm(t, comments) if err != nil { - if errors.As(err, &unexpectedCommentError{}) { + if _, ok := errors.AsType[unexpectedCommentError](err); ok { // add a new line so that the closing bracket isn't part of the unexpected comment w.write("\n") } else { @@ -1686,7 +2063,7 @@ func (w *writer) writeObject(obj ast.Object, loc *ast.Location, comments []*ast. w.write("{") defer w.write("}") - var s []any + s := make([]any, 0, obj.Len()) obj.Foreach(func(k, v *ast.Term) { s = append(s, ast.Item(k, v)) }) @@ -1697,12 +2074,12 @@ func (w *writer) writeArray(arr *ast.Array, loc *ast.Location, comments []*ast.C w.write("[") defer w.write("]") - var s []any + s := make([]any, 0, arr.Len()) arr.Foreach(func(t *ast.Term) { s = append(s, t) }) var err error - comments, err = w.writeIterable(s, loc, closingLoc(0, 0, '[', ']', loc), comments, w.listWriter()) + comments, err = w.writeIterable(s, loc, closingLoc(0, 0, '[', ']', loc), comments, w.listWriter(true)) if err != nil { return nil, err } @@ -1710,10 +2087,9 @@ func (w *writer) writeArray(arr *ast.Array, loc *ast.Location, comments []*ast.C } func (w *writer) writeSet(set ast.Set, loc *ast.Location, comments []*ast.Comment) ([]*ast.Comment, error) { - + var err error if set.Len() == 0 { w.write("set()") - var err error comments, err = w.insertComments(comments, closingLoc(0, 0, '(', ')', loc)) if err != nil { return nil, err @@ -1724,12 +2100,8 @@ func (w *writer) writeSet(set ast.Set, loc *ast.Location, comments []*ast.Commen w.write("{") defer w.write("}") - var s []any - set.Foreach(func(t *ast.Term) { - s = append(s, t) - }) - var err error - comments, err = w.writeIterable(s, loc, closingLoc(0, 0, '{', '}', loc), comments, w.listWriter()) + s := util.ToSliceOf[any](set.Slice()) + comments, err = w.writeIterable(s, loc, closingLoc(0, 0, '{', '}', loc), comments, w.listWriter(true)) if err != nil { return nil, err } @@ -1754,10 +2126,19 @@ func (w *writer) writeObjectComprehension(object *ast.ObjectComprehension, loc * w.write("{") defer w.write("}") - object.Value.Location = object.Key.Location // Ensure the value is not written on the next line. - if object.Key.Location.Row-loc.Row > 1 { - w.endLine() - w.startLine() + // Ensure the value is not written on the next line. writeComprehension + // breaks before the term whenever the term's row is below the row the + // comprehension opened on, so the value is given a location on that row + // rather than its own, which may already be a row further down. Copying + // the value's own location rather than the key's keeps Text intact, which + // writeComprehension reads to decide whether a call term was parenthesised. + valueLoc := *object.Value.Location + valueLoc.Row = loc.Row + object.Value.Location = &valueLoc + + paren := isUnionCall(object.Key) + if paren { + w.write("(") } var err error @@ -1765,6 +2146,10 @@ func (w *writer) writeObjectComprehension(object *ast.ObjectComprehension, loc * if err != nil { return nil, err } + if paren { + w.write(")") + } + w.write(": ") return w.writeComprehension('{', '}', object.Value, object.Body, loc, comments) } @@ -1776,9 +2161,8 @@ func (w *writer) writeComprehension(openChar, closeChar byte, term *ast.Term, bo } parens := false - _, ok := term.Value.(ast.Call) - if ok { - parens = term.Location.Text[0] == 40 // Starts with "(" + if _, ok := term.Value.(ast.Call); ok { + parens = isUnionCall(term) || term.Location.Text[0] == 40 // Starts with "(" } var err error comments, err = w.writeTermParens(parens, term, comments) @@ -1791,11 +2175,7 @@ func (w *writer) writeComprehension(openChar, closeChar byte, term *ast.Term, bo } func (w *writer) writeComprehensionBody(openChar, closeChar byte, body ast.Body, term, compr *ast.Location, comments []*ast.Comment) ([]*ast.Comment, error) { - exprs := make([]any, 0, len(body)) - for _, expr := range body { - exprs = append(exprs, expr) - } - lines, err := w.groupIterable(exprs, term) + lines, err := w.groupIterable(util.ToSliceOf[any](body), term) if err != nil { return nil, err } @@ -1992,15 +2372,117 @@ func (w *writer) writeIterableLine(elements []any, comments []*ast.Comment, fn e return fn(elements[i], comments) } +// isUnionExpr reports whether expr is a set-union call that renders as a bare +// `x | y`, which is comprehension syntax at an operand brace. +func isUnionExpr(expr *ast.Expr) bool { + terms, ok := expr.Terms.([]*ast.Term) + return ok && len(terms) == 3 && ast.Interned.Refs.Or.Equal(terms[0].Value) +} + +// markUnionLead parenthesizes the set union leading the rendering of expr, if +// there is one: a leading `x | y` reads as comprehension syntax at the brace of +// the body holding expr. The union is either the expression itself, or the +// leading operand of an infix call — one nested deeper is already parenthesized +// by writeCall. +func (w *writer) markUnionLead(expr *ast.Expr) { + if expr.Negated { + return + } + + if isLogicalExpr(expr) { + if lhs, _ := flattenLogical(expr); !lhs.explicit && !lhs.parens { + w.markUnionLead(lhs.body[0]) + } + + return + } + + if isUnionExpr(expr) { + w.parenExpr = expr + return + } + + terms, ok := expr.Terms.([]*ast.Term) + if !ok { + return + } + + // Infix calls render an operand first: the result for the assigned form + // (`z = x | y`), otherwise the lhs (`x | y == z`). + if bi, ok := ast.BuiltinMap[terms[0].Value.String()]; ok && bi.Infix != "" { + var lead *ast.Term + + switch len(terms) { + case bi.Decl.Arity() + 1: + lead = terms[1] + case bi.Decl.Arity() + 2: + lead = terms[len(terms)-1] + } + + if lead != nil && isUnionCall(lead) { + w.parenTerm = lead + } + } +} + +// exprRendersBraceLead reports whether expr renders starting with a `{`. Such an +// expression needs parens in an operand position, as bare braces there are read as +// an explicit body. Mirrors rendersWithLeadingBrace in the ast package. +func exprRendersBraceLead(expr *ast.Expr) bool { + switch t := expr.Terms.(type) { + case *ast.Term: + return termRendersBraceLead(t) + case []*ast.Term: + // Infix calls render an operand first: the result for the assigned form + // (`z = x | y`), otherwise the lhs (`{x} == y`). + if bi, ok := ast.BuiltinMap[t[0].Value.String()]; ok && bi.Infix != "" { + switch len(t) { + case bi.Decl.Arity() + 1: + return termRendersBraceLead(t[1]) + case bi.Decl.Arity() + 2: + return termRendersBraceLead(t[len(t)-1]) + } + } + } + + return false +} + +func termRendersBraceLead(t *ast.Term) bool { + switch v := t.Value.(type) { + case ast.Set: + // The empty set renders as `set()`. + return v.Len() > 0 + case ast.Object, *ast.SetComprehension, *ast.ObjectComprehension: + return true + case ast.Ref: + return len(v) > 0 && termRendersBraceLead(v[0]) + case ast.Call: + // An infix call renders an operand first, so a brace-led operand of a + // nested call leads the whole rendering: `{1, 2} & s == set()`. + if bi, ok := ast.BuiltinMap[v[0].Value.String()]; ok && bi.Infix != "" && + len(v) == bi.Decl.Arity()+1 { + return termRendersBraceLead(v[1]) + } + } + + return false +} + +// isUnionCall returns true if the term is a call to the union built-in, whose +// infix form (`|`) is comprehension syntax when used inside a collection +// literal or as a comprehension term, and must be parenthesized there. +func isUnionCall(t *ast.Term) bool { + call, ok := t.Value.(ast.Call) + return ok && ast.Interned.Refs.Or.Equal(call[0].Value) +} + func (w *writer) objectWriter() entryWriter { return func(x any, comments []*ast.Comment) ([]*ast.Comment, error) { entry := x.([2]*ast.Term) - call, isCall := entry[0].Value.(ast.Call) - - paren := false - if isCall && ast.Or.Ref().Equal(call[0].Value) && entry[0].Location.Text[0] == 40 { // Starts with "(" - paren = true + paren := isUnionCall(entry[0]) + if paren { w.write("(") } @@ -2015,8 +2497,7 @@ func (w *writer) objectWriter() entryWriter { w.write(": ") - call, isCall = entry[1].Value.(ast.Call) - if isCall && ast.Or.Ref().Equal(call[0].Value) && entry[1].Location.Text[0] == 40 { // Starts with "(" + if isUnionCall(entry[1]) { w.write("(") defer w.write(")") } @@ -2025,12 +2506,11 @@ func (w *writer) objectWriter() entryWriter { } } -func (w *writer) listWriter() entryWriter { +func (w *writer) listWriter(parenUnionCalls bool) entryWriter { return func(x any, comments []*ast.Comment) ([]*ast.Comment, error) { t, ok := x.(*ast.Term) - if ok { - call, isCall := t.Value.(ast.Call) - if isCall && ast.Or.Ref().Equal(call[0].Value) && t.Location.Text[0] == 40 { // Starts with "(" + if ok && isUnionCall(t) { + if parenUnionCalls || t.Location.Text[0] == 40 { // Starts with "(" w.write("(") defer w.write(")") } @@ -2074,7 +2554,7 @@ func (w *writer) groupIterable(elements []any, last *ast.Location) ([][]any, err } slices.SortFunc(elements, func(i, j any) int { - l, err := locCmp(i, j) + l, err := locCmpOrError(i, j) if err != nil { w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error())) } @@ -2220,34 +2700,27 @@ loop: return rules, others[i:] } -func locLess(a, b any) (bool, error) { - c, err := locCmp(a, b) - return c < 0, err +func locCmpOrError(a, b any) (int, error) { + al, bl, err := getLocs(a, b) + if err != nil { + return 0, err + } + return locCmp(al, bl), nil } -func locCmp(a, b any) (int, error) { - al, err := getLoc(a) - if err != nil { - return 0, err - } - bl, err := getLoc(b) - if err != nil { - return 0, err - } +func locCmp(a, b *ast.Location) int { switch { - case al == nil && bl == nil: - return 0, nil - case al == nil: - return -1, nil - case bl == nil: - return 1, nil + case a == b: + return 0 + case a == nil: + return -1 + case b == nil: + return 1 } - - if cmp := al.Row - bl.Row; cmp != 0 { - return cmp, nil - + if cmp := a.Row - b.Row; cmp != 0 { + return cmp } - return al.Col - bl.Col, nil + return a.Col - b.Col } func getLoc(x any) (*ast.Location, error) { @@ -2263,7 +2736,11 @@ func getLoc(x any) (*ast.Location, error) { } } -var negativeRow = &ast.Location{Row: -1} +func getLocs(a, b any) (*ast.Location, *ast.Location, error) { + al, err1 := getLoc(a) + bl, err2 := getLoc(b) + return al, bl, errors.Join(err1, err2) +} func closingLoc(skipOpen, skipClose, openChar, closeChar byte, loc *ast.Location) *ast.Location { i, offset := 0, 0 @@ -2453,10 +2930,9 @@ func ensureFutureKeywordImport(imps []*ast.Import, kw string) []*ast.Import { return imps } } - imp := &ast.Import{ - Path: ast.MustParseTerm("future.keywords." + kw), - } + imp := &ast.Import{Path: ast.MustParseTerm("future.keywords." + kw)} imp.Location = nextImportLoc(imps, imp) + return append(imps, imp) } @@ -2466,7 +2942,7 @@ func nextImportLoc(imps []*ast.Import, node ast.Node) *ast.Location { if imp.Loc() == nil { continue } - if isFutureKeywordsImport(imp) || isRegoV1Compatible(imp) { + if imp.Path.Value.(ast.Ref).HasPrefix(ast.FutureKeywordsRef[:1]) || isRegoV1Compatible(imp) { if imp.Loc().Row > maxRow { maxRow = imp.Loc().Row } @@ -2478,40 +2954,48 @@ func nextImportLoc(imps []*ast.Import, node ast.Node) *ast.Location { return ast.NewLocation([]byte(node.String()), defaultLocationFile, maxRow+1, 1) } -func isFutureKeywordsImport(imp *ast.Import) bool { - path := imp.Path.Value.(ast.Ref) - return len(path) >= 2 && ast.FutureRootDocument.Equal(path[0]) +func isAddedImport(imp *ast.Import) bool { + return imp.Loc() != nil && imp.Loc().File == defaultLocationFile +} + +// addedImportFollowsRule reports whether an import added by the formatter would +// be written at or after the first rule. +func addedImportFollowsRule(others []any) bool { + firstRule := -1 + for _, x := range others { + if r, ok := x.(*ast.Rule); ok && r.Loc() != nil { + if firstRule < 0 || r.Loc().Row < firstRule { + firstRule = r.Loc().Row + } + } + } + if firstRule < 0 { + return false + } + for _, x := range others { + if imp, ok := x.(*ast.Import); ok && isAddedImport(imp) && imp.Loc().Row >= firstRule { + return true + } + } + return false } func ensureRegoV1Import(imps []*ast.Import) []*ast.Import { - return ensureImport(imps, ast.RegoV1CompatibleRef) -} - -func filterRegoV1Import(imps []*ast.Import) []*ast.Import { - var ret []*ast.Import for _, imp := range imps { - path := imp.Path.Value.(ast.Ref) - if !ast.RegoV1CompatibleRef.Equal(path) { - ret = append(ret, imp) - } - } - return ret -} - -func ensureImport(imps []*ast.Import, path ast.Ref) []*ast.Import { - for _, imp := range imps { - p := imp.Path.Value.(ast.Ref) - if p.Equal(path) { + if ast.RegoV1CompatibleRef.Equal(imp.Path.Value) { return imps } } - imp := &ast.Import{ - Path: ast.NewTerm(path), - } + imp := &ast.Import{Path: ast.NewTerm(ast.RegoV1CompatibleRef)} imp.Location = nextImportLoc(imps, imp) + return append(imps, imp) } +func regoV1Import(imp *ast.Import) bool { + return ast.RegoV1CompatibleRef.Equal(imp.Path.Value) +} + // ArityFormatErrDetail but for `fmt` checks since compiler has not run yet. type ArityFormatErrDetail struct { Have []string `json:"have"` @@ -2552,3 +3036,8 @@ func isRegoV1Compatible(imp *ast.Import) bool { ast.RegoRootDocument.Equal(path[0]) && path[1].Equal(ast.InternedTerm("v1")) } + +func isUnexpectedCommentError(err error) bool { + _, ok := errors.AsType[unexpectedCommentError](err) + return ok +} diff --git a/vendor/github.com/open-policy-agent/opa/v1/ir/ir.go b/vendor/github.com/open-policy-agent/opa/v1/ir/ir.go index df6bbdcc1d..2e02fd6510 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ir/ir.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ir/ir.go @@ -15,15 +15,17 @@ package ir import ( "fmt" + "github.com/open-policy-agent/opa/v1/ast/location" "github.com/open-policy-agent/opa/v1/types" ) type ( // Policy represents a planned policy query. Policy struct { - Static *Static `json:"static,omitempty"` - Plans *Plans `json:"plans,omitempty"` - Funcs *Funcs `json:"funcs,omitempty"` + Static *Static `json:"static,omitempty"` + Plans *Plans `json:"plans,omitempty"` + Funcs *Funcs `json:"funcs,omitempty"` + UnplannedRules []*UnplannedRule `json:"unplanned_rules,omitempty"` } // Static represents a static data segment that is indexed into by the policy. @@ -84,7 +86,7 @@ type ( } locationStmt interface { - SetLocation(index, row, col int, file, text string) + SetLocation(index, row, col int, file string, text []byte) GetLocation() *Location } @@ -97,6 +99,15 @@ type ( StringConst struct { Value string `json:"value"` } + + // UnplannedRule represents a rule that was parsed but not included in the + // plan because it is not reachable from the entrypoint. + // This is used for coverage reporting, to distinguish rules that were never + // planned from rules that were planned but never executed. + UnplannedRule struct { + Path string `json:"path"` + Location *Location `json:"location"` + } ) const ( @@ -467,21 +478,45 @@ type ResultSetAddStmt struct { // Location records the filen index, and the row and column inside that file // that a statement can be connected to. type Location struct { - File int `json:"file"` // filename string constant index - Col int `json:"col"` - Row int `json:"row"` - file, text string // only used for debugging + File int `json:"file"` // filename string constant index + Col int `json:"col"` + Row int `json:"row"` + EndCol int `json:"end_col"` + EndRow int `json:"end_row"` + + // Text is only used for location ranges and debug prints. + // A named type is used so that its String method is called during printing. + // String cannot be set on Location since it is embedded and impacts parent + // structs if registered here. + Text locationText `json:"-"` + + file string // only used for debugging +} + +type locationText []byte + +func (d locationText) String() string { + return string(d) } // SetLocation sets the Location for a given Stmt. -func (l *Location) SetLocation(index, row, col int, file, text string) { +func (l *Location) SetLocation(index, row, col int, file string, text []byte) { *l = Location{ File: index, Row: row, Col: col, + Text: text, + file: file, - text: text, } + + l.EndRow, l.EndCol = location.EndOf(row, col, l.Text) +} + +// End returns the end row and col of the location range, expected to be called +// after SetLocation or unmarshalling. +func (l *Location) End() (row, col int) { + return l.EndRow, l.EndCol } // GetLocation returns a Stmt's Location. diff --git a/vendor/github.com/open-policy-agent/opa/v1/ir/marshal.go b/vendor/github.com/open-policy-agent/opa/v1/ir/marshal.go index f395fb2b62..6e13635f6c 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ir/marshal.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ir/marshal.go @@ -113,6 +113,8 @@ func (m *MakeNumberRefStmt) MarshalJSON() ([]byte, error) { File int `json:"file"` Col int `json:"col"` Row int `json:"row"` + EndCol int `json:"end_col"` + EndRow int `json:"end_row"` Index int `json:"index"` IndexLegacy int `json:"Index"` // deprecated; remove in next major Target Local `json:"target"` @@ -120,6 +122,8 @@ func (m *MakeNumberRefStmt) MarshalJSON() ([]byte, error) { File: m.File, Col: m.Col, Row: m.Row, + EndCol: m.EndCol, + EndRow: m.EndRow, Index: m.Index, IndexLegacy: m.Index, Target: m.Target, @@ -133,6 +137,8 @@ func (m *MakeNumberRefStmt) UnmarshalJSON(bs []byte) error { File int `json:"file"` Col int `json:"col"` Row int `json:"row"` + EndCol int `json:"end_col"` + EndRow int `json:"end_row"` Index *int `json:"index"` IndexLegacy *int `json:"Index"` Target Local `json:"target"` @@ -140,7 +146,7 @@ func (m *MakeNumberRefStmt) UnmarshalJSON(bs []byte) error { if err := json.Unmarshal(bs, &raw); err != nil { return err } - m.File, m.Col, m.Row, m.Target = raw.File, raw.Col, raw.Row, raw.Target + m.File, m.Col, m.Row, m.EndCol, m.EndRow, m.Target = raw.File, raw.Col, raw.Row, raw.EndCol, raw.EndRow, raw.Target switch { case raw.Index != nil: m.Index = *raw.Index diff --git a/vendor/github.com/open-policy-agent/opa/v1/ir/plan.proto b/vendor/github.com/open-policy-agent/opa/v1/ir/plan.proto index 9bd5541e8f..f1e5ba4342 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ir/plan.proto +++ b/vendor/github.com/open-policy-agent/opa/v1/ir/plan.proto @@ -14,6 +14,26 @@ message Policy { Static static = 1; Plans plans = 2; Funcs funcs = 3; + repeated UnplannedRule unplanned_rules = 4; +} + +// UnplannedRule mirrors `ir.UnplannedRule` in v1/ir/ir.go. +message UnplannedRule { + string path = 1; + Location location = 2; +} + +// Location mirrors `ir.Location` in v1/ir/ir.go. Note: Stmt inlines these +// same fields directly on its envelope (see the `Stmt` message below) +// rather than nesting a Location message, since every Stmt body embeds +// ir.Location anonymously; UnplannedRule references it as a named field +// instead, so it gets its own message here. +message Location { + int32 file = 1; + int32 col = 2; + int32 row = 3; + int32 end_col = 4; + int32 end_row = 5; } // Static mirrors `ir.Static` in v1/ir/ir.go. @@ -99,23 +119,26 @@ message Val { } // Stmt mirrors the `ir.Stmt` interface in v1/ir/ir.go. Every Stmt carries -// the source-location triple (file, col, row) on this envelope; the body -// messages below describe only the kind-specific payload. +// the source-location quintuple (file, col, row, end_col, end_row) on this +// envelope; the body messages below describe only the kind-specific +// payload. // // On the Go side, `ir.Location` is embedded into every concrete Stmt -// implementation, so `encoding/json` flattens File/Col/Row into the -// emitted JSON body. The proto promotes those fields to the envelope -// because that's both more idiomatic protobuf and lets every body +// implementation, so `encoding/json` flattens File/Col/Row/EndCol/EndRow +// into the emitted JSON body. The proto promotes those fields to the +// envelope because that's both more idiomatic protobuf and lets every body // message start its own field numbering at 1. // // Case-number assignments (4–37) are a stability commitment. Field -// numbers 1–3 are reserved for the location triple. New cases must be -// added with the next unused number; existing numbers must never be -// repurposed. +// numbers 1–3 and 38–39 are reserved for the location fields. New cases +// must be added with the next unused number; existing numbers must never +// be repurposed. message Stmt { int32 file = 1; int32 col = 2; int32 row = 3; + int32 end_col = 38; + int32 end_row = 39; oneof kind { ArrayAppendStmt array_append_stmt = 4; AssignIntStmt assign_int_stmt = 5; diff --git a/vendor/github.com/open-policy-agent/opa/v1/ir/plan.schema.json b/vendor/github.com/open-policy-agent/opa/v1/ir/plan.schema.json index e4af18ba23..5796706288 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ir/plan.schema.json +++ b/vendor/github.com/open-policy-agent/opa/v1/ir/plan.schema.json @@ -11,6 +11,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -27,6 +33,8 @@ "required": [ "array", "col", + "end_col", + "end_row", "file", "row", "value" @@ -39,6 +47,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -54,6 +68,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "target", @@ -67,6 +83,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -82,6 +104,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "source", @@ -95,6 +119,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -110,6 +140,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "source", @@ -138,6 +170,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -157,6 +195,8 @@ "required": [ "blocks", "col", + "end_col", + "end_row", "file", "row" ], @@ -168,6 +208,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -180,6 +226,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "index", "row" @@ -212,6 +260,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -243,6 +297,8 @@ "required": [ "args", "col", + "end_col", + "end_row", "file", "path", "result", @@ -256,6 +312,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -281,6 +343,8 @@ "required": [ "args", "col", + "end_col", + "end_row", "file", "func", "result", @@ -294,6 +358,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -312,6 +382,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "key", "row", @@ -326,6 +398,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -343,6 +421,8 @@ "a", "b", "col", + "end_col", + "end_row", "file", "row" ], @@ -414,6 +494,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -426,6 +512,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "source" @@ -438,6 +526,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -450,6 +544,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "source" @@ -462,6 +558,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -474,6 +576,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "source" @@ -486,6 +590,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -498,6 +608,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "source" @@ -510,6 +622,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -522,6 +640,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "source" @@ -534,6 +654,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -549,6 +675,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "source", @@ -556,12 +684,46 @@ ], "additionalProperties": false }, + "Location": { + "type": "object", + "properties": { + "col": { + "type": "integer" + }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, + "file": { + "type": "integer" + }, + "row": { + "type": "integer" + } + }, + "required": [ + "col", + "end_col", + "end_row", + "file", + "row" + ], + "additionalProperties": false + }, "MakeArrayStmt": { "type": "object", "properties": { "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -578,6 +740,8 @@ "required": [ "capacity", "col", + "end_col", + "end_row", "file", "row", "target" @@ -590,6 +754,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -602,6 +772,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "target" @@ -614,6 +786,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -629,6 +807,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "target", @@ -648,6 +828,12 @@ "row": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "index": { "type": "integer" }, @@ -662,6 +848,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "index", "row", @@ -675,6 +863,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -687,6 +881,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "target" @@ -699,6 +895,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -711,6 +913,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "target" @@ -723,6 +927,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -732,6 +942,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row" ], @@ -743,6 +955,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -760,6 +978,8 @@ "a", "b", "col", + "end_col", + "end_row", "file", "row" ], @@ -771,6 +991,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -791,6 +1017,8 @@ "required": [ "block", "col", + "end_col", + "end_row", "file", "row" ], @@ -802,6 +1030,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -820,6 +1054,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "key", "object", @@ -834,6 +1070,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -852,6 +1094,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "key", "object", @@ -866,6 +1110,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -886,6 +1136,8 @@ "a", "b", "col", + "end_col", + "end_row", "file", "row", "target" @@ -946,6 +1198,12 @@ }, "static": { "$ref": "#/$defs/Static" + }, + "unplanned_rules": { + "type": "array", + "items": { + "$ref": "#/$defs/UnplannedRule" + } } }, "additionalProperties": false @@ -956,6 +1214,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -968,6 +1232,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "target" @@ -980,6 +1246,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -992,6 +1264,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "value" @@ -1004,6 +1278,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -1016,6 +1296,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "source" @@ -1028,6 +1310,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -1057,6 +1345,8 @@ "required": [ "block", "col", + "end_col", + "end_row", "file", "key", "row", @@ -1071,6 +1361,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -1086,6 +1382,8 @@ }, "required": [ "col", + "end_col", + "end_row", "file", "row", "set", @@ -1677,6 +1975,29 @@ ], "additionalProperties": false }, + "UnplannedRule": { + "type": "object", + "properties": { + "location": { + "oneOf": [ + { + "$ref": "#/$defs/Location" + }, + { + "type": "null" + } + ] + }, + "path": { + "type": "string" + } + }, + "required": [ + "location", + "path" + ], + "additionalProperties": false + }, "Val": { "oneOf": [ { @@ -1735,6 +2056,12 @@ "col": { "type": "integer" }, + "end_col": { + "type": "integer" + }, + "end_row": { + "type": "integer" + }, "file": { "type": "integer" }, @@ -1770,6 +2097,8 @@ "required": [ "block", "col", + "end_col", + "end_row", "file", "local", "path", diff --git a/vendor/github.com/open-policy-agent/opa/v1/ir/proto.go b/vendor/github.com/open-policy-agent/opa/v1/ir/proto.go index 0c89dcb105..222e668822 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ir/proto.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ir/proto.go @@ -8,8 +8,6 @@ import ( "fmt" "math" - "google.golang.org/protobuf/proto" - pb "github.com/open-policy-agent/opa/v1/ir/v1pb" ) @@ -58,14 +56,14 @@ func stringConstToProto(s *StringConst) *pb.StringConst { if s == nil { return nil } - return &pb.StringConst{Value: proto.String(s.Value)} + return &pb.StringConst{Value: new(s.Value)} } func builtinFuncToProto(b *BuiltinFunc) *pb.BuiltinFunc { if b == nil { return nil } - return &pb.BuiltinFunc{Name: proto.String(b.Name)} + return &pb.BuiltinFunc{Name: new(b.Name)} } func plansToProto(p *Plans) *pb.Plans { @@ -83,7 +81,7 @@ func planToProto(p *Plan) *pb.Plan { if p == nil { return nil } - out := &pb.Plan{Name: proto.String(p.Name), Blocks: make([]*pb.Block, len(p.Blocks))} + out := &pb.Plan{Name: new(p.Name), Blocks: make([]*pb.Block, len(p.Blocks))} for i, b := range p.Blocks { out.Blocks[i] = blockToProto(b) } @@ -106,9 +104,9 @@ func funcToProto(f *Func) *pb.Func { return nil } out := &pb.Func{ - Name: proto.String(f.Name), + Name: new(f.Name), Params: localsToInt32s(f.Params), - Result: proto.Int32(toInt32(f.Return)), + Result: new(toInt32(f.Return)), Blocks: make([]*pb.Block, len(f.Blocks)), Path: f.Path, } @@ -189,30 +187,32 @@ func stmtToProto(s Stmt) *pb.Stmt { } loc := s.GetLocation() out := &pb.Stmt{ - File: proto.Int32(toInt32(loc.File)), - Col: proto.Int32(toInt32(loc.Col)), - Row: proto.Int32(toInt32(loc.Row)), + File: new(toInt32(loc.File)), + Col: new(toInt32(loc.Col)), + Row: new(toInt32(loc.Row)), + EndCol: new(toInt32(loc.EndCol)), + EndRow: new(toInt32(loc.EndRow)), } switch x := s.(type) { case *ArrayAppendStmt: out.Kind = &pb.Stmt_ArrayAppendStmt{ArrayAppendStmt: &pb.ArrayAppendStmt{ Value: operandToProto(x.Value), - Array: proto.Int32(toInt32(x.Array)), + Array: new(toInt32(x.Array)), }} case *AssignIntStmt: out.Kind = &pb.Stmt_AssignIntStmt{AssignIntStmt: &pb.AssignIntStmt{ - Value: proto.Int64(x.Value), - Target: proto.Int32(toInt32(x.Target)), + Value: new(x.Value), + Target: new(toInt32(x.Target)), }} case *AssignVarOnceStmt: out.Kind = &pb.Stmt_AssignVarOnceStmt{AssignVarOnceStmt: &pb.AssignVarOnceStmt{ Source: operandToProto(x.Source), - Target: proto.Int32(toInt32(x.Target)), + Target: new(toInt32(x.Target)), }} case *AssignVarStmt: out.Kind = &pb.Stmt_AssignVarStmt{AssignVarStmt: &pb.AssignVarStmt{ Source: operandToProto(x.Source), - Target: proto.Int32(toInt32(x.Target)), + Target: new(toInt32(x.Target)), }} case *BlockStmt: body := &pb.BlockStmt{Blocks: make([]*pb.Block, len(x.Blocks))} @@ -221,24 +221,24 @@ func stmtToProto(s Stmt) *pb.Stmt { } out.Kind = &pb.Stmt_BlockStmt{BlockStmt: body} case *BreakStmt: - out.Kind = &pb.Stmt_BreakStmt{BreakStmt: &pb.BreakStmt{Index: proto.Uint32(x.Index)}} + out.Kind = &pb.Stmt_BreakStmt{BreakStmt: &pb.BreakStmt{Index: new(x.Index)}} case *CallDynamicStmt: out.Kind = &pb.Stmt_CallDynamicStmt{CallDynamicStmt: &pb.CallDynamicStmt{ Args: localsToInt32s(x.Args), - Result: proto.Int32(toInt32(x.Result)), + Result: new(toInt32(x.Result)), Path: operandsToProto(x.Path), }} case *CallStmt: out.Kind = &pb.Stmt_CallStmt{CallStmt: &pb.CallStmt{ - Function: proto.String(x.Func), + Function: new(x.Func), Args: operandsToProto(x.Args), - Result: proto.Int32(toInt32(x.Result)), + Result: new(toInt32(x.Result)), }} case *DotStmt: out.Kind = &pb.Stmt_DotStmt{DotStmt: &pb.DotStmt{ Source: operandToProto(x.Source), Key: operandToProto(x.Key), - Target: proto.Int32(toInt32(x.Target)), + Target: new(toInt32(x.Target)), }} case *EqualStmt: out.Kind = &pb.Stmt_EqualStmt{EqualStmt: &pb.EqualStmt{ @@ -248,39 +248,39 @@ func stmtToProto(s Stmt) *pb.Stmt { case *IsArrayStmt: out.Kind = &pb.Stmt_IsArrayStmt{IsArrayStmt: &pb.IsArrayStmt{Source: operandToProto(x.Source)}} case *IsDefinedStmt: - out.Kind = &pb.Stmt_IsDefinedStmt{IsDefinedStmt: &pb.IsDefinedStmt{Source: proto.Int32(toInt32(x.Source))}} + out.Kind = &pb.Stmt_IsDefinedStmt{IsDefinedStmt: &pb.IsDefinedStmt{Source: new(toInt32(x.Source))}} case *IsObjectStmt: out.Kind = &pb.Stmt_IsObjectStmt{IsObjectStmt: &pb.IsObjectStmt{Source: operandToProto(x.Source)}} case *IsSetStmt: out.Kind = &pb.Stmt_IsSetStmt{IsSetStmt: &pb.IsSetStmt{Source: operandToProto(x.Source)}} case *IsUndefinedStmt: - out.Kind = &pb.Stmt_IsUndefinedStmt{IsUndefinedStmt: &pb.IsUndefinedStmt{Source: proto.Int32(toInt32(x.Source))}} + out.Kind = &pb.Stmt_IsUndefinedStmt{IsUndefinedStmt: &pb.IsUndefinedStmt{Source: new(toInt32(x.Source))}} case *LenStmt: out.Kind = &pb.Stmt_LenStmt{LenStmt: &pb.LenStmt{ Source: operandToProto(x.Source), - Target: proto.Int32(toInt32(x.Target)), + Target: new(toInt32(x.Target)), }} case *MakeArrayStmt: out.Kind = &pb.Stmt_MakeArrayStmt{MakeArrayStmt: &pb.MakeArrayStmt{ - Capacity: proto.Int32(x.Capacity), - Target: proto.Int32(toInt32(x.Target)), + Capacity: new(x.Capacity), + Target: new(toInt32(x.Target)), }} case *MakeNullStmt: - out.Kind = &pb.Stmt_MakeNullStmt{MakeNullStmt: &pb.MakeNullStmt{Target: proto.Int32(toInt32(x.Target))}} + out.Kind = &pb.Stmt_MakeNullStmt{MakeNullStmt: &pb.MakeNullStmt{Target: new(toInt32(x.Target))}} case *MakeNumberIntStmt: out.Kind = &pb.Stmt_MakeNumberIntStmt{MakeNumberIntStmt: &pb.MakeNumberIntStmt{ - Value: proto.Int64(x.Value), - Target: proto.Int32(toInt32(x.Target)), + Value: new(x.Value), + Target: new(toInt32(x.Target)), }} case *MakeNumberRefStmt: out.Kind = &pb.Stmt_MakeNumberRefStmt{MakeNumberRefStmt: &pb.MakeNumberRefStmt{ - Index: proto.Int32(toInt32(x.Index)), - Target: proto.Int32(toInt32(x.Target)), + Index: new(toInt32(x.Index)), + Target: new(toInt32(x.Target)), }} case *MakeObjectStmt: - out.Kind = &pb.Stmt_MakeObjectStmt{MakeObjectStmt: &pb.MakeObjectStmt{Target: proto.Int32(toInt32(x.Target))}} + out.Kind = &pb.Stmt_MakeObjectStmt{MakeObjectStmt: &pb.MakeObjectStmt{Target: new(toInt32(x.Target))}} case *MakeSetStmt: - out.Kind = &pb.Stmt_MakeSetStmt{MakeSetStmt: &pb.MakeSetStmt{Target: proto.Int32(toInt32(x.Target))}} + out.Kind = &pb.Stmt_MakeSetStmt{MakeSetStmt: &pb.MakeSetStmt{Target: new(toInt32(x.Target))}} case *NopStmt: out.Kind = &pb.Stmt_NopStmt{NopStmt: &pb.NopStmt{}} case *NotEqualStmt: @@ -294,41 +294,41 @@ func stmtToProto(s Stmt) *pb.Stmt { out.Kind = &pb.Stmt_ObjectInsertOnceStmt{ObjectInsertOnceStmt: &pb.ObjectInsertOnceStmt{ Key: operandToProto(x.Key), Value: operandToProto(x.Value), - Object: proto.Int32(toInt32(x.Object)), + Object: new(toInt32(x.Object)), }} case *ObjectInsertStmt: out.Kind = &pb.Stmt_ObjectInsertStmt{ObjectInsertStmt: &pb.ObjectInsertStmt{ Key: operandToProto(x.Key), Value: operandToProto(x.Value), - Object: proto.Int32(toInt32(x.Object)), + Object: new(toInt32(x.Object)), }} case *ObjectMergeStmt: out.Kind = &pb.Stmt_ObjectMergeStmt{ObjectMergeStmt: &pb.ObjectMergeStmt{ - A: proto.Int32(toInt32(x.A)), - B: proto.Int32(toInt32(x.B)), - Target: proto.Int32(toInt32(x.Target)), + A: new(toInt32(x.A)), + B: new(toInt32(x.B)), + Target: new(toInt32(x.Target)), }} case *ResetLocalStmt: - out.Kind = &pb.Stmt_ResetLocalStmt{ResetLocalStmt: &pb.ResetLocalStmt{Target: proto.Int32(toInt32(x.Target))}} + out.Kind = &pb.Stmt_ResetLocalStmt{ResetLocalStmt: &pb.ResetLocalStmt{Target: new(toInt32(x.Target))}} case *ResultSetAddStmt: - out.Kind = &pb.Stmt_ResultSetAddStmt{ResultSetAddStmt: &pb.ResultSetAddStmt{Value: proto.Int32(toInt32(x.Value))}} + out.Kind = &pb.Stmt_ResultSetAddStmt{ResultSetAddStmt: &pb.ResultSetAddStmt{Value: new(toInt32(x.Value))}} case *ReturnLocalStmt: - out.Kind = &pb.Stmt_ReturnLocalStmt{ReturnLocalStmt: &pb.ReturnLocalStmt{Source: proto.Int32(toInt32(x.Source))}} + out.Kind = &pb.Stmt_ReturnLocalStmt{ReturnLocalStmt: &pb.ReturnLocalStmt{Source: new(toInt32(x.Source))}} case *ScanStmt: out.Kind = &pb.Stmt_ScanStmt{ScanStmt: &pb.ScanStmt{ - Source: proto.Int32(toInt32(x.Source)), - Key: proto.Int32(toInt32(x.Key)), - Value: proto.Int32(toInt32(x.Value)), + Source: new(toInt32(x.Source)), + Key: new(toInt32(x.Key)), + Value: new(toInt32(x.Value)), Block: blockToProto(x.Block), }} case *SetAddStmt: out.Kind = &pb.Stmt_SetAddStmt{SetAddStmt: &pb.SetAddStmt{ Value: operandToProto(x.Value), - Set: proto.Int32(toInt32(x.Set)), + Set: new(toInt32(x.Set)), }} case *WithStmt: out.Kind = &pb.Stmt_WithStmt{WithStmt: &pb.WithStmt{ - Local: proto.Int32(toInt32(x.Local)), + Local: new(toInt32(x.Local)), Path: intsToInt32s(x.Path), Value: operandToProto(x.Value), Block: blockToProto(x.Block), diff --git a/vendor/github.com/open-policy-agent/opa/v1/ir/v1pb/plan.pb.go b/vendor/github.com/open-policy-agent/opa/v1/ir/v1pb/plan.pb.go index a47afbcc0f..fe55e67fc0 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/ir/v1pb/plan.pb.go +++ b/vendor/github.com/open-policy-agent/opa/v1/ir/v1pb/plan.pb.go @@ -668,24 +668,27 @@ func (*Val_Local) isVal_Kind() {} func (*Val_StringIndex) isVal_Kind() {} // Stmt mirrors the `ir.Stmt` interface in v1/ir/ir.go. Every Stmt carries -// the source-location triple (file, col, row) on this envelope; the body -// messages below describe only the kind-specific payload. +// the source-location quintuple (file, col, row, end_col, end_row) on this +// envelope; the body messages below describe only the kind-specific +// payload. // // On the Go side, `ir.Location` is embedded into every concrete Stmt -// implementation, so `encoding/json` flattens File/Col/Row into the -// emitted JSON body. The proto promotes those fields to the envelope -// because that's both more idiomatic protobuf and lets every body +// implementation, so `encoding/json` flattens File/Col/Row/EndCol/EndRow +// into the emitted JSON body. The proto promotes those fields to the +// envelope because that's both more idiomatic protobuf and lets every body // message start its own field numbering at 1. // // Case-number assignments (4–37) are a stability commitment. Field -// numbers 1–3 are reserved for the location triple. New cases must be -// added with the next unused number; existing numbers must never be -// repurposed. +// numbers 1–3 and 38–39 are reserved for the location fields. New cases +// must be added with the next unused number; existing numbers must never +// be repurposed. type Stmt struct { - state protoimpl.MessageState `protogen:"open.v1"` - File *int32 `protobuf:"varint,1,opt,name=file" json:"file,omitempty"` - Col *int32 `protobuf:"varint,2,opt,name=col" json:"col,omitempty"` - Row *int32 `protobuf:"varint,3,opt,name=row" json:"row,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + File *int32 `protobuf:"varint,1,opt,name=file" json:"file,omitempty"` + Col *int32 `protobuf:"varint,2,opt,name=col" json:"col,omitempty"` + Row *int32 `protobuf:"varint,3,opt,name=row" json:"row,omitempty"` + EndCol *int32 `protobuf:"varint,38,opt,name=end_col,json=endCol" json:"end_col,omitempty"` + EndRow *int32 `protobuf:"varint,39,opt,name=end_row,json=endRow" json:"end_row,omitempty"` // Types that are valid to be assigned to Kind: // // *Stmt_ArrayAppendStmt @@ -778,6 +781,20 @@ func (x *Stmt) GetRow() int32 { return 0 } +func (x *Stmt) GetEndCol() int32 { + if x != nil && x.EndCol != nil { + return *x.EndCol + } + return 0 +} + +func (x *Stmt) GetEndRow() int32 { + if x != nil && x.EndRow != nil { + return *x.EndRow + } + return 0 +} + func (x *Stmt) GetKind() isStmt_Kind { if x != nil { return x.Kind @@ -3100,11 +3117,13 @@ const file_v1_ir_plan_proto_rawDesc = "" + "\x04bool\x18\x01 \x01(\bH\x00R\x04bool\x12\x16\n" + "\x05local\x18\x02 \x01(\x05H\x00R\x05local\x12#\n" + "\fstring_index\x18\x03 \x01(\x05H\x00R\vstringIndexB\x06\n" + - "\x04kind\"\xf5\x11\n" + + "\x04kind\"\xa7\x12\n" + "\x04Stmt\x12\x12\n" + "\x04file\x18\x01 \x01(\x05R\x04file\x12\x10\n" + "\x03col\x18\x02 \x01(\x05R\x03col\x12\x10\n" + - "\x03row\x18\x03 \x01(\x05R\x03row\x12H\n" + + "\x03row\x18\x03 \x01(\x05R\x03row\x12\x17\n" + + "\aend_col\x18& \x01(\x05R\x06endCol\x12\x17\n" + + "\aend_row\x18' \x01(\x05R\x06endRow\x12H\n" + "\x11array_append_stmt\x18\x04 \x01(\v2\x1a.opa.ir.v1.ArrayAppendStmtH\x00R\x0farrayAppendStmt\x12B\n" + "\x0fassign_int_stmt\x18\x05 \x01(\v2\x18.opa.ir.v1.AssignIntStmtH\x00R\rassignIntStmt\x12O\n" + "\x14assign_var_once_stmt\x18\x06 \x01(\v2\x1c.opa.ir.v1.AssignVarOnceStmtH\x00R\x11assignVarOnceStmt\x12B\n" + diff --git a/vendor/github.com/open-policy-agent/opa/v1/loader/errors.go b/vendor/github.com/open-policy-agent/opa/v1/loader/errors.go index 55b8e7dc44..3e3fb732d9 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/loader/errors.go +++ b/vendor/github.com/open-policy-agent/opa/v1/loader/errors.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/open-policy-agent/opa/v1/ast" + "github.com/open-policy-agent/opa/v1/util" ) // Errors is a wrapper for multiple loader errors. @@ -21,10 +22,7 @@ func (e Errors) Error() string { if len(e) == 1 { return "1 error occurred during loading: " + e[0].Error() } - buf := make([]string, len(e)) - for i := range buf { - buf[i] = e[i].Error() - } + buf := util.Map(e, error.Error) return fmt.Sprintf("%v errors occurred during loading:\n", len(e)) + strings.Join(buf, "\n") } diff --git a/vendor/github.com/open-policy-agent/opa/v1/loader/loader.go b/vendor/github.com/open-policy-agent/opa/v1/loader/loader.go index d97e3e5409..81eb9a7eed 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/loader/loader.go +++ b/vendor/github.com/open-policy-agent/opa/v1/loader/loader.go @@ -12,12 +12,12 @@ import ( "io/fs" "os" "path/filepath" + "runtime" "strings" - "sigs.k8s.io/yaml" - fileurl "github.com/open-policy-agent/opa/internal/file/url" "github.com/open-policy-agent/opa/internal/merge" + "github.com/open-policy-agent/opa/internal/yaml" "github.com/open-policy-agent/opa/v1/ast" astJSON "github.com/open-policy-agent/opa/v1/ast/json" "github.com/open-policy-agent/opa/v1/bundle" @@ -29,6 +29,9 @@ import ( "github.com/open-policy-agent/opa/v1/util" ) +// goos is overridden in tests to exercise Windows path handling on other platforms. +var goos = runtime.GOOS + // Result represents the result of successfully loading zero or more files. type Result struct { Documents map[string]any @@ -592,6 +595,11 @@ func SplitPrefix(path string) ([]string, string) { if strings.Index(path, "://") == strings.Index(path, ":") { return nil, path } + // On Windows, a leading colon can belong to the path itself, separating the + // volume name from the rest of the path, rather than to a data prefix. + if hasWindowsVolumeName(path) { + return nil, path + } parts := strings.SplitN(path, ":", 2) if len(parts) == 2 && len(parts[0]) > 0 { return strings.Split(parts[0], "."), parts[1] @@ -599,6 +607,25 @@ func SplitPrefix(path string) ([]string, string) { return nil, path } +// hasWindowsVolumeName returns true on Windows if path begins with a volume +// name, i.e. a drive letter followed by a colon and a separator (c:/foo) or a +// UNC/device prefix (\\?\c:\foo), but not a drive-relative path (c:foo), which +// is read as a single-character data prefix instead. +func hasWindowsVolumeName(path string) bool { + if goos != "windows" || len(path) < 3 { + return false + } + // UNC and device paths, e.g. \\server\share or \\?\c:\foo. These aren't all + // loadable -- UNC reads are rejected outright -- but they're never prefixes, + // and splitting them would hide the path from that check. + if isSlash(path[0]) && isSlash(path[1]) { + return true + } + // Drive-rooted paths, e.g. c:/foo. + c := path[0] + return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') && path[1] == ':' && isSlash(path[2]) +} + func (l *Result) merge(path string, result any) error { switch result := result.(type) { case bundle.Bundle: diff --git a/vendor/github.com/open-policy-agent/opa/v1/logging/logging.go b/vendor/github.com/open-policy-agent/opa/v1/logging/logging.go index 135785994f..457ee027a7 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/logging/logging.go +++ b/vendor/github.com/open-policy-agent/opa/v1/logging/logging.go @@ -336,15 +336,15 @@ func (h *SlogHandler) Handle(ctx context.Context, record slog.Record) error { msg := record.Message switch record.Level { case slog.LevelDebug: - logger.Debug(msg) + logger.Debug("%s", msg) case slog.LevelInfo: - logger.Info(msg) + logger.Info("%s", msg) case slog.LevelWarn: - logger.Warn(msg) + logger.Warn("%s", msg) case slog.LevelError: - logger.Error(msg) + logger.Error("%s", msg) default: - logger.Info(msg) + logger.Info("%s", msg) } return nil } @@ -359,7 +359,7 @@ func (h *SlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { } } -func (h *SlogHandler) WithGroup(name string) slog.Handler { +func (h *SlogHandler) WithGroup(string) slog.Handler { return h } diff --git a/vendor/github.com/open-policy-agent/opa/v1/metrics/metrics.go b/vendor/github.com/open-policy-agent/opa/v1/metrics/metrics.go index 481f27337e..cc64665425 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/metrics/metrics.go +++ b/vendor/github.com/open-policy-agent/opa/v1/metrics/metrics.go @@ -311,19 +311,19 @@ type Counter interface { } type counter struct { - c uint64 + c atomic.Uint64 } func (c *counter) Incr() { - atomic.AddUint64(&c.c, 1) + c.c.Add(1) } func (c *counter) Add(n uint64) { - atomic.AddUint64(&c.c, n) + c.c.Add(n) } func (c *counter) Value() any { - return atomic.LoadUint64(&c.c) + return c.c.Load() } func Statistics(num ...int64) any { @@ -346,12 +346,12 @@ var ( noOpCounterInstance = &noOpCounter{} ) -func (*noOpMetrics) Info() Info { return Info{Name: ""} } -func (*noOpMetrics) Timer(name string) Timer { return noOpTimerInstance } -func (*noOpMetrics) Histogram(name string) Histogram { return noOpHistogramInstance } -func (*noOpMetrics) Counter(name string) Counter { return noOpCounterInstance } -func (*noOpMetrics) All() map[string]any { return nil } -func (*noOpMetrics) Clear() {} +func (*noOpMetrics) Info() Info { return Info{Name: ""} } +func (*noOpMetrics) Timer(string) Timer { return noOpTimerInstance } +func (*noOpMetrics) Histogram(string) Histogram { return noOpHistogramInstance } +func (*noOpMetrics) Counter(string) Counter { return noOpCounterInstance } +func (*noOpMetrics) All() map[string]any { return nil } +func (*noOpMetrics) Clear() {} func (*noOpMetrics) MarshalJSON() ([]byte, error) { return []byte(`{"name": ""}`), nil } @@ -361,10 +361,10 @@ func (*noOpTimer) Stop() int64 { return 0 } func (*noOpTimer) Value() any { return 0 } func (*noOpTimer) Int64() int64 { return 0 } -func (*noOpHistogram) Update(v int64) {} -func (*noOpHistogram) Value() any { return nil } +func (*noOpHistogram) Update(int64) {} +func (*noOpHistogram) Value() any { return nil } func (*noOpCounter) Incr() {} -func (*noOpCounter) Add(_ uint64) {} +func (*noOpCounter) Add(uint64) {} func (*noOpCounter) Value() any { return 0 } func (*noOpCounter) Int64() int64 { return 0 } diff --git a/vendor/github.com/open-policy-agent/opa/v1/rego/rego.go b/vendor/github.com/open-policy-agent/opa/v1/rego/rego.go index 097b80e3d8..3a2ff89ab4 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/rego/rego.go +++ b/vendor/github.com/open-policy-agent/opa/v1/rego/rego.go @@ -7,6 +7,7 @@ package rego import ( "bytes" + "cmp" "context" "errors" "fmt" @@ -122,6 +123,7 @@ type EvalContext struct { printHook print.Hook capabilities *ast.Capabilities strictBuiltinErrors bool + builtinErrorList *[]topdown.Error virtualCache topdown.VirtualCache baseCache topdown.BaseCache tracing tracing.Options @@ -388,6 +390,15 @@ func EvalPrintHook(ph print.Hook) EvalOption { } } +// EvalBuiltinErrorList overrides, for this Eval call only, the list +// built-in errors are appended to — letting a caller that evaluates the +// same PreparedEvalQuery multiple times keep each call's errors separate. +func EvalBuiltinErrorList(list *[]topdown.Error) EvalOption { + return func(e *EvalContext) { + e.builtinErrorList = list + } +} + // EvalVirtualCache sets the topdown.VirtualCache to use for evaluation. // This is optional, and if not set, the default cache is used. func EvalVirtualCache(vc topdown.VirtualCache) EvalOption { @@ -447,8 +458,12 @@ func EvalEvaluatedRuleTracker(t *topdown.EvaluatedRuleTracker) EvalOption { } func (pq preparedQuery) Modules() map[string]*ast.Module { - mods := make(map[string]*ast.Module) + size := len(pq.r.parsedModules) + for _, b := range pq.r.bundles { + size += len(b.Modules) + } + mods := make(map[string]*ast.Module, size) maps.Copy(mods, pq.r.parsedModules) for _, b := range pq.r.bundles { @@ -465,6 +480,11 @@ func (pq preparedQuery) Modules() map[string]*ast.Module { // once the evaluation is complete to close any transactions that might have // been opened. func (pq preparedQuery) newEvalContext(ctx context.Context, options []EvalOption) (*EvalContext, func(context.Context), error) { + disableInlining, err := parseStringsToRefs(pq.r.disableInlining) + if err != nil { + return nil, func(context.Context) {}, err + } + ectx := &EvalContext{ hasInput: false, rawInput: nil, @@ -477,6 +497,7 @@ func (pq preparedQuery) newEvalContext(ctx context.Context, options []EvalOption queryTracers: nil, unknowns: pq.r.unknowns, parsedUnknowns: pq.r.parsedUnknowns, + disableInlining: disableInlining, nondeterministicBuiltins: pq.r.nondeterministicBuiltins, compiledQuery: compiledQuery{}, indexing: true, @@ -485,6 +506,7 @@ func (pq preparedQuery) newEvalContext(ctx context.Context, options []EvalOption printHook: pq.r.printHook, capabilities: pq.r.capabilities, strictBuiltinErrors: pq.r.strictBuiltinErrors, + builtinErrorList: pq.r.builtinErrorList, tracing: pq.r.distributedTracingOpts, } @@ -492,9 +514,7 @@ func (pq preparedQuery) newEvalContext(ctx context.Context, options []EvalOption o(ectx) } - if ectx.metrics == nil { - ectx.metrics = metrics.New() - } + ectx.metrics = util.Or(ectx.metrics, metrics.New) if ectx.instrument { ectx.instrumentation = topdown.NewInstrumentation(ectx.metrics) @@ -503,12 +523,6 @@ func (pq preparedQuery) newEvalContext(ctx context.Context, options []EvalOption // Default to an empty "finish" function finishFunc := func(context.Context) {} - var err error - ectx.disableInlining, err = parseStringsToRefs(pq.r.disableInlining) - if err != nil { - return nil, finishFunc, err - } - if ectx.txn == nil { ectx.txn, err = pq.r.store.NewTransaction(ctx) if err != nil { @@ -527,11 +541,9 @@ func (pq preparedQuery) newEvalContext(ctx context.Context, options []EvalOption } if ectx.parsedInput == nil { - if ectx.rawInput == nil { - // Fall back to the original Rego objects input if none was specified - // Note that it could still be nil - ectx.rawInput = pq.r.rawInput - } + // Fall back to the original Rego objects input if none was specified + // Note that it could still be nil + ectx.rawInput = util.NilOr(ectx.rawInput, pq.r.rawInput) if pq.r.targetPlugin(pq.r.target) == nil && // no plugin claims this target pq.r.target != targetWasm { @@ -597,13 +609,16 @@ func (errs Errors) Error() string { return "no error" } if len(errs) == 1 { - return fmt.Sprintf("1 error occurred: %v", errs[0].Error()) + return "1 error occurred: " + errs[0].Error() } - buf := []string{fmt.Sprintf("%v errors occurred", len(errs))} + bb := new(bytes.Buffer) + util.WriteInt(bb, len(errs)) + bb.WriteString(" errors occurred") for _, err := range errs { - buf = append(buf, err.Error()) + bb.WriteByte('\n') + bb.WriteString(err.Error()) } - return strings.Join(buf, "\n") + return bb.String() } var errPartialEvaluationNotEffective = errors.New("partial evaluation not effective") @@ -690,6 +705,7 @@ type Rego struct { interQueryBuiltinValueCache cache.InterQueryValueCache ndBuiltinCache builtins.NDBCache strictBuiltinErrors bool + stackTraces bool builtinErrorList *[]topdown.Error resolvers []refResolver externalSources []ast.ExternalRuleSource @@ -753,7 +769,7 @@ func RegisterBuiltin1(decl *Function, impl Builtin1) { }) topdown.RegisterBuiltinFunc(decl.Name, func(bctx BuiltinContext, terms []*ast.Term, iter func(*ast.Term) error) error { result, err := memoize(decl, bctx, terms, func() (*ast.Term, error) { return impl(bctx, terms[0]) }) - return finishFunction(decl.Name, bctx, result, err, iter) + return finishFunction(decl.Name, bctx.Location, result, err, iter) }) } @@ -767,7 +783,7 @@ func RegisterBuiltin2(decl *Function, impl Builtin2) { }) topdown.RegisterBuiltinFunc(decl.Name, func(bctx BuiltinContext, terms []*ast.Term, iter func(*ast.Term) error) error { result, err := memoize(decl, bctx, terms, func() (*ast.Term, error) { return impl(bctx, terms[0], terms[1]) }) - return finishFunction(decl.Name, bctx, result, err, iter) + return finishFunction(decl.Name, bctx.Location, result, err, iter) }) } @@ -781,7 +797,7 @@ func RegisterBuiltin3(decl *Function, impl Builtin3) { }) topdown.RegisterBuiltinFunc(decl.Name, func(bctx BuiltinContext, terms []*ast.Term, iter func(*ast.Term) error) error { result, err := memoize(decl, bctx, terms, func() (*ast.Term, error) { return impl(bctx, terms[0], terms[1], terms[2]) }) - return finishFunction(decl.Name, bctx, result, err, iter) + return finishFunction(decl.Name, bctx.Location, result, err, iter) }) } @@ -795,7 +811,7 @@ func RegisterBuiltin4(decl *Function, impl Builtin4) { }) topdown.RegisterBuiltinFunc(decl.Name, func(bctx BuiltinContext, terms []*ast.Term, iter func(*ast.Term) error) error { result, err := memoize(decl, bctx, terms, func() (*ast.Term, error) { return impl(bctx, terms[0], terms[1], terms[2], terms[3]) }) - return finishFunction(decl.Name, bctx, result, err, iter) + return finishFunction(decl.Name, bctx.Location, result, err, iter) }) } @@ -809,7 +825,7 @@ func RegisterBuiltinDyn(decl *Function, impl BuiltinDyn) { }) topdown.RegisterBuiltinFunc(decl.Name, func(bctx BuiltinContext, terms []*ast.Term, iter func(*ast.Term) error) error { result, err := memoize(decl, bctx, terms, func() (*ast.Term, error) { return impl(bctx, terms) }) - return finishFunction(decl.Name, bctx, result, err, iter) + return finishFunction(decl.Name, bctx.Location, result, err, iter) }) } @@ -817,7 +833,7 @@ func RegisterBuiltinDyn(decl *Function, impl BuiltinDyn) { func Function1(decl *Function, f Builtin1) func(*Rego) { return newFunction(decl, func(bctx BuiltinContext, terms []*ast.Term, iter func(*ast.Term) error) error { result, err := memoize(decl, bctx, terms, func() (*ast.Term, error) { return f(bctx, terms[0]) }) - return finishFunction(decl.Name, bctx, result, err, iter) + return finishFunction(decl.Name, bctx.Location, result, err, iter) }) } @@ -825,7 +841,7 @@ func Function1(decl *Function, f Builtin1) func(*Rego) { func Function2(decl *Function, f Builtin2) func(*Rego) { return newFunction(decl, func(bctx BuiltinContext, terms []*ast.Term, iter func(*ast.Term) error) error { result, err := memoize(decl, bctx, terms, func() (*ast.Term, error) { return f(bctx, terms[0], terms[1]) }) - return finishFunction(decl.Name, bctx, result, err, iter) + return finishFunction(decl.Name, bctx.Location, result, err, iter) }) } @@ -833,7 +849,7 @@ func Function2(decl *Function, f Builtin2) func(*Rego) { func Function3(decl *Function, f Builtin3) func(*Rego) { return newFunction(decl, func(bctx BuiltinContext, terms []*ast.Term, iter func(*ast.Term) error) error { result, err := memoize(decl, bctx, terms, func() (*ast.Term, error) { return f(bctx, terms[0], terms[1], terms[2]) }) - return finishFunction(decl.Name, bctx, result, err, iter) + return finishFunction(decl.Name, bctx.Location, result, err, iter) }) } @@ -841,7 +857,7 @@ func Function3(decl *Function, f Builtin3) func(*Rego) { func Function4(decl *Function, f Builtin4) func(*Rego) { return newFunction(decl, func(bctx BuiltinContext, terms []*ast.Term, iter func(*ast.Term) error) error { result, err := memoize(decl, bctx, terms, func() (*ast.Term, error) { return f(bctx, terms[0], terms[1], terms[2], terms[3]) }) - return finishFunction(decl.Name, bctx, result, err, iter) + return finishFunction(decl.Name, bctx.Location, result, err, iter) }) } @@ -849,7 +865,7 @@ func Function4(decl *Function, f Builtin4) func(*Rego) { func FunctionDyn(decl *Function, f BuiltinDyn) func(*Rego) { return newFunction(decl, func(bctx BuiltinContext, terms []*ast.Term, iter func(*ast.Term) error) error { result, err := memoize(decl, bctx, terms, func() (*ast.Term, error) { return f(bctx, terms) }) - return finishFunction(decl.Name, bctx, result, err, iter) + return finishFunction(decl.Name, bctx.Location, result, err, iter) }) } @@ -1294,6 +1310,16 @@ func StrictBuiltinErrors(yes bool) func(r *Rego) { } } +// StackTraces tells the evaluator to record the stack of queries being evaluated +// when an error occurred on the returned *topdown.Error. The stack is exposed as +// topdown.Error.StackTrace and left out of the error message, so callers render +// it themselves. Off by default; see [topdown.Query.WithStackTraces] for why. +func StackTraces(yes bool) func(r *Rego) { + return func(r *Rego) { + r.stackTraces = yes + } +} + // BuiltinErrorList supplies an error slice to store built-in function errors. func BuiltinErrorList(list *[]topdown.Error) func(r *Rego) { return func(r *Rego) { @@ -1461,9 +1487,7 @@ func New(options ...func(r *Rego)) *Rego { r.ownStore = false } - if r.metrics == nil { - r.metrics = metrics.New() - } + r.metrics = util.Or(r.metrics, metrics.New) if r.instrument { r.instrumentation = topdown.NewInstrumentation(r.metrics) @@ -1596,8 +1620,10 @@ func (r *Rego) Partial(ctx context.Context) (*PartialQueries, error) { EvalTransaction(r.txn), EvalMetrics(r.metrics), EvalInstrument(r.instrument), + EvalTime(r.time), EvalInterQueryBuiltinCache(r.interQueryBuiltinCache), EvalInterQueryBuiltinValueCache(r.interQueryBuiltinValueCache), + EvalSeed(r.seed), } if r.ndBuiltinCache != nil { @@ -1639,7 +1665,6 @@ func CompilePartial(yes bool) CompileOption { // Compile returns a compiled policy query. func (r *Rego) Compile(ctx context.Context, opts ...CompileOption) (*CompileResult, error) { var cfg CompileContext - for _, opt := range opts { opt(&cfg) } @@ -1648,7 +1673,6 @@ func (r *Rego) Compile(ctx context.Context, opts ...CompileOption) (*CompileResu modules := make([]*ast.Module, 0, len(r.compiler.Modules)) if cfg.partial { - pq, err := r.Partial(ctx) if err != nil { return nil, err @@ -1856,14 +1880,12 @@ func (r *Rego) PrepareForEval(ctx context.Context, opts ...PrepareOption) (Prepa return PreparedEvalQuery{}, err } - // nolint: staticcheck // SA4006 false positive cr, err := r.compileWasm(modules, queries, evalQueryType) if err != nil { _ = txnClose(ctx, err) // Ignore error return PreparedEvalQuery{}, err } - // nolint: staticcheck // SA4006 false positive data, err := r.store.Read(ctx, r.txn, storage.RootPath) if err != nil { _ = txnClose(ctx, err) // Ignore error @@ -1942,33 +1964,27 @@ func (r *Rego) PrepareForPartial(ctx context.Context, opts ...PrepareOption) (Pr return PreparedPartialQuery{preparedQuery{r, pCfg}}, err } -func (r *Rego) prepare(ctx context.Context, qType queryType, extras []extraStage) error { - var err error - +func (r *Rego) prepare(ctx context.Context, qType queryType, extras []extraStage) (err error) { r.parsedInput, err = r.parseInput() if err != nil { return err } - err = r.loadFiles(ctx, r.txn, r.metrics) - if err != nil { + if err := r.loadFiles(ctx, r.txn, r.metrics); err != nil { return err } - err = r.loadBundles(ctx, r.txn, r.metrics) - if err != nil { + if err := r.loadBundles(ctx, r.txn, r.metrics); err != nil { return err } - err = r.parseModules(ctx, r.txn, r.metrics) - if err != nil { + if err := r.parseModules(ctx, r.txn, r.metrics); err != nil { return err } // Compile the modules *before* the query, else functions // defined in the module won't be found... - err = r.compileModules(ctx, r.txn, r.metrics) - if err != nil { + if err := r.compileModules(ctx, r.txn, r.metrics); err != nil { return err } @@ -1977,25 +1993,19 @@ func (r *Rego) prepare(ctx context.Context, qType queryType, extras []extraStage return err } - queryImports := []*ast.Import{} + var queryImports []*ast.Import for _, imp := range imports { path := imp.Path.Value.(ast.Ref) - if path.HasPrefix([]*ast.Term{ast.FutureRootDocument}) || path.HasPrefix([]*ast.Term{ast.RegoRootDocument}) { + if path.HasPrefix(ast.FutureKeywordsRef[:1]) || path.HasPrefix(ast.RegoV1CompatibleRef[:1]) { queryImports = append(queryImports, imp) } } - r.parsedQuery, err = r.parseQuery(queryImports, r.metrics) - if err != nil { + if r.parsedQuery, err = r.parseQuery(queryImports, r.metrics); err != nil { return err } - err = r.compileAndCacheQuery(qType, r.parsedQuery, imports, r.metrics, extras) - if err != nil { - return err - } - - return nil + return r.compileAndCacheQuery(qType, r.parsedQuery, imports, r.metrics, extras) } func (r *Rego) parseModules(ctx context.Context, txn storage.Transaction, m metrics.Metrics) error { @@ -2141,7 +2151,7 @@ func (*Rego) parseRawInput(rawInput *any, m metrics.Metrics) (ast.Value, error) // roundtrip through json: this turns slices (e.g. []string, []bool) into // []any, the only array type ast.InterfaceToValue can work with - if err := util.RoundTrip(rawPtr); err != nil { + if err := util.RoundTripFast(rawPtr); err != nil { return nil, err } @@ -2161,24 +2171,21 @@ func (r *Rego) parseQuery(queryImports []*ast.Import, m metrics.Metrics) (ast.Bo return nil, err } popts.RegoVersion = r.regoVersion - popts, err = parserOptionsFromRegoVersionImport(queryImports, popts) - if err != nil { - return nil, err - } + popts = parserOptionsFromRegoVersionImport(queryImports, popts) popts.SkipRules = true popts.Capabilities = r.capabilities return ast.ParseBodyWithOpts(r.query, popts) } -func parserOptionsFromRegoVersionImport(imports []*ast.Import, popts ast.ParserOptions) (ast.ParserOptions, error) { +func parserOptionsFromRegoVersionImport(imports []*ast.Import, popts ast.ParserOptions) ast.ParserOptions { for _, imp := range imports { - if ast.RegoV1CompatibleRef.Compare(imp.Path.Value) == 0 { + if ast.RegoV1CompatibleRef.Equal(imp.Path.Value) { popts.RegoVersion = ast.RegoV1 - return popts, nil + return popts } } - return popts, nil + return popts } func (r *Rego) compileModules(ctx context.Context, txn storage.Transaction, m metrics.Metrics) error { @@ -2196,7 +2203,6 @@ func (r *Rego) compileModules(ctx context.Context, txn storage.Transaction, m me // Only compile again if there are new modules. if len(r.bundles) > 0 || len(r.parsedModules) > 0 { - // The bundle.Activate call will activate any bundles passed in // (ie compile + handle data store changes), and include any of // the additional modules passed in. If no bundles are provided @@ -2204,18 +2210,20 @@ func (r *Rego) compileModules(ctx context.Context, txn storage.Transaction, m me // Use this as the single-point of compiling everything only a // single time. opts := &bundle.ActivateOpts{ - Ctx: ctx, - Store: r.store, - Txn: txn, - Compiler: r.compilerForTxn(ctx, r.store, txn), - Metrics: m, - Bundles: r.bundles, - ExtraModules: r.parsedModules, - ParserOptions: ast.ParserOptions{RegoVersion: r.regoVersion}, + Ctx: ctx, + Store: r.store, + Txn: txn, + Compiler: r.compilerForTxn(ctx, r.store, txn), + Metrics: m, + Bundles: r.bundles, + ExtraModules: r.parsedModules, + ParserOptions: ast.ParserOptions{ + RegoVersion: r.regoVersion, + Capabilities: r.capabilities, + }, } - err := bundle.Activate(opts) - if err != nil { - return err + if err := bundle.Activate(opts); err != nil { + return fmt.Errorf("bundle activation failed: %w", err) } } @@ -2261,11 +2269,13 @@ func (r *Rego) prepareImports() ([]*ast.Import, error) { imports := r.parsedImports if len(r.imports) > 0 { - s := make([]string, len(r.imports)) + var sb strings.Builder for i := range r.imports { - s[i] = fmt.Sprintf("import %v", r.imports[i]) + sb.WriteString("import ") + sb.WriteString(r.imports[i]) + sb.WriteByte('\n') } - parsed, err := ast.ParseImports(strings.Join(s, "\n")) + parsed, err := ast.ParseImports(sb.String()) if err != nil { return nil, err } @@ -2337,20 +2347,16 @@ func (r *Rego) eval(ctx context.Context, ectx *EvalContext) (ResultSet, error) { WithInterQueryBuiltinCache(ectx.interQueryBuiltinCache). WithInterQueryBuiltinValueCache(ectx.interQueryBuiltinValueCache). WithStrictBuiltinErrors(r.strictBuiltinErrors). - WithBuiltinErrorList(r.builtinErrorList). + WithStackTraces(r.stackTraces). + WithBuiltinErrorList(ectx.builtinErrorList). WithSeed(ectx.seed). WithPrintHook(ectx.printHook). WithDistributedTracingOpts(r.distributedTracingOpts). WithVirtualCache(ectx.virtualCache). WithBaseCache(ectx.baseCache). WithRequestMetadata(ectx.requestMetadata). - WithResponseMetadata(ectx.responseMetadata) - - if ectx.evaluated != nil { - q = q.WithEvaluatedRuleTracker(ectx.evaluated) - } else { - q = q.WithEvaluatedRuleTracker(r.evaluated) - } + WithResponseMetadata(ectx.responseMetadata). + WithEvaluatedRuleTracker(cmp.Or(ectx.evaluated, r.evaluated)) if !ectx.time.IsZero() { q = q.WithTime(ectx.time) @@ -2648,10 +2654,12 @@ func (r *Rego) partial(ctx context.Context, ectx *EvalContext) (*PartialQueries, WithInterQueryBuiltinCache(ectx.interQueryBuiltinCache). WithInterQueryBuiltinValueCache(ectx.interQueryBuiltinValueCache). WithStrictBuiltinErrors(ectx.strictBuiltinErrors). + WithStackTraces(r.stackTraces). WithSeed(ectx.seed). WithPrintHook(ectx.printHook). WithRequestMetadata(ectx.requestMetadata). - WithResponseMetadata(ectx.responseMetadata) + WithResponseMetadata(ectx.responseMetadata). + WithEvaluatedRuleTracker(cmp.Or(ectx.evaluated, r.evaluated)) if !ectx.time.IsZero() { q = q.WithTime(ectx.time) @@ -2818,12 +2826,11 @@ func (*Rego) rewriteQueryForPartialEval(_ ast.QueryCompiler, query ast.Body) (as // where rewriting them can substantially simplify the result, and it is unlikely // that the caller would need expression values. func (*Rego) rewriteEqualsForPartialQueryCompile(_ ast.QueryCompiler, query ast.Body) (ast.Body, error) { - doubleEq := ast.Equal.Ref() unifyOp := ast.Equality.Ref() ast.WalkExprs(query, func(x *ast.Expr) bool { if x.IsCall() { operator := x.Operator() - if operator.Equal(doubleEq) && len(x.Operands()) == 2 { + if operator.Equal(ast.Interned.Refs.Equal) && len(x.Operands()) == 2 { x.SetOperator(ast.NewTerm(unifyOp)) } } @@ -2843,11 +2850,11 @@ func (r *Rego) generateTermVar() *ast.Term { return ast.VarTerm(fmt.Sprintf("%sterm%v", prefix, r.termVarID)) } -func (r Rego) hasQuery() bool { +func (r *Rego) hasQuery() bool { return len(r.query) != 0 || len(r.parsedQuery) != 0 } -func (r Rego) hasWasmModule() bool { +func (r *Rego) hasWasmModule() bool { for _, b := range r.bundles { if len(b.WasmModules) > 0 { return true @@ -2981,11 +2988,9 @@ func iteration(x any) bool { } case ast.Ref: if !stopped { - if bi := ast.BuiltinMap[x.String()]; bi != nil { - if bi.Relation { - stopped = true - return stopped - } + if bi := ast.BuiltinMap[x.String()]; bi != nil && bi.Relation { + stopped = true + return stopped } for i := 1; i < len(x); i++ { if _, ok := x[i].Value.(ast.Var); ok { @@ -3008,27 +3013,16 @@ func parseStringsToRefs(s []string) ([]ast.Ref, error) { if len(s) == 0 { return nil, nil } - - refs := make([]ast.Ref, len(s)) - for i := range refs { - var err error - refs[i], err = ast.ParseRef(s[i]) - if err != nil { - return nil, err - } - } - - return refs, nil + return util.TryMap(s, ast.ParseRef) } // helper function to finish a built-in function call. If an error occurred, // wrap the error and return it. Otherwise, invoke the iterator if the result // was defined. -func finishFunction(name string, bctx topdown.BuiltinContext, result *ast.Term, err error, iter func(*ast.Term) error) error { +func finishFunction(name string, loc *ast.Location, result *ast.Term, err error, iter func(*ast.Term) error) error { if err != nil { - var e *HaltError sb := strings.Builder{} - if errors.As(err, &e) { + if e, ok := errors.AsType[*HaltError](err); ok { sb.Grow(len(name) + len(e.Error()) + 2) sb.WriteString(name) sb.WriteString(": ") @@ -3036,7 +3030,7 @@ func finishFunction(name string, bctx topdown.BuiltinContext, result *ast.Term, tdErr := &topdown.Error{ Code: topdown.BuiltinErr, Message: sb.String(), - Location: bctx.Location, + Location: loc, } return topdown.Halt{Err: tdErr.Wrap(e)} } @@ -3047,7 +3041,7 @@ func finishFunction(name string, bctx topdown.BuiltinContext, result *ast.Term, tdErr := &topdown.Error{ Code: topdown.BuiltinErr, Message: sb.String(), - Location: bctx.Location, + Location: loc, } return tdErr.Wrap(err) } @@ -3073,11 +3067,10 @@ func newFunction(decl *Function, f topdown.BuiltinFunc) func(*Rego) { } func generateJSON(term *ast.Term, ectx *EvalContext) (any, error) { - return ast.JSONWithOpt(term.Value, - ast.JSONOpt{ - SortSets: ectx.sortSets, - CopyMaps: ectx.copyMaps, - }) + return ast.JSONWithOpt(term.Value, ast.JSONOpt{ + SortSets: ectx.sortSets, + CopyMaps: ectx.copyMaps, + }) } func (r *Rego) planQuery(queries []ast.Body, evalQueryType queryType) (*ir.Policy, error) { diff --git a/vendor/github.com/open-policy-agent/opa/v1/storage/errors.go b/vendor/github.com/open-policy-agent/opa/v1/storage/errors.go index a3d1c00737..0781eec4ec 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/storage/errors.go +++ b/vendor/github.com/open-policy-agent/opa/v1/storage/errors.go @@ -4,10 +4,6 @@ package storage -import ( - "fmt" -) - const ( // InternalErr indicates an unknown, internal error has occurred. InternalErr = "storage_internal_error" @@ -49,44 +45,29 @@ type Error struct { func (err *Error) Error() string { if err.Message != "" { - return fmt.Sprintf("%v: %v", err.Code, err.Message) + return err.Code + ": " + err.Message } return err.Code } // IsNotFound returns true if this error is a NotFoundErr. func IsNotFound(err error) bool { - if err, ok := err.(*Error); ok { - return err.Code == NotFoundErr - } - return false + return isError(err, NotFoundErr) } // IsWriteConflictError returns true if this error a WriteConflictErr. func IsWriteConflictError(err error) bool { - switch err := err.(type) { - case *Error: - return err.Code == WriteConflictErr - } - return false + return isError(err, WriteConflictErr) } // IsInvalidPatch returns true if this error is a InvalidPatchErr. func IsInvalidPatch(err error) bool { - switch err := err.(type) { - case *Error: - return err.Code == InvalidPatchErr - } - return false + return isError(err, InvalidPatchErr) } // IsInvalidTransaction returns true if this error is a InvalidTransactionErr. func IsInvalidTransaction(err error) bool { - switch err := err.(type) { - case *Error: - return err.Code == InvalidTransactionErr - } - return false + return isError(err, InvalidTransactionErr) } // IsIndexingNotSupported is a stub for backwards-compatibility. @@ -119,3 +100,8 @@ func policyNotSupportedError() *Error { Code: PolicyNotSupportedErr, } } + +func isError(err error, code string) bool { + e, ok := err.(*Error) + return ok && e.Code == code +} diff --git a/vendor/github.com/open-policy-agent/opa/v1/storage/inmem/ast.go b/vendor/github.com/open-policy-agent/opa/v1/storage/inmem/ast.go index 40f18ab0de..3f7e93d593 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/storage/inmem/ast.go +++ b/vendor/github.com/open-policy-agent/opa/v1/storage/inmem/ast.go @@ -284,21 +284,19 @@ func removeInAstArray(arr *ast.Array, path storage.Path) (ast.Value, error) { } if len(path) == 1 { - var elems []*ast.Term // Note: possibly expensive operation for large data. + elems := make([]*ast.Term, 0, arr.Len()-1) for i := range arr.Len() { - if i == idx { - continue + if i != idx { + elems = append(elems, arr.Elem(i)) } - elems = append(elems, arr.Elem(i)) } return ast.NewArray(elems...), nil } updatedChild, err := removeInAst(arr.Elem(idx).Value, path[1:]) - if err != nil { - return nil, err + if err == nil { + arr.Set(idx, ast.NewTerm(updatedChild)) } - arr.Set(idx, ast.NewTerm(updatedChild)) - return arr, nil + return arr, err } diff --git a/vendor/github.com/open-policy-agent/opa/v1/storage/inmem/inmem.go b/vendor/github.com/open-policy-agent/opa/v1/storage/inmem/inmem.go index 8aa1fc9e42..d4b2d06d01 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/storage/inmem/inmem.go +++ b/vendor/github.com/open-policy-agent/opa/v1/storage/inmem/inmem.go @@ -20,7 +20,6 @@ import ( "fmt" "io" "path/filepath" - "strings" "sync" "sync/atomic" @@ -116,7 +115,7 @@ func NewFromASTObject(data ast.Object) storage.Store { type store struct { rmu sync.RWMutex // reader-writer lock wmu sync.Mutex // writer lock - xid uint64 // last generated transaction id + xid atomic.Uint64 // last generated transaction id data any // raw or AST data policies map[string][]byte // raw policies triggers map[*handle]storage.TriggerConfig // registered triggers @@ -137,7 +136,7 @@ type handle struct { func (db *store) NewTransaction(_ context.Context, params ...storage.TransactionParams) (storage.Transaction, error) { txn := &transaction{ - xid: atomic.AddUint64(&db.xid, uint64(1)), + xid: db.xid.Add(1), db: db, } @@ -173,7 +172,7 @@ func (db *store) Truncate(ctx context.Context, txn storage.Transaction, params s } if update.IsPolicy { - err = underlying.UpsertPolicy(strings.TrimLeft(update.Path.String(), "/"), update.Value) + err = underlying.UpsertPolicy(update.Path.PolicyID(), update.Value) if err != nil { return err } @@ -183,11 +182,8 @@ func (db *store) Truncate(ctx context.Context, txn storage.Transaction, params s return err } - var key []string - dirpath := strings.TrimLeft(update.Path.String(), "/") - if len(dirpath) > 0 { - key = strings.Split(dirpath, "/") - } + // Do not round trip via String() to avoid URL encoding. + key := []string(update.Path) if value != nil { obj, err := mktree(key, value) @@ -349,7 +345,7 @@ func (db *store) Write(_ context.Context, txn storage.Transaction, op storage.Pa val := util.Reference(value) if db.roundTripOnWrite { - if err := util.RoundTrip(val); err != nil { + if err := util.RoundTripFast(val); err != nil { return err } } diff --git a/vendor/github.com/open-policy-agent/opa/v1/storage/path.go b/vendor/github.com/open-policy-agent/opa/v1/storage/path.go index 16bb3e42c5..0143082adb 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/storage/path.go +++ b/vendor/github.com/open-policy-agent/opa/v1/storage/path.go @@ -9,10 +9,10 @@ import ( "fmt" "net/url" "slices" - "strconv" "strings" "github.com/open-policy-agent/opa/v1/ast" + "github.com/open-policy-agent/opa/v1/util" ) // RootPath refers to the root document in storage. @@ -102,11 +102,10 @@ func (p Path) Ref(head *ast.Term) (ref ast.Ref) { ref = make(ast.Ref, len(p)+1) ref[0] = head for i := range p { - idx, err := strconv.ParseInt(p[i], 10, 64) - if err == nil { - ref[i+1] = ast.UIntNumberTerm(uint64(idx)) + if idx, ok := util.Atoi(p[i]); ok && idx >= 0 { + ref[i+1] = ast.InternedTerm(idx) } else { - ref[i+1] = ast.StringTerm(p[i]) + ref[i+1] = ast.InternedTerm(p[i]) } } return ref @@ -131,6 +130,14 @@ func (p Path) String() string { return sb.String() } +// PolicyID returns the ID identifying the module stored at p, for use with the +// [Policy] interface: segments are joined verbatim, without the leading '/' and +// percent-encoding [Path.String] applies, so that IDs match the raw, unescaped +// bundle manifest roots they're compared against. +func (p Path) PolicyID() string { + return strings.Join(p, "/") +} + // MustParsePath returns a new Path for s. If s cannot be parsed, this function // will panic. This is mostly for test purposes. func MustParsePath(s string) Path { diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/aggregates.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/aggregates.go index f018057213..1005f522d1 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/aggregates.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/aggregates.go @@ -5,7 +5,9 @@ package topdown import ( + "math" "math/big" + "slices" "github.com/open-policy-agent/opa/v1/ast" "github.com/open-policy-agent/opa/v1/topdown/builtins" @@ -63,37 +65,50 @@ func exactIntAccumulate(a termIterable, init int64, op func(z, x, y *big.Int) *b return builtins.IntToNumber(acc), true } +// addInt returns x+y, reporting false if the sum overflows an int so the caller +// can fall back to exact big.Int accumulation instead of wrapping silently. +func addInt(x, y int) (int, bool) { + if (y > 0 && x > math.MaxInt-y) || (y < 0 && x < math.MinInt-y) { + return 0, false + } + return x + y, true +} + func builtinSum(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { switch a := operands[0].Value.(type) { case *ast.Array: // Fast path for arrays of integers is := 0 - nonInts := a.Until(func(x *ast.Term) bool { + bail := a.Until(func(x *ast.Term) bool { if n, ok := x.Value.(ast.Number); ok { if i, ok := n.Int(); ok { - is += i - return false + if s, ok := addInt(is, i); ok { + is = s + return false + } } } return true }) - if !nonInts { + if !bail { return iter(ast.InternedTerm(is)) } - // Non-integer values found, so we need to sum as floats. + // A non-integer element, or an integer sum that would overflow the + // machine int: accumulate on exact big.Ints, falling back to floats for + // genuinely non-integer input. if n, ok := exactIntAccumulate(a, 0, (*big.Int).Add); ok { return iter(ast.NewTerm(n)) } - sum := big.NewFloat(0) + sum := new(big.Float) tmp := new(big.Float) err := a.Iter(func(x *ast.Term) error { n, ok := x.Value.(ast.Number) if !ok { return builtins.NewOperandElementErr(1, a, x.Value, "number") } - sum = new(big.Float).Add(sum, builtins.NumberToFloatInto(tmp, n)) + sum = sum.Add(sum, builtins.NumberToFloatInto(tmp, n)) return nil }) if err != nil { @@ -103,16 +118,20 @@ func builtinSum(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) err case ast.Set: // Fast path for sets of integers is := 0 - nonInts := a.Until(func(x *ast.Term) bool { - if n, ok := x.Value.(ast.Number); ok { + bail := false + for _, term := range a.Slice() { + if n, ok := term.Value.(ast.Number); ok { if i, ok := n.Int(); ok { - is += i - return false + if s, ok := addInt(is, i); ok { + is = s + continue + } } } - return true - }) - if !nonInts { + bail = true + break + } + if !bail { return iter(ast.InternedTerm(is)) } @@ -120,18 +139,15 @@ func builtinSum(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) err return iter(ast.NewTerm(n)) } - sum := big.NewFloat(0) + sum := new(big.Float) tmp := new(big.Float) - err := a.Iter(func(x *ast.Term) error { - n, ok := x.Value.(ast.Number) + + for _, term := range a.Slice() { + n, ok := term.Value.(ast.Number) if !ok { - return builtins.NewOperandElementErr(1, a, x.Value, "number") + return builtins.NewOperandElementErr(1, a, term.Value, "number") } - sum = new(big.Float).Add(sum, builtins.NumberToFloatInto(tmp, n)) - return nil - }) - if err != nil { - return err + sum = sum.Add(sum, builtins.NumberToFloatInto(tmp, n)) } return iter(ast.NewTerm(builtins.FloatToNumber(sum))) } @@ -152,7 +168,7 @@ func builtinProduct(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) if !ok { return builtins.NewOperandElementErr(1, a, x.Value, "number") } - product = new(big.Float).Mul(product, builtins.NumberToFloatInto(tmp, n)) + product = product.Mul(product, builtins.NumberToFloatInto(tmp, n)) return nil }) if err != nil { @@ -171,7 +187,7 @@ func builtinProduct(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) if !ok { return builtins.NewOperandElementErr(1, a, x.Value, "number") } - product = new(big.Float).Mul(product, builtins.NumberToFloatInto(tmp, n)) + product = product.Mul(product, builtins.NumberToFloatInto(tmp, n)) return nil }) if err != nil { @@ -190,7 +206,7 @@ func builtinMax(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) err } max := ast.InternedNullTerm.Value a.Foreach(func(x *ast.Term) { - if ast.Compare(max, x.Value) <= 0 { + if max.Compare(x.Value) <= 0 { max = x.Value } }) @@ -199,16 +215,7 @@ func builtinMax(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) err if a.Len() == 0 { return nil } - max, err := a.Reduce(ast.InternedNullTerm, func(max *ast.Term, elem *ast.Term) (*ast.Term, error) { - if ast.Compare(max, elem) <= 0 { - return elem, nil - } - return max, nil - }) - if err != nil { - return err - } - return iter(max) + return iter(slices.MaxFunc(a.Slice(), ast.TermValueCompare)) } return builtins.NewOperandTypeErr(1, operands[0].Value, "set", "array") @@ -222,7 +229,7 @@ func builtinMin(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) err } min := a.Elem(0).Value a.Foreach(func(x *ast.Term) { - if ast.Compare(min, x.Value) >= 0 { + if min.Compare(x.Value) >= 0 { min = x.Value } }) @@ -231,23 +238,7 @@ func builtinMin(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) err if a.Len() == 0 { return nil } - min, err := a.Reduce(ast.InternedNullTerm, func(min *ast.Term, elem *ast.Term) (*ast.Term, error) { - // The null term is considered to be less than any other term, - // so in order for min of a set to make sense, we need to check - // for it. - if min.Value.Compare(ast.InternedNullValue) == 0 { - return elem, nil - } - - if ast.Compare(min, elem) >= 0 { - return elem, nil - } - return min, nil - }) - if err != nil { - return err - } - return iter(min) + return iter(slices.MinFunc(a.Slice(), ast.TermValueCompare)) } return builtins.NewOperandTypeErr(1, operands[0].Value, "set", "array") diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/arithmetic.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/arithmetic.go index ce870904f5..abdd6ea72d 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/arithmetic.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/arithmetic.go @@ -130,7 +130,10 @@ func arithDivide(a, b *big.Float) (*big.Float, error) { } func arithRem(a, b *big.Int) (*big.Int, error) { - if b.Int64() == 0 { + // Sign, not Int64: Int64 returns the low 64 bits when b does not fit in an + // int64, so any nonzero multiple of 2^64 (e.g. 10 % 18446744073709551616) + // would be misreported as modulo by zero. + if b.Sign() == 0 { return nil, errors.New("modulo by zero") } return new(big.Int).Rem(a, b), nil diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/bindings.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/bindings.go index 809a31676c..6ad36c8fff 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/bindings.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/bindings.go @@ -6,10 +6,10 @@ package topdown import ( "fmt" - "strconv" "strings" "github.com/open-policy-agent/opa/v1/ast" + "github.com/open-policy-agent/opa/v1/util" ) type undo struct { @@ -35,9 +35,9 @@ type bindings struct { instr *Instrumentation } -func newBindings(id uint64, instr *Instrumentation) *bindings { +func newBindings(instr *Instrumentation) *bindings { values := newBindingsArrayHashmap() - return &bindings{id, values, instr} + return &bindings{0, values, instr} } // newBindingsWithSize creates bindings pre-sized for the expected number of entries. @@ -49,7 +49,6 @@ func newBindingsWithSize(id uint64, instr *Instrumentation, sizeHint int) *bindi } func (u *bindings) Iter(caller *bindings, iter func(*ast.Term, *ast.Term) error) error { - var err error u.values.Iter(func(k *ast.Term, _ value) bool { @@ -87,6 +86,10 @@ func (u *bindings) PlugNamespaced(a *ast.Term, caller *bindings) *ast.Term { return u.plugNamespaced(a, caller) } +func (u *bindings) size() int { + return u.values.size() +} + func (u *bindings) plugNamespaced(a *ast.Term, caller *bindings) *ast.Term { switch v := a.Value.(type) { case ast.Var: @@ -189,12 +192,12 @@ func (u *bindings) namespaceVar(v *ast.Term, caller *bindings) *ast.Term { if !ok { panic("illegal value") } - if caller != nil && caller != u { - // Root documents (i.e., data, input) should never be namespaced because they - // are globally unique. - if !ast.RootDocumentNames.Contains(v) { - return ast.VarTerm(string(name) + strconv.FormatUint(u.id, 10)) - } + if caller != nil && caller != u && !ast.RootDocumentNames.Contains(v) { + // Root documents (i.e., data, input) should never be namespaced + // because they are globally unique. + len := len(name) + util.NumDigitsUint(u.id) + buf := util.AppendInt(append(make([]byte, 0, len), name...), u.id) + return ast.VarTerm(util.ByteSliceToString(buf)) } return v } @@ -296,11 +299,7 @@ func (vis namespacingVisitor) namespaceTerm(a *ast.Term) *ast.Term { return &cpy case ast.Ref: cpy := *a - ref := make(ast.Ref, len(v)) - for i := range ref { - ref[i] = vis.namespaceTerm(v[i]) - } - cpy.Value = ref + cpy.Value = ast.Ref(util.Map(v, vis.namespaceTerm)) return &cpy } return a @@ -458,6 +457,13 @@ func (b *bindingsArrayHashmap) Iter(f func(k *ast.Term, v value) bool) { } } +func (b *bindingsArrayHashmap) size() int { + if b.m == nil { + return b.n + } + return len(b.m) +} + func (b *bindingsArrayHashmap) find(key *ast.Term) int { if b.a == nil || b.n == 0 { return -1 diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/builtins.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/builtins.go index b5a8a6714d..1f5cfb7f33 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/builtins.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/builtins.go @@ -7,7 +7,6 @@ package topdown import ( "context" "encoding/binary" - "fmt" "io" "math/rand" @@ -73,7 +72,6 @@ type ( // function. If a random number generator cannot be created, an error is // returned. func (bctx *BuiltinContext) Rand() (*rand.Rand, error) { - if bctx.rand != nil { return bctx.rand, nil } @@ -180,26 +178,18 @@ func functionalWrapper4(name string, fn FunctionalBuiltin4) BuiltinFunc { } func handleBuiltinErr(name string, loc *ast.Location, err error) error { + var code string switch err := err.(type) { case BuiltinEmpty: return nil case *Error, Halt: return err case builtins.ErrOperand: - e := &Error{ - Code: TypeErr, - Message: fmt.Sprintf("%v: %v", name, err.Error()), - Location: loc, - } - return e.Wrap(err) + code = TypeErr default: - e := &Error{ - Code: BuiltinErr, - Message: fmt.Sprintf("%v: %v", name, err.Error()), - Location: loc, - } - return e.Wrap(err) + code = BuiltinErr } + return (&Error{Code: code, Message: name + ": " + err.Error(), Location: loc}).Wrap(err) } func readInt64(r io.Reader) (int64, error) { diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/cache.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/cache.go index 9a162e2a57..a7022f1703 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/cache.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/cache.go @@ -144,7 +144,7 @@ type baseCache struct { root *baseCacheElem } -func newBaseCache() *baseCache { +func newBaseCache() BaseCache { return &baseCache{ root: newBaseCacheElem(), } @@ -229,8 +229,8 @@ func (s *refStack) Pop() { func (s *refStack) Prefixed(ref ast.Ref) bool { if s != nil { sl := s.sl.Slice() - for i := len(sl) - 1; i >= 0; i-- { - if slices.ContainsFunc(sl[i].refs, ref.HasPrefix) { + for _, s := range slices.Backward(sl) { + if slices.ContainsFunc(s.refs, ref.HasPrefix) { return true } } @@ -330,7 +330,7 @@ func (s *functionMocksStack) PopPairs() { } func (s *functionMocksStack) PutPairs(mocks [][2]*ast.Term) { - el := frame{} + el := make(frame, len(mocks)) for i := range mocks { el[mocks[i][0].Value.String()] = mocks[i][1] } @@ -347,8 +347,8 @@ func (s *functionMocksStack) Get(f ast.Ref) (*ast.Term, bool) { } current := s.stack.PeekGroup() - for i := len(current) - 1; i >= 0; i-- { - if r, ok := current[i][f.String()]; ok { + for _, c := range slices.Backward(current) { + if r, ok := c[f.String()]; ok { return r, true } } diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/cidr.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/cidr.go index e6e2bb3ae7..3f419a2931 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/cidr.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/cidr.go @@ -7,7 +7,6 @@ import ( "math/big" "net" "slices" - "sort" cidrMerge "github.com/open-policy-agent/opa/internal/cidr/merge" "github.com/open-policy-agent/opa/v1/ast" @@ -235,38 +234,15 @@ type cidrBlockRange struct { Network *net.IPNet } +func (c *cidrBlockRange) Compare(other *cidrBlockRange) int { + if cmp := bytes.Compare(*c.Last, *other.Last); cmp != 0 { // Compare last IP. + return cmp + } + return bytes.Compare(*c.First, *other.First) // Then compare first IP. +} + type cidrBlockRanges []*cidrBlockRange -// Implement Sort interface -func (c cidrBlockRanges) Len() int { - return len(c) -} - -func (c cidrBlockRanges) Swap(i, j int) { - c[i], c[j] = c[j], c[i] -} - -func (c cidrBlockRanges) Less(i, j int) bool { - // Compare last IP. - cmp := bytes.Compare(*c[i].Last, *c[j].Last) - if cmp < 0 { - return true - } else if cmp > 0 { - return false - } - - // Then compare first IP. - cmp = bytes.Compare(*c[i].First, *c[j].First) - if cmp < 0 { - return true - } else if cmp > 0 { - return false - } - - // Ranges are Equal. - return false -} - // builtinNetCIDRMerge merges the provided list of IP addresses and subnets into the smallest possible list of CIDRs. // It merges adjacent subnets where possible, those contained within others and also removes any duplicates. // Original Algorithm: https://github.com/netaddr/netaddr. @@ -283,16 +259,12 @@ func builtinNetCIDRMerge(_ BuiltinContext, operands []*ast.Term, iter func(*ast. networks = append(networks, network) } case ast.Set: - err := v.Iter(func(x *ast.Term) error { - network, err := generateIPNet(x) + for _, term := range v.Slice() { + network, err := generateIPNet(term) if err != nil { return err } networks = append(networks, network) - return nil - }) - if err != nil { - return err } default: return errors.New("operand must be an array") @@ -367,7 +339,7 @@ func generateIPNet(term *ast.Term) (*net.IPNet, error) { } func mergeCIDRs(ranges cidrBlockRanges) cidrBlockRanges { - sort.Sort(ranges) + slices.SortFunc(ranges, (*cidrBlockRange).Compare) // Merge adjacent CIDRs if possible. for i := len(ranges) - 1; i > 0; i-- { diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/comparison.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/comparison.go index d0434a028b..e5f34a2919 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/comparison.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/comparison.go @@ -6,43 +6,35 @@ package topdown import "github.com/open-policy-agent/opa/v1/ast" -type compareFunc func(a, b ast.Value) bool - -func compareGreaterThan(a, b ast.Value) bool { - return a.Compare(b) > 0 +func builtinGreaterThan(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { + return iter(ast.InternedTerm(operands[0].Value.Compare(operands[1].Value) > 0)) } -func compareGreaterThanEq(a, b ast.Value) bool { - return a.Compare(b) >= 0 +func builtinGreaterThanEq(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { + return iter(ast.InternedTerm(operands[0].Value.Compare(operands[1].Value) >= 0)) } -func compareLessThan(a, b ast.Value) bool { - return a.Compare(b) < 0 +func builtinLessThan(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { + return iter(ast.InternedTerm(operands[0].Value.Compare(operands[1].Value) < 0)) } -func compareLessThanEq(a, b ast.Value) bool { - return a.Compare(b) <= 0 +func builtinLessThanEq(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { + return iter(ast.InternedTerm(operands[0].Value.Compare(operands[1].Value) <= 0)) } -func compareNotEq(a, b ast.Value) bool { - return a.Compare(b) != 0 +func builtinNotEqual(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { + return iter(ast.InternedTerm(!operands[0].Equal(operands[1]))) } -func compareEq(a, b ast.Value) bool { - return a.Compare(b) == 0 -} - -func builtinCompare(cmp compareFunc) BuiltinFunc { - return func(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { - return iter(ast.InternedTerm(cmp(operands[0].Value, operands[1].Value))) - } +func builtinEqual(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { + return iter(ast.InternedTerm(operands[0].Equal(operands[1]))) } func init() { - RegisterBuiltinFunc(ast.GreaterThan.Name, builtinCompare(compareGreaterThan)) - RegisterBuiltinFunc(ast.GreaterThanEq.Name, builtinCompare(compareGreaterThanEq)) - RegisterBuiltinFunc(ast.LessThan.Name, builtinCompare(compareLessThan)) - RegisterBuiltinFunc(ast.LessThanEq.Name, builtinCompare(compareLessThanEq)) - RegisterBuiltinFunc(ast.NotEqual.Name, builtinCompare(compareNotEq)) - RegisterBuiltinFunc(ast.Equal.Name, builtinCompare(compareEq)) + RegisterBuiltinFunc(ast.GreaterThan.Name, builtinGreaterThan) + RegisterBuiltinFunc(ast.GreaterThanEq.Name, builtinGreaterThanEq) + RegisterBuiltinFunc(ast.LessThan.Name, builtinLessThan) + RegisterBuiltinFunc(ast.LessThanEq.Name, builtinLessThanEq) + RegisterBuiltinFunc(ast.NotEqual.Name, builtinNotEqual) + RegisterBuiltinFunc(ast.Equal.Name, builtinEqual) } diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/copypropagation/copypropagation.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/copypropagation/copypropagation.go index 799a716136..75cc6dbb12 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/copypropagation/copypropagation.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/copypropagation/copypropagation.go @@ -6,7 +6,6 @@ package copypropagation import ( "fmt" - "sort" "github.com/open-policy-agent/opa/v1/ast" "github.com/open-policy-agent/opa/v1/util" @@ -78,9 +77,6 @@ func (p *CopyPropagator) WithCompiler(c *ast.Compiler) *CopyPropagator { // Apply executes the copy propagation optimization and returns a new query. func (p *CopyPropagator) Apply(query ast.Body) ast.Body { - - result := ast.NewBody() - uf, ok := makeDisjointSets(p.livevars, query) if !ok { return query @@ -103,6 +99,7 @@ func (p *CopyPropagator) Apply(query ast.Body) ast.Body { return false }) + result := ast.NewBody() removedEqs := ast.NewValueMap() for _, expr := range query { @@ -374,11 +371,10 @@ func (p *CopyPropagator) placeholderRef(b *binding) *ast.Expr { if !ok || !p.placeholders.Contains(k) { return nil } - ref, ok := b.v.(ast.Ref) - if !ok { + if _, ok = b.v.(ast.Ref); !ok { return nil } - return ast.NewExpr(ast.NewTerm(ref)) + return ast.NewExpr(ast.NewTerm(b.v)) } func (p *CopyPropagator) updateBindingsEq(a, b *ast.Term) (ast.Var, ast.Value, bool) { @@ -475,10 +471,9 @@ func sortbindings(bindings *ast.ValueMap) []*binding { sorted = append(sorted, &binding{k, v}) return false }) - sort.Slice(sorted, func(i, j int) bool { - return sorted[i].k.Compare(sorted[j].k) > 0 + return util.SortedFunc(sorted, func(a, b *binding) int { + return b.k.Compare(a.k) }) - return sorted } // makeDisjointSets builds the union-find structure for the query. The structure @@ -497,24 +492,22 @@ func makeDisjointSets(livevars ast.VarSet, query ast.Body) (*unionFind, bool) { for _, expr := range query { if expr.IsEquality() && !expr.Negated && len(expr.With) == 0 { a, b := expr.Operand(0), expr.Operand(1) - varA, ok1 := a.Value.(ast.Var) - varB, ok2 := b.Value.(ast.Var) + _, aIsVar := a.Value.(ast.Var) + _, bIsVar := b.Value.(ast.Var) switch { - case ok1 && ok2: - if _, ok := uf.Merge(varA, varB); !ok { + case aIsVar && bIsVar: + if _, ok := uf.Merge(a.Value, b.Value); !ok { return nil, false } - - case ok1 && ast.IsConstant(b.Value): - root := uf.MakeSet(varA) + case aIsVar && ast.IsConstant(b.Value): + root := uf.MakeSet(a.Value) if root.constant != nil && !root.constant.Equal(b) { return nil, false } root.constant = b - - case ok2 && ast.IsConstant(a.Value): - root := uf.MakeSet(varB) + case bIsVar && ast.IsConstant(a.Value): + root := uf.MakeSet(b.Value) if root.constant != nil && !root.constant.Equal(a) { return nil, false } @@ -527,11 +520,10 @@ func makeDisjointSets(livevars ast.VarSet, query ast.Body) (*unionFind, bool) { } func isNoop(expr *ast.Expr) bool { - switch t := expr.Terms.(type) { case []*ast.Term: // A==A can be ignored - if expr.Operator().Equal(ast.Equal.Ref()) { + if expr.Operator().Equal(ast.Interned.Refs.Equal) { return expr.Operand(0).Equal(expr.Operand(1)) } return false diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/encoding.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/encoding.go index 5ed8df68f3..5e636760f3 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/encoding.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/encoding.go @@ -12,8 +12,7 @@ import ( "net/url" "strings" - "sigs.k8s.io/yaml" - + "github.com/open-policy-agent/opa/internal/yaml" "github.com/open-policy-agent/opa/v1/ast" "github.com/open-policy-agent/opa/v1/topdown/builtins" "github.com/open-policy-agent/opa/v1/util" diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/errors.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/errors.go index e80339e312..43a2fd72df 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/errors.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/errors.go @@ -6,11 +6,14 @@ package topdown import ( "errors" + "strconv" "github.com/open-policy-agent/opa/v1/ast" "github.com/open-policy-agent/opa/v1/util" ) +var cancelErr = &Error{Code: CancelErr} + // Halt is a special error type that built-in function implementations return to indicate // that policy evaluation should stop immediately. type Halt struct { @@ -29,11 +32,16 @@ type Error struct { Code string `json:"code"` Message string `json:"message"` Location *ast.Location `json:"location,omitempty"` - err error `json:"-"` + + // StackTrace is the stack of queries being evaluated when the error occurred. + // Only populated when enabled (see Query.WithStackTraces), and left out of + // Error() so enabling it doesn't change the messages callers display. + StackTrace StackTrace `json:"stack_trace,omitempty"` + + err error `json:"-"` } const ( - // InternalErr represents an unknown evaluation error. InternalErr string = "eval_internal_error" @@ -61,22 +69,21 @@ const ( // IsError returns true if the err is an Error. func IsError(err error) bool { - var e *Error - return errors.As(err, &e) + _, ok := errors.AsType[*Error](err) + return ok } // IsCancel returns true if err was caused by cancellation. func IsCancel(err error) bool { - return errors.Is(err, &Error{Code: CancelErr}) + return errors.Is(err, cancelErr) } // Is allows matching topdown errors using errors.Is (see IsCancel). func (e *Error) Is(target error) bool { - var t *Error - if errors.As(target, &t) { + if t, ok := errors.AsType[*Error](target); ok { return (t.Code == "" || e.Code == t.Code) && (t.Message == "" || e.Message == t.Message) && - (t.Location == nil || t.Location.Compare(e.Location) == 0) + (t.Location == nil || t.Location.Equal(e.Location)) } return false } @@ -154,6 +161,18 @@ func mergeConflictErr(loc *ast.Location) error { } } +// unevaluatedOperandErr is returned when a built-in function would have been +// called with an operand that requires evaluation, which indicates a bug in OPA +// rather than in the policy being evaluated. +func unevaluatedOperandErr(loc *ast.Location, name string, pos int, operand *ast.Term) error { + return &Error{ + Code: InternalErr, + Location: loc, + Message: "built-in function " + name + " called with operand " + strconv.Itoa(pos) + + " that requires evaluation: " + operand.String(), + } +} + func internalErr(loc *ast.Location, msg string) error { return &Error{ Code: InternalErr, diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/errors_jsonv2.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/errors_jsonv2.go index 29b6f3761e..1d0ed6d72a 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/errors_jsonv2.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/errors_jsonv2.go @@ -20,5 +20,9 @@ func (e *Error) MarshalJSONTo(enc *jsontext.Encoder) (err error) { err = jsonv2.WriteField(enc, "location", e.Location) } + if len(e.StackTrace) > 0 { + err = errors.Join(err, jsonv2.WriteFieldValue(enc, "stack_trace", e.StackTrace)) + } + return errors.Join(err, enc.WriteToken(jsontext.EndObject)) } diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/eval.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/eval.go index f22af29267..882e248501 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/eval.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/eval.go @@ -107,6 +107,7 @@ type eval struct { inliningControl *inliningControl runtime *ast.Term builtinErrors *builtinErrors + stackCapture *stackTraceCapture roundTripper CustomizeRoundTripper evaluated *EvaluatedRuleTracker genvarprefix string @@ -134,8 +135,8 @@ var ( deecPool = util.NewSyncPool[deferredEarlyExitContainer]() resolverPool = util.NewSyncPool[evalResolver]() arraysRecPool = util.NewSyncPool[biunifyArraysRecParams]() - evalFuncPool = util.NewResettablePool[evalFunc, *evalFunc]() - evalBuiltinPool = util.NewResettablePool[evalBuiltin, *evalBuiltin]() + evalFuncPool = util.NewResettablePool[evalFunc]() + evalBuiltinPool = util.NewResettablePool[evalBuiltin]() ) func (e *eval) Run(iter evalIterator) error { @@ -192,6 +193,7 @@ func (e *eval) closure(query ast.Body, cpy *eval) { cpy.queryID = cpy.queryIDFact.Next() cpy.parent = e cpy.findOne = false + cpy.defined = false } // childWithBindingSizeHint creates a child evaluator with bindings pre-sized for the expected number of variables. @@ -203,6 +205,7 @@ func (e *eval) childWithBindingSizeHint(query ast.Body, cpy *eval, sizeHint int) cpy.bindings = newBindingsWithSize(cpy.queryID, e.instr, sizeHint) cpy.parent = e cpy.findOne = false + cpy.defined = false } func (e *eval) next(iter evalIterator) error { @@ -375,17 +378,13 @@ func (e *eval) evalExpr(iter evalIterator) error { } if e.cancel != nil && e.cancel.Cancelled() { + cancelErr := &Error{Code: CancelErr, Message: "caller cancelled query execution"} if e.ctx != nil && e.ctx.Err() != nil { - return &Error{ - Code: CancelErr, - Message: e.ctx.Err().Error(), - err: e.ctx.Err(), - } - } - return &Error{ - Code: CancelErr, - Message: "caller cancelled query execution", + err := e.ctx.Err() + cancelErr.Message = err.Error() + cancelErr.err = err } + return cancelErr } if e.index >= len(e.query) { @@ -413,20 +412,21 @@ func (e *eval) evalExpr(iter evalIterator) error { return e.evalWith(iter) } - return e.evalStep(func(e *eval) error { + err := e.evalStep(func(e *eval) error { return e.next(iter) }) + + // The innermost point an error passes through with every enclosing query's + // expression index still intact. + return e.withStackTrace(err) } -func (e *eval) evalStep(iter evalIterator) error { +func (e *eval) evalStep(iter evalIterator) (err error) { expr := e.query[e.index] - if expr.Negated { return e.evalNot(iter) } - var err error - // NOTE(æ): the reason why there's one branch for the tracing case and one almost // identical branch below for when tracing is disabled is that the tracing case // allocates wildly. These allocations are cause by the "defined" boolean variable @@ -629,7 +629,6 @@ func (e *eval) fmtVar() string { func (e *eval) evalNot(iter evalIterator) error { expr := e.query[e.index] - if e.unknown(expr, e.bindings) { return e.setupAndEvalNotPartial(iter) } @@ -639,7 +638,6 @@ func (e *eval) evalNot(iter evalIterator) error { defer evalPool.Put(child) e.closure(negation, child) - if e.traceEnabled { child.traceEnter(negation) } @@ -660,8 +658,6 @@ func (e *eval) evalNot(iter evalIterator) error { return iter(e) } - child.defined = false - e.traceFail(expr) return nil } @@ -741,20 +737,20 @@ func (e *eval) evalWith(iter evalIterator) error { input, err := mergeTermWithValues(e.input, pairsInput) if err != nil { - return &Error{ + return e.withStackTrace(&Error{ Code: ConflictErr, Location: expr.Location, Message: err.Error(), - } + }) } data, err := mergeTermWithValues(e.data, pairsData) if err != nil { - return &Error{ + return e.withStackTrace(&Error{ Code: ConflictErr, Location: expr.Location, Message: err.Error(), - } + }) } oldInput, oldData, pushedFrame := e.evalWithPush(input, data, functionMocks, targets, disable) @@ -765,6 +761,7 @@ func (e *eval) evalWith(iter evalIterator) error { oldInput, oldData, pushedFrame = e.evalWithPush(input, data, functionMocks, targets, disable) return err }) + err = e.withStackTrace(err) e.evalWithPop(oldInput, oldData, pushedFrame) @@ -793,24 +790,15 @@ func (e *eval) evalWithPush(input, data *ast.Term, functionMocks [][2]*ast.Term, e.data = data } - if e.comprehensionCache == nil { - e.comprehensionCache = newComprehensionCache() - } - + e.comprehensionCache = util.Or(e.comprehensionCache, newComprehensionCache) e.comprehensionCache.Push() e.virtualCache.Push() - if e.targetStack == nil { - e.targetStack = newRefStack() - } - + e.targetStack = util.Or(e.targetStack, newRefStack) e.targetStack.Push(targets) e.inliningControl.PushDisable(disable, true) - if e.functionMocks == nil { - e.functionMocks = newFunctionMocksStack() - } - + e.functionMocks = util.Or(e.functionMocks, newFunctionMocksStack) e.functionMocks.PutPairs(functionMocks) return oldInput, oldData, pushedFrame @@ -939,7 +927,6 @@ func (e *eval) evalNotPartial(expr *ast.Expr, unNegateFn unNegateFn, complementF } func (e *eval) evalNotPartialSupport(negationID uint64, expr *ast.Expr, supportTermsFn supportTermsFn, unknowns ast.VarSet, queries []ast.Body, iter evalIterator) error { - // Prepare support rule head. supportName := fmt.Sprintf("__not%d_%d_%d__", e.queryID, e.index, negationID) term := ast.RefTerm(ast.DefaultRootDocument, e.saveNamespace, ast.StringTerm(supportName)) @@ -952,27 +939,15 @@ func (e *eval) evalNotPartialSupport(negationID uint64, expr *ast.Expr, supportT bodyVars.Update(q.Vars(ast.VarVisitorParams{})) } - unknowns = unknowns.Intersect(bodyVars) - // Make rule args. Sort them to ensure order is deterministic. - args := make([]*ast.Term, 0, len(unknowns)) - - for v := range unknowns { - args = append(args, ast.NewTerm(v)) - } - - slices.SortFunc(args, ast.TermValueCompare) - + args := util.SortedFunc(util.MapKeys(unknowns.Intersect(bodyVars), ast.ToTerm), ast.TermValueCompare) if len(args) > 0 { - head.Args = args + head.Args = util.SortedFunc(args, ast.TermValueCompare) } // Save support rules. for _, query := range queries { - e.saveSupport.Insert(path, &ast.Rule{ - Head: head, - Body: query, - }) + e.saveSupport.Insert(path, &ast.Rule{Head: head, Body: query}) } // Save expression that refers to support rule set. @@ -998,7 +973,8 @@ func (e *eval) evalCall(terms []*ast.Term, iter unifyIterator) error { mock, mocked := e.functionMocks.Get(ref) if mocked { if m, ok := mock.Value.(ast.Ref); ok && isFunction(e.compiler.TypeEnv, m) { // builtin or data function - mockCall := append([]*ast.Term{mock}, terms[1:]...) + mockCall := make([]*ast.Term, 0, len(terms)) + mockCall = append(append(mockCall, mock), terms[1:]...) e.functionMocks.Push() err := e.evalCall(mockCall, func() error { @@ -1029,19 +1005,28 @@ func (e *eval) evalCall(terms []*ast.Term, iter unifyIterator) error { ir, err = e.getRules(ref, terms[1:], index) } defer ast.IndexResultPool.Put(ir) - if err != nil { + if err != nil || ir == nil { return err } - if ir == nil { - return nil + + // Since values may outlive the function in partial evaluation, + // only use pooled evalFunc when not doing partial eval. + var eval *evalFunc + if e.partial() { + eval = &evalFunc{} + } else { + eval = evalFuncPool.Get() + defer evalFuncPool.Put(eval) } - eval := evalFuncPool.Get() - defer evalFuncPool.Put(eval) - eval.e = e - eval.terms = terms eval.ir = ir + eval.terms = slices.Grow(eval.terms, len(terms))[:len(terms)] + copy(eval.terms, terms) + + if eval.cacheKey == nil { + eval.cacheKey, eval.args = make(ast.Ref, 0, 8), make([]*ast.Term, 0, 8) + } return eval.eval(iter) } @@ -1319,8 +1304,7 @@ func (e *eval) biunifyValues(a, b *ast.Term, b1, b2 *bindings, iter unifyIterato // Sets must not contain unbound variables at this point as we cannot unify // them. So simply plug both sides (to substitute any bound variables with // values) and then check for equality. - switch a.Value.(type) { - case ast.Set: + if _, ok := a.Value.(ast.Set); ok { a = b1.Plug(a) b = b2.Plug(b) } @@ -1382,7 +1366,6 @@ func (e *eval) biunifyRef(a, b *ast.Term, b1, b2 *bindings, iter unifyIterator) } func (e *eval) biunifyComprehension(a, b *ast.Term, b1, b2 *bindings, swap bool, iter unifyIterator) error { - if e.unknown(a, b1) { return e.biunifyComprehensionPartial(a, b, b1, b2, swap, iter) } @@ -1410,16 +1393,13 @@ func (e *eval) biunifyComprehension(a, b *ast.Term, b1, b2 *bindings, swap bool, } func (e *eval) buildComprehensionCache(a *ast.Term) (*ast.Term, error) { - index := e.comprehensionIndex(a) if index == nil { e.instr.counterIncr(evalOpComprehensionCacheSkip) return nil, nil } - if e.comprehensionCache == nil { - e.comprehensionCache = newComprehensionCache() - } + e.comprehensionCache = util.Or(e.comprehensionCache, newComprehensionCache) cache, ok := e.comprehensionCache.Elem(a) if !ok { @@ -1443,13 +1423,7 @@ func (e *eval) buildComprehensionCache(a *ast.Term) (*ast.Term, error) { e.instr.counterIncr(evalOpComprehensionCacheHit) } - values := make([]*ast.Term, len(index.Keys)) - - for i := range index.Keys { - values[i] = e.bindings.Plug(index.Keys[i]) - } - - return cache.Get(values), nil + return cache.Get(util.Map(index.Keys, e.bindings.Plug)), nil } func (e *eval) buildComprehensionCacheArray(x *ast.ArrayComprehension, keys []*ast.Term) (*comprehensionCacheElem, error) { @@ -1459,10 +1433,7 @@ func (e *eval) buildComprehensionCacheArray(x *ast.ArrayComprehension, keys []*a e.childWithBindingSizeHint(x.Body, child, ast.EstimateBodyBindingCount(x.Body)) node := newComprehensionCacheElem() return node, child.Run(func(child *eval) error { - values := make([]*ast.Term, len(keys)) - for i := range keys { - values[i] = child.bindings.Plug(keys[i]) - } + values := util.Map(keys, child.bindings.Plug) head := child.bindings.Plug(x.Term) cached := node.Get(values) if cached != nil { @@ -1481,10 +1452,7 @@ func (e *eval) buildComprehensionCacheSet(x *ast.SetComprehension, keys []*ast.T e.childWithBindingSizeHint(x.Body, child, ast.EstimateBodyBindingCount(x.Body)) node := newComprehensionCacheElem() return node, child.Run(func(child *eval) error { - values := make([]*ast.Term, len(keys)) - for i := range keys { - values[i] = child.bindings.Plug(keys[i]) - } + values := util.Map(keys, child.bindings.Plug) head := child.bindings.Plug(x.Term) cached := node.Get(values) if cached != nil { @@ -1504,10 +1472,7 @@ func (e *eval) buildComprehensionCacheObject(x *ast.ObjectComprehension, keys [] e.childWithBindingSizeHint(x.Body, child, ast.EstimateBodyBindingCount(x.Body)) node := newComprehensionCacheElem() return node, child.Run(func(child *eval) error { - values := make([]*ast.Term, len(keys)) - for i := range keys { - values[i] = child.bindings.Plug(keys[i]) - } + values := util.Map(keys, child.bindings.Plug) headKey := child.bindings.Plug(x.Key) headValue := child.bindings.Plug(x.Value) cached := node.Get(values) @@ -1834,6 +1799,17 @@ type evalResolver struct { args []*ast.Term } +// IndexEveryCandidateEvaluated implements ast.IndexEveryCandidateEvaluated. +// +// Partial evaluation never exits early, whatever its definitions resolve to: +// evalExpr raises the error only where `!e.partial()`. So it evaluates every +// candidate even under a ruleset IndexResult.EarlyExit says a caller could stop in +// -- that field is what the ruleset permits, not what this caller does, which is +// why an index has to ask. +func (e *evalResolver) IndexEveryCandidateEvaluated() bool { + return e.e.partial() +} + func (e *evalResolver) Resolve(ref ast.Ref) (ast.Value, error) { e.e.instr.startTimer(evalOpResolve) @@ -2025,24 +2001,20 @@ func (e *eval) rewrittenVar(v ast.Var) (ast.Var, bool) { } func (e *eval) getDeclArgsLen(x *ast.Expr) (int, error) { - if !x.IsCall() { return -1, nil } operator := x.Operator() bi, _, ok := e.builtinFunc(operator.String()) - if ok { return bi.Decl.Arity(), nil } ir, err := e.getRules(operator, nil, e.ruleIndex(operator)) defer ast.IndexResultPool.Put(ir) - if err != nil { + if err != nil || ir == nil || ir.Empty() { return -1, err - } else if ir == nil || ir.Empty() { - return -1, nil } return len(ir.Rules[0].Head.Args), nil @@ -2076,17 +2048,24 @@ func (e *evalBuiltin) canUseNDBCache(bi *ast.Builtin) bool { return bi.Nondeterministic && e.bctx != nil && e.bctx.NDBuiltinCache != nil } -func (e *evalBuiltin) eval(iter unifyIterator) error { - - operands := make([]*ast.Term, len(e.terms)) - - for i := range e.terms { - operands[i] = e.e.bindings.Plug(e.terms[i]) +// operandRequiresEval returns true if a plugged built-in operand still contains +// terms that must be evaluated. ast.IsConstant answers this exactly, but walks +// composites, making the check linear in operand size on every built-in call. +// This stays O(1) -- IsGround is a cached field on composites -- at the cost of +// missing nested terms that require evaluation but are ground (e.g. [data.foo]). +func operandRequiresEval(v ast.Value) bool { + switch v.(type) { + case ast.Var, ast.Ref, ast.Call, + *ast.ArrayComprehension, *ast.ObjectComprehension, *ast.SetComprehension: + return true } - numDeclArgs := e.bi.Decl.Arity() + return !v.IsGround() +} - e.e.instr.startTimer(evalOpBuiltinCall) +func (e *evalBuiltin) eval(iter unifyIterator) error { + operands := util.Map(e.terms, e.e.bindings.Plug) + numDeclArgs := e.bi.Decl.Arity() // NOTE(philipc): We sometimes have to drop the very last term off // the args list for cases where a builtin's result is used/assigned, @@ -2097,6 +2076,24 @@ func (e *evalBuiltin) eval(iter unifyIterator) error { endIndex-- } + // Every operand must be ground, except a captured output -- walk() is called + // with a non-ground composite there. Void built-ins have none, and Arity() + // undercounts the variadic ones (always void), so endIndex can't be used. + checkEnd := endIndex + if e.bi.Decl.Result() == nil { + checkEnd = len(operands) + } + + for i, operand := range operands[:checkEnd] { + if operandRequiresEval(operand.Value) { + // If hit, this is a bug: the compiler hoists arguments that require evaluation. + // Fail loudly, as the built-in would return undefined instead leading to unexpected results. + return unevaluatedOperandErr(e.e.query[e.e.index].Location, e.bi.Name, i+1, operand) + } + } + + e.e.instr.startTimer(evalOpBuiltinCall) + // We skip evaluation of the builtin entirely if the NDBCache is // present, and we have a non-deterministic builtin already cached. if e.canUseNDBCache(e.bi) { @@ -2132,12 +2129,9 @@ func (e *evalBuiltin) eval(iter unifyIterator) error { } // Normal unification flow for builtins: - err := e.f(bctx, operands, func(output *ast.Term) error { - + err := e.f(bctx, operands, func(output *ast.Term) (err error) { e.e.instr.stopTimer(evalOpBuiltinCall) - var err error - switch { case e.bi.Decl.Result() == nil: err = iter() @@ -2171,6 +2165,13 @@ func (e *evalBuiltin) eval(iter unifyIterator) error { if t, ok := err.(Halt); ok { err = t.Err } else { + // Built-in errors are collected here rather than unwinding through + // evalExpr, so the stack has to be recorded now. Skip it when the + // collected errors have no consumer: query.go drops them, and a + // policy over messy data reaches this for every row. + if c := e.e.stackCapture; c != nil && c.builtinErrors { + err = e.e.attachStackTrace(err) + } e.e.builtinErrors.errs = append(e.e.builtinErrors.errs, err) err = nil } @@ -2181,15 +2182,21 @@ func (e *evalBuiltin) eval(iter unifyIterator) error { } type evalFunc struct { - e *eval - ir *ast.IndexResult - terms []*ast.Term + terms []*ast.Term + cacheKey []*ast.Term + args []*ast.Term + e *eval + ir *ast.IndexResult } // Reset clears the fields before this evalFunc is returned to its pool, // so pooling it doesn't keep terms/index results from the previous call alive. func (e *evalFunc) Reset() { - e.e, e.terms, e.ir = nil, nil, nil + clear(e.terms) + clear(e.cacheKey) + clear(e.args) + e.terms, e.cacheKey, e.args = e.terms[:0], e.cacheKey[:0], e.args[:0] + e.e, e.ir = nil, nil } func (e *evalFunc) eval(iter unifyIterator) error { @@ -2249,35 +2256,28 @@ func (e *evalFunc) eval(iter unifyIterator) error { } func (e *evalFunc) evalValue(iter unifyIterator, argCount int, findOne bool) error { - var cacheKey ast.Ref if !e.e.partial() { - var hit bool - var err error - cacheKey, hit, err = e.evalCache(argCount, iter) - if err != nil { + hit, err := e.evalCache(argCount, iter) + if err != nil || hit { return err - } else if hit { - return nil } } - // NOTE(anders): While it makes the code a bit more complex, reusing the - // args slice across each function increment saves a lot of resources - // compared to creating a new one inside each call to evalOneRule... so - // think twice before simplifying this :) - args := make([]*ast.Term, len(e.terms)-1) + numArgs := len(e.terms) - 1 + e.args = slices.Grow(e.args, numArgs)[:numArgs] var prev *ast.Term return withSuppressEarlyExit(func() error { var outerEe *deferredEarlyExitError for _, rule := range e.ir.Rules { - copy(args, rule.Head.Args) - if len(args) == len(rule.Head.Args)+1 { - args[len(args)-1] = rule.Head.Value + copy(e.args, rule.Head.Args) + numHeadArgs := len(rule.Head.Args) + if numArgs == numHeadArgs+1 { + e.args[numArgs-1] = rule.Head.Value } - next, err := e.evalOneRule(iter, rule, args, cacheKey, prev, findOne) + next, err := e.evalOneRule(iter, rule, prev, findOne) if err != nil { if oee, ok := err.(*deferredEarlyExitError); ok { if outerEe == nil { @@ -2289,12 +2289,12 @@ func (e *evalFunc) evalValue(iter unifyIterator, argCount int, findOne bool) err } if next == nil { for _, erule := range e.ir.Else[rule] { - copy(args, erule.Head.Args) - if len(args) == len(erule.Head.Args)+1 { - args[len(args)-1] = erule.Head.Value + copy(e.args, erule.Head.Args) + if numArgs == numHeadArgs+1 { + e.args[numArgs-1] = erule.Head.Value } - next, err = e.evalOneRule(iter, erule, args, cacheKey, prev, findOne) + next, err = e.evalOneRule(iter, erule, prev, findOne) if err != nil { if oee, ok := err.(*deferredEarlyExitError); ok { if outerEe == nil { @@ -2315,12 +2315,12 @@ func (e *evalFunc) evalValue(iter unifyIterator, argCount int, findOne bool) err } if e.ir.Default != nil && prev == nil { - copy(args, e.ir.Default.Head.Args) - if len(args) == len(e.ir.Default.Head.Args)+1 { - args[len(args)-1] = e.ir.Default.Head.Value + copy(e.args, e.ir.Default.Head.Args) + if numArgs == len(e.ir.Default.Head.Args)+1 { + e.args[numArgs-1] = e.ir.Default.Head.Value } - _, err := e.evalOneRule(iter, e.ir.Default, args, cacheKey, prev, findOne) + _, err := e.evalOneRule(iter, e.ir.Default, prev, findOne) return err } @@ -2333,46 +2333,43 @@ func (e *evalFunc) evalValue(iter unifyIterator, argCount int, findOne bool) err }) } -func (e *evalFunc) evalCache(argCount int, iter unifyIterator) (ast.Ref, bool, error) { +func (e *evalFunc) evalCache(argCount int, iter unifyIterator) (bool, error) { plen := len(e.terms) if plen == argCount+2 { // func name + output = 2 plen -= 1 } - cacheKey := make([]*ast.Term, plen) + e.cacheKey = slices.Grow(e.cacheKey, plen)[:plen] for i := range plen { - if e.terms[i].IsGround() { - // Avoid expensive copying of ref if it is ground. - cacheKey[i] = e.terms[i] - } else { - cacheKey[i] = e.e.bindings.Plug(e.terms[i]) + e.cacheKey[i] = e.terms[i] // Avoid expensive copying of ref if ground + if !e.terms[i].IsGround() { + e.cacheKey[i] = e.e.bindings.Plug(e.terms[i]) } } - cached, _ := e.e.virtualCache.Get(cacheKey) - if cached != nil { + if cached, _ := e.e.virtualCache.Get(e.cacheKey); cached != nil { e.e.instr.counterIncr(evalOpVirtualCacheHit) if argCount == len(e.terms)-1 { // f(x) if ast.Boolean(false).Equal(cached.Value) { - return nil, true, nil + return true, nil } - return nil, true, iter() + return true, iter() } // f(x, y), y captured output value - return nil, true, e.e.unify(e.terms[len(e.terms)-1] /* y */, cached, iter) + return true, e.e.unify(e.terms[len(e.terms)-1] /* y */, cached, iter) } e.e.instr.counterIncr(evalOpVirtualCacheMiss) - return cacheKey, false, nil + return false, nil } -func (e *evalFunc) evalOneRule(iter unifyIterator, rule *ast.Rule, args []*ast.Term, cacheKey ast.Ref, prev *ast.Term, findOne bool) (*ast.Term, error) { +func (e *evalFunc) evalOneRule(iter unifyIterator, rule *ast.Rule, prev *ast.Term, findOne bool) (*ast.Term, error) { child := evalPool.Get() defer evalPool.Put(child) // Optimization: pre-size bindings based on function argument count to reduce memory waste. // Function argument count is known at compile time and most functions have < 10 arguments. // This avoids allocating the default 16-slot array when only 2-3 bindings are needed. - sizeHint := len(args) + sizeHint := len(e.args) e.e.childWithBindingSizeHint(rule.Body, child, sizeHint) child.findOne = findOne @@ -2380,25 +2377,26 @@ func (e *evalFunc) evalOneRule(iter unifyIterator, rule *ast.Rule, args []*ast.T child.traceEnter(rule) - err := child.biunifyTerms(e.terms[1:], args, e.e.bindings, child.bindings, func() error { + err := child.biunifyTerms(e.terms[1:], e.args, e.e.bindings, child.bindings, func() error { return child.eval(func(child *eval) error { child.traceExit(rule) e.e.evaluated.Record(rule) // Partial evaluation must save an expression that tests the output value if the output value // was not captured to handle the case where the output value may be `false`. - if len(rule.Head.Args) == len(e.terms)-1 && e.e.saveSet.Contains(rule.Head.Value, child.bindings) { + noOutputCapture := len(rule.Head.Args) == len(e.terms)-1 + if noOutputCapture && e.e.saveSet.Contains(rule.Head.Value, child.bindings) { err := e.e.saveExpr(ast.NewExpr(rule.Head.Value), child.bindings, iter) child.traceRedo(rule) return err } result = child.bindings.Plug(rule.Head.Value) - if cacheKey != nil { - e.e.virtualCache.Put(cacheKey, result) // the redos confirm this, or the evaluation is aborted + if e.cacheKey != nil { + e.e.virtualCache.Put(e.cacheKey, result) // the redos confirm this, or the evaluation is aborted } - if len(rule.Head.Args) == len(e.terms)-1 && ast.Boolean(false).Equal(result.Value) { + if noOutputCapture && ast.Boolean(false).Equal(result.Value) { if prev != nil && !prev.Equal(result) { return functionConflictErr(rule.Location) } @@ -2437,15 +2435,13 @@ func (e *evalFunc) partialEvalSupport(declArgsLen int, iter unifyIterator) error if !e.e.saveSupport.Exists(path) { for _, rule := range e.ir.Rules { - err := e.partialEvalSupportRule(rule, path) - if err != nil { + if err := e.partialEvalSupportRule(rule, path); err != nil { return err } } if e.ir.Default != nil { - err := e.partialEvalSupportRule(e.ir.Default, path) - if err != nil { + if err := e.partialEvalSupportRule(e.ir.Default, path); err != nil { return err } } @@ -2455,9 +2451,11 @@ func (e *evalFunc) partialEvalSupport(declArgsLen int, iter unifyIterator) error return nil } - term := ast.NewTerm(path) + terms := make([]*ast.Term, len(e.terms)) + terms[0] = ast.NewTerm(path) + copy(terms[1:], e.terms[1:]) - return e.e.saveCall(declArgsLen, append([]*ast.Term{term}, e.terms[1:]...), iter) + return e.e.saveCall(declArgsLen, terms, iter) } func (e *evalFunc) partialEvalSupportRule(rule *ast.Rule, path ast.Ref) error { @@ -2486,6 +2484,7 @@ func (e *evalFunc) partialEvalSupportRule(rule *ast.Rule, path ast.Ref) error { // Skip this rule body if it fails to type-check. // Type-checking failure means the rule body will never succeed. if e.e.compiler.PassesTypeCheck(plugged) { + e.e.evaluated.Record(rule) head := &ast.Head{ Name: rule.Head.Name, Reference: rule.Head.Reference, @@ -2517,12 +2516,12 @@ type deferredEarlyExitContainer struct { } func (dc *deferredEarlyExitContainer) handleErr(err error) error { - if err == nil { - return nil - } - - if dc.deferred == nil && errors.As(err, &dc.deferred) && dc.deferred != nil { - return nil + if err != nil && dc.deferred == nil { + var ok bool + dc.deferred, ok = errors.AsType[*deferredEarlyExitError](err) + if ok && dc.deferred != nil { + return nil + } } return err @@ -2566,7 +2565,6 @@ func (e evalTree) eval(iter unifyIterator) error { } func (e evalTree) finish(iter unifyIterator) error { - // In some cases, it may not be possible to PE the ref. If the path refers // to virtual docs that PE does not support or base documents where inlining // has been disabled, then we have to save. @@ -2583,7 +2581,6 @@ func (e evalTree) finish(iter unifyIterator) error { } func (e evalTree) next(iter unifyIterator, plugged *ast.Term) error { - var node *ast.TreeNode cpy := e @@ -2636,19 +2633,14 @@ func (e evalTree) next(iter unifyIterator, plugged *ast.Term) error { cacheRef = append(cacheRef, k) } - if !expand { + if !expand && e.e.partial() { // The parameter key(s) are not ground, so we cannot // select a concrete sub-source. Under partial evaluation // the reference is unknown and must be residualized; // otherwise it is simply undefined and we fall through // with the bare (rule-less) external node. - if e.e.partial() { - saved := make(ast.Ref, len(e.ref)) - for i := range e.ref { - saved[i] = e.bindings.Plug(e.ref[i]) - } - return e.e.saveUnify(ast.NewTerm(saved), e.rterm, e.bindings, e.rbindings, iter) - } + saved := ast.Ref(util.Map(e.ref, e.bindings.Plug)) + return e.e.saveUnify(ast.NewTerm(saved), e.rterm, e.bindings, e.rbindings, iter) } } @@ -2755,6 +2747,13 @@ func (e evalTree) enumerate(iter unifyIterator) error { doc, err := e.e.Resolve(e.plugged[:e.pos]) if err != nil { + // The save set check in biunifyValues compares refs as written, so a ref + // that only becomes unknown once bindings are plugged (e.g. + // data[input.type].x with data.project.x unknown) reaches here, where the + // document can't be enumerated and must be saved as evalTree.finish does. + if ast.IsUnknownValueErr(err) { + return e.e.saveUnify(ast.NewTerm(e.plugged), e.rterm, e.bindings, e.rbindings, iter) + } return err } @@ -2765,6 +2764,7 @@ func (e evalTree) enumerate(iter unifyIterator) error { // Use method value to avoid closure allocation. // Create once and reuse for both doc and virtual doc enumeration. en := enumerateNext{iter: iter, e: &e, key: nil} + call := en.call if doc != nil { switch doc := doc.(type) { @@ -2772,7 +2772,7 @@ func (e evalTree) enumerate(iter unifyIterator) error { for i := range doc.Len() { k := ast.InternedTerm(i) en.key = k - err := e.e.biunify(k, e.ref[e.pos], e.bindings, e.bindings, en.call) + err := e.e.biunify(k, e.ref[e.pos], e.bindings, e.bindings, call) if err := dc.handleErr(err); err != nil { return err @@ -2782,7 +2782,7 @@ func (e evalTree) enumerate(iter unifyIterator) error { ki := doc.KeysIterator() for k, more := ki.Next(); more; k, more = ki.Next() { en.key = k - err := e.e.biunify(k, e.ref[e.pos], e.bindings, e.bindings, en.call) + err := e.e.biunify(k, e.ref[e.pos], e.bindings, e.bindings, call) if err := dc.handleErr(err); err != nil { return err } @@ -2791,7 +2791,7 @@ func (e evalTree) enumerate(iter unifyIterator) error { // Use Slice() to avoid closure allocation in Iter() for _, elem := range doc.Slice() { en.key = elem - err := e.e.biunify(elem, e.ref[e.pos], e.bindings, e.bindings, en.call) + err := e.e.biunify(elem, e.ref[e.pos], e.bindings, e.bindings, call) if err := dc.handleErr(err); err != nil { return err } @@ -2810,8 +2810,15 @@ func (e evalTree) enumerate(iter unifyIterator) error { // Reuse the same enumerateNext for virtual documents for _, k := range e.node.Sorted { key := ast.NewTerm(k) + + // next() descends into both the base document and the rule tree, so + // enumerating a key present in both would yield it twice. + if docHasKey(doc, key) { + continue + } + en.key = key - if err := e.e.biunify(key, e.ref[e.pos], e.bindings, e.bindings, en.call); err != nil { + if err := e.e.biunify(key, e.ref[e.pos], e.bindings, e.bindings, call); err != nil { return err } } @@ -2819,6 +2826,25 @@ func (e evalTree) enumerate(iter unifyIterator) error { return nil } +// docHasKey returns true if key is one of the keys evalTree.enumerate yields +// for the base document doc. +func docHasKey(doc ast.Value, key *ast.Term) bool { + switch doc := doc.(type) { + case ast.Object: + return doc.Get(key) != nil + case *ast.Array: + i, ok := key.Value.(ast.Number) + if !ok { + return false + } + idx, ok := i.Int() + return ok && idx >= 0 && idx < doc.Len() + case ast.Set: + return doc.Contains(key) + } + return false +} + func (e evalTree) extent() (*ast.Term, error) { base, err := e.e.Resolve(e.plugged) if err != nil { @@ -2905,17 +2931,12 @@ type evalVirtual struct { } func (e evalVirtual) eval(iter unifyIterator) error { - ir, err := e.e.getRules(e.plugged[:e.pos+1], nil, e.e.ruleIndex(e.plugged[:e.pos+1])) defer ast.IndexResultPool.Put(ir) - if err != nil { + if err != nil || ir == nil { return err } - if ir == nil { - return nil - } - // Partial evaluation of ordered rules is not supported currently. Save the // expression and continue. This could be revisited in the future. if len(ir.Else) > 0 && e.e.unknownRef(e.ref, e.bindings) { @@ -3003,9 +3024,7 @@ func (h *evalVirtualPartialCacheHint) keyWithoutScope() ast.Ref { } func (e evalVirtualPartial) eval(iter unifyIterator) error { - - unknown := e.e.unknown(e.ref[:e.pos+1], e.bindings) - + unknown := e.e.unknownRef(e.ref[:e.pos+1], e.bindings) if len(e.ref) == e.pos+1 { if unknown { return e.partialEvalSupport(iter) @@ -3038,14 +3057,13 @@ func maxRefLength(rules []*ast.Rule, ceil int) int { } func (e evalVirtualPartial) evalEachRule(iter unifyIterator, unknown bool) error { - if e.ir.Empty() { return nil } if e.e.partial() { m := maxRefLength(e.ir.Rules, len(e.ref)) - if e.e.unknown(e.ref[e.pos+1:m], e.bindings) { + if e.e.unknownRef(e.ref[e.pos+1:m], e.bindings) { for _, rule := range e.ir.Rules { if err := e.evalOneRulePostUnify(iter, rule); err != nil { return err @@ -3095,7 +3113,6 @@ func (e evalVirtualPartial) evalEachRule(iter unifyIterator, unknown bool) error } func (e evalVirtualPartial) evalAllRules(iter unifyIterator, rules []*ast.Rule) error { - cacheKey := e.plugged[:e.pos+1] result, _ := e.e.virtualCache.Get(cacheKey) if result != nil { @@ -3128,17 +3145,16 @@ func (e evalVirtualPartial) evalAllRulesNoCache(rules []*ast.Rule) (*ast.Term, e for _, rule := range rules { e.e.childWithBindingSizeHint(rule.Body, child, ast.EstimateBodyBindingCount(rule.Body)) child.traceEnter(rule) - err := child.eval(func(*eval) error { + err := child.eval(func(*eval) (err error) { child.traceExit(rule) e.e.evaluated.Record(rule) - var err error + result, _, err = e.reduce(rule, child.bindings, result, &visitedRefs) - if err != nil { - return err + if err == nil && child.traceEnabled { + child.traceRedo(rule) } - child.traceRedo(rule) - return nil + return err }) if err != nil { @@ -3159,14 +3175,20 @@ func wrapInObjects(leaf *ast.Term, ref ast.Ref) *ast.Term { return ast.ObjectTerm(ast.Item(key, val)) } -func (e evalVirtualPartial) evalOneRulePreUnify(iter unifyIterator, rule *ast.Rule, result *ast.Term, unknown bool, visitedRefs *[]ast.Ref) (*ast.Term, error) { +func (e evalVirtualPartial) evalOneRulePreUnify( + iter unifyIterator, + rule *ast.Rule, + result *ast.Term, + unknown bool, + visitedRefs *[]ast.Ref, +) (*ast.Term, error) { child := evalPool.Get() defer evalPool.Put(child) e.e.childWithBindingSizeHint(rule.Body, child, ast.EstimateBodyBindingCount(rule.Body)) - - child.traceEnter(rule) - var defined bool + if child.traceEnabled { + child.traceEnter(rule) + } headKey := rule.Head.Key if headKey == nil { @@ -3174,11 +3196,17 @@ func (e evalVirtualPartial) evalOneRulePreUnify(iter unifyIterator, rule *ast.Ru } // Walk the dynamic portion of rule ref and key to unify vars - err := child.biunifyRuleHead(e.pos+1, e.ref, rule, e.bindings, child.bindings, func(_ int) error { - defined = true - return child.eval(func(child *eval) error { - - child.traceExit(rule) + err := child.biunifyRuleHead(e.pos+1, e.ref, rule, e.bindings, child.bindings, func(int) error { + child.defined = true + return child.eval(func(child *eval) (err error) { + if child.traceEnabled { + child.traceExit(rule) + defer func() { + if err == nil { + child.traceRedo(rule) + } + }() + } term := rule.Head.Value if term == nil { @@ -3187,7 +3215,6 @@ func (e evalVirtualPartial) evalOneRulePreUnify(iter unifyIterator, rule *ast.Ru if unknown { term, termbindings := child.bindings.apply(term) - if rule.Head.RuleKind() == ast.MultiValue { term = ast.SetTerm(term) } @@ -3195,37 +3222,24 @@ func (e evalVirtualPartial) evalOneRulePreUnify(iter unifyIterator, rule *ast.Ru objRef := rule.Ref()[e.pos+1:] term = wrapInObjects(term, objRef) - err := e.evalTerm(iter, e.pos+1, term, termbindings) - if err != nil { - return err - } + err = e.evalTerm(iter, e.pos+1, term, termbindings) } else { var dup bool - var err error result, dup, err = e.reduce(rule, child.bindings, result, visitedRefs) - if err != nil { - return err - } else if !unknown && dup { + if err == nil && !unknown && dup && child.traceEnabled { child.traceDuplicate(rule) - return nil } } - child.traceRedo(rule) - - return nil + return err }) }) - if err != nil { - return nil, err - } - - if !defined { + if err == nil && child.traceEnabled && !child.defined { child.traceFail(rule) } - return result, nil + return result, err } func (e *eval) biunifyRuleHead(pos int, ref ast.Ref, rule *ast.Rule, refBindings, ruleBindings *bindings, iter unifyRefIterator) error { @@ -3256,34 +3270,30 @@ func (e *eval) biunifyDynamicRef(pos int, a, b ast.Ref, b1, b2 *bindings, iter u func (e evalVirtualPartial) evalOneRulePostUnify(iter unifyIterator, rule *ast.Rule) error { child := evalPool.Get() - defer evalPool.Put(child) + defer func() { + if child.traceEnabled && !child.defined { + child.traceFail(rule) + } + evalPool.Put(child) + }() e.e.childWithBindingSizeHint(rule.Body, child, ast.EstimateBodyBindingCount(rule.Body)) + if e.e.traceEnabled { + child.traceEnter(rule) + } - child.traceEnter(rule) - var defined bool - - err := child.eval(func(child *eval) error { - defined = true - return e.e.biunifyRuleHead(e.pos+1, e.ref, rule, e.bindings, child.bindings, func(_ int) error { - return e.evalOneRuleContinue(iter, rule, child) + return child.eval(func(next *eval) error { + child.defined = true + return e.e.biunifyRuleHead(e.pos+1, e.ref, rule, e.bindings, next.bindings, func(int) error { + return e.evalOneRuleContinue(iter, rule, next) }) }) - - if err != nil { - return err - } - - if !defined { - child.traceFail(rule) - } - - return nil } func (e evalVirtualPartial) evalOneRuleContinue(iter unifyIterator, rule *ast.Rule, child *eval) error { - - child.traceExit(rule) + if child.traceEnabled { + child.traceExit(rule) + } term := rule.Head.Value if term == nil { @@ -3291,7 +3301,6 @@ func (e evalVirtualPartial) evalOneRuleContinue(iter unifyIterator, rule *ast.Ru } term, termbindings := child.bindings.apply(term) - if rule.Head.RuleKind() == ast.MultiValue { term = ast.SetTerm(term) } @@ -3300,16 +3309,14 @@ func (e evalVirtualPartial) evalOneRuleContinue(iter unifyIterator, rule *ast.Ru term = wrapInObjects(term, objRef) err := e.evalTerm(iter, e.pos+1, term, termbindings) - if err != nil { - return err + if child.traceEnabled && err == nil { + child.traceRedo(rule) } - child.traceRedo(rule) - return nil + return err } func (e evalVirtualPartial) partialEvalSupport(iter unifyIterator) error { - path := e.e.namespaceRef(e.plugged[:e.pos+1]) term := ast.NewTerm(e.e.namespaceRef(e.ref)) @@ -3360,6 +3367,7 @@ func (e evalVirtualPartial) partialEvalSupportRule(rule *ast.Rule, _ ast.Ref) (b // Skip this rule body if it fails to type-check. // Type-checking failure means the rule body will never succeed. if e.e.compiler.PassesTypeCheck(plugged) { + e.e.evaluated.Record(rule) var value *ast.Term if rule.Head.Value != nil { @@ -3423,10 +3431,9 @@ func (e evalVirtualPartial) evalTerm(iter unifyIterator, pos int, term *ast.Term } func (e evalVirtualPartial) evalCache(iter unifyIterator) (evalVirtualPartialCacheHint, error) { - var hint evalVirtualPartialCacheHint - if e.e.unknown(e.ref[:e.pos+1], e.bindings) { + if e.e.unknownRef(e.ref[:e.pos+1], e.bindings) { // FIXME: Return empty hint if unknowns in any e.ref elem overlapping with applicable rule refs? return hint, nil } @@ -3499,8 +3506,7 @@ func (e evalVirtualPartial) evalCache(iter unifyIterator) (evalVirtualPartialCac scope.Ref = append(scope.Ref, plugged) hint.key[len(hint.key)-1] = ast.NewTerm(scope) } else { - scope = vcKeyScope{} - scope.Ref = append(scope.Ref, plugged) + scope = vcKeyScope{Ref: ast.Ref{plugged}} hint.key = append(hint.key, ast.NewTerm(scope)) } } @@ -3604,9 +3610,10 @@ func (q vcKeyScope) AppendText(buf []byte) ([]byte, error) { // reduce removes vars from the tail of the ref. func (q vcKeyScope) reduce() vcKeyScope { ref := q.Ref.CopyNonGround() - var i int - for i = len(q.Ref) - 1; i >= 0; i-- { - if _, ok := q.Ref[i].Value.(ast.Var); !ok { + i := -1 + for idx, v := range slices.Backward(q.Ref) { + if _, ok := v.Value.(ast.Var); !ok { + i = idx break } } @@ -3734,7 +3741,6 @@ type evalVirtualComplete struct { } func (e evalVirtualComplete) eval(iter unifyIterator) error { - if e.ir.Empty() { return nil } @@ -3856,9 +3862,8 @@ func (e evalVirtualComplete) evalValueRule(iter unifyIterator, rule *ast.Rule, p e.e.evaluated.Record(rule) result = child.bindings.Plug(rule.Head.Value) - if prev != nil { - if ast.Compare(result, prev) != 0 { + if !prev.Equal(result) { return completeDocConflictErr(rule.Location) } child.traceRedo(rule) @@ -3891,6 +3896,7 @@ func (e evalVirtualComplete) partialEval(iter unifyIterator) error { err := child.eval(func(child *eval) error { child.traceExit(rule) + e.e.evaluated.Record(rule) term, termbindings := child.bindings.apply(rule.Head.Value) if err := e.evalTerm(iter, term, termbindings); err != nil { @@ -3976,6 +3982,7 @@ func (e evalVirtualComplete) partialEvalSupportRule(rule *ast.Rule, packagePath // Skip this rule body if it fails to type-check. // Type-checking failure means the rule body will never succeed. if e.e.compiler.PassesTypeCheck(plugged) { + e.e.evaluated.Record(rule) head := ast.RefHead(ruleRef, child.bindings.PlugNamespaced(rule.Head.Value, e.e.caller.bindings)) if !e.e.inliningControl.shallow { @@ -4045,6 +4052,15 @@ func (e evalTerm) eval(iter unifyIterator) error { func (e evalTerm) next(iter unifyIterator, plugged *ast.Term) error { + // Key selects an unknown sub-document: save the reference instead of reading + // a concrete document that may lack the key, or hold a stand-in value. The + // partial() test is inline to keep the call off the non-partial hot path. + if e.e.partial() { + if ref, ok := e.unknownPath(plugged); ok { + return e.e.saveUnify(ast.NewTerm(ref), e.rterm, e.bindings, e.rbindings, iter) + } + } + term, bindings := e.get(plugged) if term == nil { return nil @@ -4057,11 +4073,84 @@ func (e evalTerm) next(iter unifyIterator, plugged *ast.Term) error { return cpy.eval(iter) } +// pluggedPrefix returns e.ref[:e.pos] with variables resolved. +func (e evalTerm) pluggedPrefix() ast.Ref { + prefix := make(ast.Ref, e.pos) + for i := range prefix { + prefix[i] = e.bindings.Plug(e.ref[i]) + } + return prefix +} + +// unknownPath reports whether descending into key lands on an unknown, and if +// so returns the full plugged reference to save. +func (e evalTerm) unknownPath(key *ast.Term) (ast.Ref, bool) { + ref := make(ast.Ref, len(e.ref)) + for i := range ref { + if i == e.pos { + ref[i] = key + } else { + ref[i] = e.bindings.Plug(e.ref[i]) + } + } + + if !e.e.saveSet.Covers(ref[:e.pos+1]) { + return nil, false + } + return ref, true +} + +// enumerateUnknownKeys branches on each key an unknown contributes below the +// current prefix but the concrete document lacks. Without it, input[k] with +// input.x unknown and input = {"y": 2} would only ever consider k = "y". +func (e evalTerm) enumerateUnknownKeys(iter unifyIterator, handleErr func(error) error) error { + prefix := e.pluggedPrefix() + + for _, k := range e.e.saveSet.Keys(prefix) { + if term, _ := e.get(k); term != nil { + continue // already covered by the concrete enumeration above + } + // Nothing concrete to descend into: save the whole remaining reference. + ref := make(ast.Ref, len(e.ref)) + copy(ref, prefix) + ref[e.pos] = k + for i := e.pos + 1; i < len(ref); i++ { + ref[i] = e.bindings.Plug(e.ref[i]) + } + // Positions below the key can still rule the branch out: with input.x.a + // unknown, input[k].b has nothing to match at k = "x". + if !e.e.saveSet.ContainsOverlapping(ast.NewTerm(ref), e.bindings) { + continue + } + err := e.e.biunify(k, e.ref[e.pos], e.bindings, e.bindings, func() error { + return e.e.saveUnify(ast.NewTerm(ref), e.rterm, e.bindings, e.rbindings, iter) + }) + if err := handleErr(err); err != nil { + return err + } + } + + return nil +} + +// evalTermNext is the evalTerm counterpart of enumerateNext: it lets the +// object/set enumeration loops pass a method value to biunify instead of a +// function literal, which would escape to the heap on every iteration. +// evalTerm is held by value so call() doesn't chase a second pointer. +type evalTermNext struct { + e evalTerm + iter unifyIterator + key *ast.Term +} + +func (en *evalTermNext) call() error { + return en.e.next(en.iter, en.e.termbindings.Plug(en.key)) +} + func (e evalTerm) enumerate(iter unifyIterator) error { var deferredEe *deferredEarlyExitError handleErr := func(err error) error { - var dee *deferredEarlyExitError - if errors.As(err, &dee) { + if dee, ok := errors.AsType[*deferredEarlyExitError](err); ok { if deferredEe == nil { deferredEe = dee } @@ -4102,10 +4191,14 @@ func (e evalTerm) enumerate(iter unifyIterator) error { } } case ast.Object: - for _, k := range v.Keys() { - err := e.e.biunify(k, e.ref[e.pos], e.termbindings, e.bindings, func() error { - return e.next(iter, e.termbindings.Plug(k)) - }) + // Bind the method value once, outside the loop: a func literal — or a method + // value materialized per iteration — escapes to the heap on every key. + en := evalTermNext{iter: iter, e: e} + call := en.call + ki := v.KeysIterator() + for k, more := ki.Next(); more; k, more = ki.Next() { + en.key = k + err := e.e.biunify(k, e.ref[e.pos], e.termbindings, e.bindings, call) if err != nil { if err := handleErr(err); err != nil { return err @@ -4113,10 +4206,11 @@ func (e evalTerm) enumerate(iter unifyIterator) error { } } case ast.Set: + en := evalTermNext{iter: iter, e: e} + call := en.call for _, elem := range v.Slice() { - err := e.e.biunify(elem, e.ref[e.pos], e.termbindings, e.bindings, func() error { - return e.next(iter, e.termbindings.Plug(elem)) - }) + en.key = elem + err := e.e.biunify(elem, e.ref[e.pos], e.termbindings, e.bindings, call) if err != nil { if err := handleErr(err); err != nil { return err @@ -4125,6 +4219,12 @@ func (e evalTerm) enumerate(iter unifyIterator) error { } } + if e.e.partial() { + if err := e.enumerateUnknownKeys(iter, handleErr); err != nil { + return err + } + } + if deferredEe != nil { return deferredEe } @@ -4327,7 +4427,6 @@ func (e evalNot) eval(iter evalIterator) error { defer evalPool.Put(child) e.e.closure(e.not.Body, child) - if e.e.traceEnabled { child.traceEnter(e.not.Body) } @@ -4402,7 +4501,7 @@ func (e evalNot) evalPartial(iter evalIterator) error { expr := e.e.query[e.e.index] - unNegate := func(expr *ast.Expr) ast.Body { + unNegate := func(*ast.Expr) ast.Body { return e.not.Body } @@ -4545,13 +4644,12 @@ func evalLogicalOperand(parent *eval, body ast.Body) (bool, error) { child.traceEnter(body) } - defined := false err := child.eval(func(*eval) error { if parent.traceEnabled { child.traceExit(body) child.traceRedo(body) } - defined = true + child.defined = true return nil }) @@ -4561,7 +4659,7 @@ func evalLogicalOperand(parent *eval, body ast.Body) (bool, error) { return false, err } - return defined, nil + return child.defined, nil } func plugBody(e *eval, body ast.Body) error { @@ -4655,8 +4753,7 @@ func getSavePairsFromExpr(declArgsLen int, x *ast.Expr, b *bindings, result []sa func getSavePairsFromTerm(x *ast.Term, b *bindings, result []savePair) []savePair { if _, ok := x.Value.(ast.Var); ok { - result = append(result, savePair{x, b}) - return result + return append(result, savePair{x, b}) } vis := ast.NewVarVisitor().WithParams(ast.VarVisitorParams{ SkipClosures: true, @@ -4693,12 +4790,19 @@ func plugKeys(a ast.Object, b *bindings) ast.Object { } func canInlineNegation(safe ast.VarSet, queries []ast.Body) bool { - size := 1 vis := newNestedCheckVisitor() for _, query := range queries { size *= len(query) + + // NOTE(tsandall): this limit is arbitrary–it's only in place to prevent the + // partial evaluation result from blowing up. In the future, we could make this + // configurable or do something more clever. + if size > maxInlineNegationSize { + return false + } + for _, expr := range query { if containsNestedRefOrCall(vis, expr) { // Expressions containing nested refs or calls cannot be trivially negated @@ -4726,12 +4830,13 @@ func canInlineNegation(safe ast.VarSet, queries []ast.Body) bool { } } - // NOTE(tsandall): this limit is arbitrary–it's only in place to prevent the - // partial evaluation result from blowing up. In the future, we could make this - // configurable or do something more clever. - return size <= 16 + return true } +// maxInlineNegationSize is the largest cross product of negated queries that +// evalNotPartial will inline instead of generating support rules for. +const maxInlineNegationSize = 16 + type nestedCheckVisitor struct { vis *ast.GenericVisitor found bool @@ -4752,7 +4857,6 @@ func (v *nestedCheckVisitor) visit(x any) bool { } func containsNestedRefOrCall(vis *nestedCheckVisitor, expr *ast.Expr) bool { - if expr.IsEquality() { for _, term := range expr.Operands() { if containsNestedRefOrCallInTerm(vis, term) { @@ -4808,10 +4912,7 @@ func containsNestedRefOrCallInTerm(vis *nestedCheckVisitor, term *ast.Term) bool return false default: vis.vis.Walk(v) - if vis.found { - return true - } - return false + return vis.found } } @@ -4890,7 +4991,7 @@ func merge(a, b ast.Value) (ast.Value, bool) { // objects can be merged with other objects. If the values cannot be merged, // objB value will be overwritten by objA value. func mergeObjects(objA, objB ast.Object) (result ast.Object, ok bool) { - result = ast.NewObject() + result = ast.NewObjectWithCapacity(objA.Len() + objB.Len()) stop := objA.Until(func(k, v *ast.Term) bool { if v2 := objB.Get(k); v2 == nil { result.Insert(k, v) @@ -4964,9 +5065,9 @@ func (e *eval) updateSavedMocks(withs []*ast.With) []*ast.With { // tree levels. keys are the ground parameter terms in reference order. func wrapExternalParams(keys []*ast.Term, tree *ast.TreeNode) *ast.TreeNode { node := tree - for i := len(keys) - 1; i >= 0; i-- { + for _, key := range slices.Backward(keys) { node = &ast.TreeNode{ - Children: map[ast.Value]*ast.TreeNode{keys[i].Value: node}, + Children: map[ast.Value]*ast.TreeNode{key.Value: node}, } } return node diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/glob.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/glob.go index 4e80c519ba..8924710401 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/glob.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/glob.go @@ -16,7 +16,7 @@ const globInterQueryValueCacheHits = "rego_builtin_glob_interquery_value_cache_h var noDelimiters = []rune{} var dotDelimiters = []rune{'.'} var globCacheLock = sync.RWMutex{} -var globCache = map[string]glob.Glob{} +var globCache = map[string]*glob.Pattern{} func builtinGlobMatch(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { pattern, err := builtins.StringOperand(operands[0].Value, 1) @@ -66,7 +66,7 @@ func globCompileAndMatch(bctx BuiltinContext, id, pattern, match string, delimit // TODO: Use named cache val, ok := bctx.InterQueryBuiltinValueCache.Get(ast.String(id)) if ok { - pat, valid := val.(glob.Glob) + pat, valid := val.(*glob.Pattern) if !valid { // The cache key may exist for a different value type (eg. regex). // In this case, we calculate the glob and return the result w/o updating the cache. diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/http.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/http.go index 79d49f33ca..839725497b 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/http.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/http.go @@ -33,80 +33,14 @@ import ( "github.com/open-policy-agent/opa/v1/util" ) -type cachingMode string - const ( defaultHTTPRequestTimeoutEnv = "HTTP_SEND_TIMEOUT" defaultCachingMode cachingMode = "serialized" cachingModeDeserialized cachingMode = "deserialized" -) + httpSendLatencyMetricKey = "rego_builtin_http_send" + httpSendInterQueryCacheHits = httpSendLatencyMetricKey + "_interquery_cache_hits" + httpSendNetworkRequests = httpSendLatencyMetricKey + "_network_requests" -var defaultHTTPRequestTimeout = time.Second * 5 - -var allowedKeyNames = [...]string{ - "method", - "url", - "body", - "enable_redirect", - "force_json_decode", - "force_yaml_decode", - "headers", - "raw_body", - "tls_use_system_certs", - "tls_ca_cert", - "tls_ca_cert_file", - "tls_ca_cert_env_variable", - "tls_client_cert", - "tls_client_cert_file", - "tls_client_cert_env_variable", - "tls_client_key", - "tls_client_key_file", - "tls_client_key_env_variable", - "tls_insecure_skip_verify", - "tls_server_name", - "timeout", - "cache", - "force_cache", - "force_cache_duration_seconds", - "raise_error", - "caching_mode", - "max_retry_attempts", - "cache_ignored_headers", -} - -// ref: https://www.rfc-editor.org/rfc/rfc7231#section-6.1 -var cacheableHTTPStatusCodes = [...]int{ - http.StatusOK, - http.StatusNonAuthoritativeInfo, - http.StatusNoContent, - http.StatusPartialContent, - http.StatusMultipleChoices, - http.StatusMovedPermanently, - http.StatusNotFound, - http.StatusMethodNotAllowed, - http.StatusGone, - http.StatusRequestURITooLong, - http.StatusNotImplemented, -} - -var ( - httpSendNetworkErrTerm, httpSendInternalErrTerm *ast.Term - - allowedKeys = ast.NewSet() - cacheableCodes = ast.NewSet() - requiredKeys = ast.NewSet(ast.InternedTerm("method"), ast.InternedTerm("url")) - httpSendLatencyMetricKey = "rego_builtin_http_send" - httpSendInterQueryCacheHits = httpSendLatencyMetricKey + "_interquery_cache_hits" - httpSendNetworkRequests = httpSendLatencyMetricKey + "_network_requests" -) - -type httpSendKey string - -// CustomizeRoundTripper allows customizing an existing http.Transport, -// to the returned value, which could be the same Transport or a new one. -type CustomizeRoundTripper func(*http.Transport) http.RoundTripper - -const ( // httpSendBuiltinCacheKey is the key in the builtin context cache that // points to the http.send() specific cache resides at. httpSendBuiltinCacheKey httpSendKey = "HTTP_SEND_CACHE_KEY" @@ -124,8 +58,117 @@ const ( maxRetryDelay = time.Second * 60 ) -func builtinHTTPSend(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { +var ( + defaultHTTPRequestTimeout = time.Second * 5 + allowedKeyNames = [...]string{ + "method", + "url", + "body", + "enable_redirect", + "force_json_decode", + "force_yaml_decode", + "headers", + "raw_body", + "tls_use_system_certs", + "tls_ca_cert", + "tls_ca_cert_file", + "tls_ca_cert_env_variable", + "tls_client_cert", + "tls_client_cert_file", + "tls_client_cert_env_variable", + "tls_client_key", + "tls_client_key_file", + "tls_client_key_env_variable", + "tls_insecure_skip_verify", + "tls_server_name", + "timeout", + "cache", + "force_cache", + "force_cache_duration_seconds", + "raise_error", + "caching_mode", + "max_retry_attempts", + "cache_ignored_headers", + } + // ref: https://www.rfc-editor.org/rfc/rfc7231#section-6.1 + cacheableHTTPStatusCodes = [...]int{ + http.StatusOK, + http.StatusNonAuthoritativeInfo, + http.StatusNoContent, + http.StatusPartialContent, + http.StatusMultipleChoices, + http.StatusMovedPermanently, + http.StatusNotFound, + http.StatusMethodNotAllowed, + http.StatusGone, + http.StatusRequestURITooLong, + http.StatusNotImplemented, + } + allowedKeys = ast.NewSet() + cacheableCodes = ast.NewSet() + requiredKeys = ast.NewSet(ast.InternedTerm("method"), ast.InternedTerm("url")) +) + +type ( + // CustomizeRoundTripper allows customizing an existing http.Transport, + // to the returned value, which could be the same Transport or a new one. + CustomizeRoundTripper func(*http.Transport) http.RoundTripper + cachingMode string + httpSendKey string + interQueryCacheValue struct { + Data []byte + } + // httpRequestExecutor defines an interface for the http send cache + httpRequestExecutor interface { + CheckCache() (ast.Value, error) + InsertIntoCache(value *http.Response) (ast.Value, error) + InsertErrorIntoCache(err error) + ExecuteHTTPRequest() (*http.Response, error) + } + // The httpSendCache is used for intra-query caching of http.send results. + httpSendCache struct { + entries *util.HasherMap[ast.Value, httpSendCacheEntry] + } + httpSendCacheEntry struct { + response *ast.Value + error error + } + interQueryCache struct { + bctx BuiltinContext + req ast.Object + key ast.Object + httpReq *http.Request + httpClient *http.Client + forceJSONDecode bool + forceYAMLDecode bool + forceCacheParams forceCacheParams + } + interQueryCacheData struct { + RespBody []byte + Status string + StatusCode int + Headers http.Header + ExpiresAt time.Time + } + intraQueryCache struct { + bctx BuiltinContext + req ast.Object + key ast.Object + } + forceCacheParams struct { + forceDurationSeconds int32 + } + responseHeaders struct { + etag string // identifier for a specific version of the response + lastModified string // date and time response was last modified as per origin server + } + // deltaSeconds specifies a non-negative integer, representing + // time in seconds: http://tools.ietf.org/html/rfc7234#section-1.2.1 + deltaSeconds int32 +) + +func builtinHTTPSend(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { obj, err := builtins.ObjectOperand(operands[0].Value, 1) if err != nil { return handleBuiltinErr(ast.HTTPSend.Name, bctx.Location, err) @@ -139,7 +182,7 @@ func builtinHTTPSend(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.T req, err := validateHTTPRequestOperand(operands[0], 1) if err != nil { if raiseError { - return handleHTTPSendErr(bctx, err) + return handleHTTPSendErr(bctx.Context, bctx.Location, err) } return iter(generateRaiseErrorResult(handleBuiltinErr(ast.HTTPSend.Name, bctx.Location, err))) @@ -148,9 +191,8 @@ func builtinHTTPSend(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.T result, err := getHTTPResponse(bctx, req) if err != nil { if raiseError { - return handleHTTPSendErr(bctx, err) + return handleHTTPSendErr(bctx.Context, bctx.Location, err) } - result = generateRaiseErrorResult(err) } return iter(result) @@ -161,12 +203,12 @@ func generateRaiseErrorResult(err error) *ast.Term { switch err.(type) { case *url.Error: errObj = ast.NewObject( - ast.Item(ast.InternedTerm("code"), httpSendNetworkErrTerm), + ast.Item(ast.InternedTerm("code"), ast.InternedTerm(HTTPSendNetworkErr)), ast.Item(ast.InternedTerm("message"), ast.StringTerm(err.Error())), ) default: errObj = ast.NewObject( - ast.Item(ast.InternedTerm("code"), httpSendInternalErrTerm), + ast.Item(ast.InternedTerm("code"), ast.InternedTerm(HTTPSendInternalErr)), ast.Item(ast.InternedTerm("message"), ast.StringTerm(err.Error())), ) } @@ -178,7 +220,6 @@ func generateRaiseErrorResult(err error) *ast.Term { } func getHTTPResponse(bctx BuiltinContext, req ast.Object) (*ast.Term, error) { - bctx.Metrics.Timer(httpSendLatencyMetricKey).Start() defer bctx.Metrics.Timer(httpSendLatencyMetricKey).Stop() @@ -200,11 +241,12 @@ func getHTTPResponse(bctx BuiltinContext, req ast.Object) (*ast.Term, error) { if resp == nil { httpResp, err := reqExecutor.ExecuteHTTPRequest() + defer util.Close(httpResp) + if err != nil { reqExecutor.InsertErrorIntoCache(err) return nil, err } - defer util.Close(httpResp) // Add result to intra/inter-query cache. resp, err = reqExecutor.InsertIntoCache(httpResp) if err != nil { @@ -229,59 +271,47 @@ func getKeyFromRequest(req ast.Object) (ast.Object, error) { key.Insert(ast.InternedTerm("cache_ignored_headers"), ast.InternedNullTerm) return key, nil } - var cacheIgnoredHeaders []string - err := ast.As(cacheIgnoredHeadersTerm.Value, &cacheIgnoredHeaders) - if err != nil { - return nil, err + cacheIgnoredHeaders, ok := cacheIgnoredHeadersTerm.Value.(*ast.Array) + if !ok || cacheIgnoredHeaders.Until(util.Not(ast.TermValueIs[ast.String])) { + return nil, errors.New("cache_ignored_headers must be an array of strings") } - var allHeaders map[string]any - err = ast.As(allHeadersTerm.Value, &allHeaders) - if err != nil { - return nil, err - } - for _, header := range cacheIgnoredHeaders { - delete(allHeaders, header) - } - val, err := ast.InterfaceToValue(allHeaders) - if err != nil { - return nil, err - } - key.Insert(ast.InternedTerm("headers"), ast.NewTerm(val)) + allHeaders := allHeadersTerm.Value.(ast.Object) + filteredHeaders := ast.NewObjectWithCapacity(allHeaders.Len()) + + allHeaders.Foreach(func(key, val *ast.Term) { + if !cacheIgnoredHeaders.Until(key.Equal) { + filteredHeaders.Insert(key, val) + } + }) + + key.Insert(ast.InternedTerm("headers"), ast.NewTerm(filteredHeaders)) // remove cache_ignored_headers key key.Insert(ast.InternedTerm("cache_ignored_headers"), ast.InternedNullTerm) return key, nil } func init() { + ast.InternStringTerm(HTTPSendNetworkErr, HTTPSendInternalErr) + ast.InternStringTerm(allowedKeyNames[:]...) for _, element := range allowedKeyNames { - ast.InternStringTerm(element) allowedKeys.Add(ast.InternedTerm(element)) } - ast.InternStringTerm(HTTPSendNetworkErr, HTTPSendInternalErr) - httpSendNetworkErrTerm = ast.InternedTerm(HTTPSendNetworkErr) - httpSendInternalErrTerm = ast.InternedTerm(HTTPSendInternalErr) - createCacheableHTTPStatusCodes() initDefaults() RegisterBuiltinFunc(ast.HTTPSend.Name, builtinHTTPSend) } -func handleHTTPSendErr(bctx BuiltinContext, err error) error { +func handleHTTPSendErr(ctx context.Context, loc *ast.Location, err error) error { // Return HTTP client timeout errors in a generic error message to avoid confusion about what happened. // Do not do this if the builtin context was cancelled and is what caused the request to stop. - if urlErr, ok := err.(*url.Error); ok && urlErr.Timeout() && bctx.Context.Err() == nil { + if urlErr, ok := err.(*url.Error); ok && urlErr.Timeout() && ctx.Err() == nil { err = fmt.Errorf("%s %s: request timed out", urlErr.Op, urlErr.URL) } - if err := bctx.Context.Err(); err != nil { - return Halt{ - Err: &Error{ - Code: CancelErr, - Message: fmt.Sprintf("http.send: timed out (%s)", err.Error()), - }, - } + if err := ctx.Err(); err != nil { + return Halt{Err: &Error{Code: CancelErr, Message: fmt.Sprintf("http.send: timed out (%s)", err.Error())}} } - return handleBuiltinErr(ast.HTTPSend.Name, bctx.Location, err) + return handleBuiltinErr(ast.HTTPSend.Name, loc, err) } func initDefaults() { @@ -300,7 +330,6 @@ func initDefaults() { } func validateHTTPRequestOperand(term *ast.Term, pos int) (ast.Object, error) { - obj, err := builtins.ObjectOperand(term.Value, pos) if err != nil { return nil, err @@ -324,8 +353,7 @@ func validateHTTPRequestOperand(term *ast.Term, pos int) (ast.Object, error) { // canonicalizeHeaders returns a copy of the headers where the keys are in // canonical HTTP form. func canonicalizeHeaders(headers map[string]any) map[string]any { - canonicalized := map[string]any{} - + canonicalized := make(map[string]any, len(headers)) for k, v := range headers { canonicalized[http.CanonicalHeaderKey(k)] = v } @@ -371,21 +399,16 @@ func useSocket(rawURL string) (bool, string, *http.Transport) { return true, u.String(), tr } -func verifyHost(bctx BuiltinContext, host string) error { - if bctx.Capabilities == nil || bctx.Capabilities.AllowNet == nil { +func verifyHost(caps *ast.Capabilities, host string) error { + if caps == nil || caps.AllowNet == nil || slices.Contains(caps.AllowNet, host) { return nil } - - if slices.Contains(bctx.Capabilities.AllowNet, host) { - return nil - } - - return fmt.Errorf("unallowed host: %s", host) + return fmt.Errorf("disallowed host: %s", host) } -func verifyURLHost(bctx BuiltinContext, unverifiedURL string) error { +func verifyURLHost(caps *ast.Capabilities, unverifiedURL string) error { // Eager return to avoid unnecessary URL parsing - if bctx.Capabilities == nil || bctx.Capabilities.AllowNet == nil { + if caps == nil || caps.AllowNet == nil { return nil } @@ -394,47 +417,40 @@ func verifyURLHost(bctx BuiltinContext, unverifiedURL string) error { return err } - host := strings.Split(parsedURL.Host, ":")[0] + host, _, _ := strings.Cut(parsedURL.Host, ":") - return verifyHost(bctx, host) + return verifyHost(caps, host) } func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *http.Client, error) { var ( url, method string - // Additional CA certificates loading options. - tlsCaCert []byte - tlsCaCertEnvVar, tlsCaCertFile string - // Client TLS certificate and key options. Each input source - // comes in a matched pair. - tlsClientCert, tlsClientKey []byte - tlsClientCertEnvVar, tlsClientKeyEnvVar string - tlsClientCertFile, tlsClientKeyFile, tlsServerName string + // CA and client certificates loading options. Each input source comes in a matched pair. + tlsCaCertEnvVar, tlsCaCertFile string + tlsCaCert, tlsClientCert, tlsClientKey []byte + tlsClientCertEnvVar, tlsClientKeyEnvVar, tlsClientCertFile, tlsClientKeyFile string - body, rawBody *bytes.Buffer - enableRedirect, tlsInsecureSkipVerify bool - tlsUseSystemCerts *bool - tlsConfig tls.Config + body *bytes.Buffer + enableRedirect, tlsUseSystemCerts, ok bool customHeaders map[string]any ) + tlsConfig := &tls.Config{} timeout := defaultHTTPRequestTimeout - for _, val := range obj.Keys() { - key, err := ast.JSON(val.Value) + for _, key := range obj.Keys() { + keyAny, err := ast.JSON(key.Value) if err != nil { return nil, nil, err } - - key = key.(string) + val := obj.Get(key) var strVal string - - if s, ok := obj.Get(val).Value.(ast.String); ok { + if s, ok := val.Value.(ast.String); ok { strVal = strings.Trim(string(s), "\"") } else { // Most parameters are strings, so consolidate the type checking. - switch key { + switch keyAny { case "method", "url", "raw_body", @@ -448,83 +464,78 @@ func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *htt "tls_client_key_file", "tls_client_key_env_variable", "tls_server_name": - return nil, nil, fmt.Errorf("%q must be a string", key) + return nil, nil, fmt.Errorf("%q must be a string", keyAny) } } - switch key { + switch keyAny { case "method": method = strings.ToUpper(strVal) case "url": - err := verifyURLHost(bctx, strVal) - if err != nil { + if err := verifyURLHost(bctx.Capabilities, strVal); err != nil { return nil, nil, err } url = strVal case "enable_redirect": - enableRedirect, err = strconv.ParseBool(obj.Get(val).String()) - if err != nil { + if enableRedirect, err = strconv.ParseBool(val.String()); err != nil { return nil, nil, err } case "body": - bodyVal := obj.Get(val).Value - bodyValInterface, err := ast.JSON(bodyVal) + if body != nil { + break // raw_body takes precedence + } + bodyVal, err := ast.JSON(val.Value) if err != nil { return nil, nil, err } - - bodyValBytes, err := json.Marshal(bodyValInterface) + bodyValBytes, err := json.Marshal(bodyVal) if err != nil { return nil, nil, err } body = bytes.NewBuffer(bodyValBytes) case "raw_body": - rawBody = bytes.NewBufferString(strVal) + body = bytes.NewBufferString(strVal) case "tls_use_system_certs": - tempTLSUseSystemCerts, err := strconv.ParseBool(obj.Get(val).String()) + tlsUseSystemCerts, err = strconv.ParseBool(val.String()) if err != nil { return nil, nil, err } - tlsUseSystemCerts = &tempTLSUseSystemCerts case "tls_ca_cert": - tlsCaCert = []byte(strVal) + tlsCaCert = util.StringToByteSlice(strVal) case "tls_ca_cert_file": tlsCaCertFile = strVal case "tls_ca_cert_env_variable": tlsCaCertEnvVar = strVal case "tls_client_cert": - tlsClientCert = []byte(strVal) + tlsClientCert = util.StringToByteSlice(strVal) case "tls_client_cert_file": tlsClientCertFile = strVal case "tls_client_cert_env_variable": tlsClientCertEnvVar = strVal case "tls_client_key": - tlsClientKey = []byte(strVal) + tlsClientKey = util.StringToByteSlice(strVal) case "tls_client_key_file": tlsClientKeyFile = strVal case "tls_client_key_env_variable": tlsClientKeyEnvVar = strVal case "tls_server_name": - tlsServerName = strVal + tlsConfig.ServerName = strVal case "headers": - headersVal := obj.Get(val).Value - headersValInterface, err := ast.JSON(headersVal) + headersValInterface, err := ast.JSON(val.Value) if err != nil { return nil, nil, err } - var ok bool customHeaders, ok = headersValInterface.(map[string]any) if !ok { return nil, nil, errors.New("invalid type for headers key") } case "tls_insecure_skip_verify": - tlsInsecureSkipVerify, err = strconv.ParseBool(obj.Get(val).String()) + tlsConfig.InsecureSkipVerify, err = strconv.ParseBool(val.String()) if err != nil { return nil, nil, err } case "timeout": - timeout, err = parseTimeout(obj.Get(val).Value) - if err != nil { + if timeout, err = parseTimeout(val.Value); err != nil { return nil, nil, err } case "cache", "caching_mode", @@ -532,7 +543,7 @@ func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *htt "force_json_decode", "force_yaml_decode", "raise_error", "max_retry_attempts", "cache_ignored_headers": // no-op default: - return nil, nil, fmt.Errorf("invalid parameter %q", key) + return nil, nil, fmt.Errorf("invalid parameter %q", keyAny) } } @@ -540,26 +551,13 @@ func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *htt customHeaders = canonicalizeHeaders(customHeaders) } - isTLS := false - client := &http.Client{ - Timeout: timeout, - CheckRedirect: func(*http.Request, []*http.Request) error { - return http.ErrUseLastResponse - }, - } - - if tlsInsecureSkipVerify { - isTLS = true - tlsConfig.InsecureSkipVerify = tlsInsecureSkipVerify - } + client := &http.Client{Timeout: timeout, CheckRedirect: useLastResponseRedirect} if len(tlsClientCert) > 0 && len(tlsClientKey) > 0 { cert, err := tls.X509KeyPair(tlsClientCert, tlsClientKey) if err != nil { return nil, nil, err } - - isTLS = true tlsConfig.Certificates = append(tlsConfig.Certificates, cert) } @@ -568,8 +566,6 @@ func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *htt if err != nil { return nil, nil, err } - - isTLS = true tlsConfig.Certificates = append(tlsConfig.Certificates, cert) } @@ -581,20 +577,16 @@ func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *htt return nil, nil, fmt.Errorf("cannot extract public/private key pair from envvars %q, %q: %w", tlsClientCertEnvVar, tlsClientKeyEnvVar, err) } - - isTLS = true tlsConfig.Certificates = append(tlsConfig.Certificates, cert) } // Check the system certificates config first so that we // load additional certificated into the correct pool. - if tlsUseSystemCerts != nil && *tlsUseSystemCerts && runtime.GOOS != "windows" { + if tlsUseSystemCerts && runtime.GOOS != "windows" { pool, err := x509.SystemCertPool() if err != nil { return nil, nil, err } - - isTLS = true tlsConfig.RootCAs = pool } @@ -604,8 +596,6 @@ func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *htt if err != nil { return nil, nil, err } - - isTLS = true tlsConfig.RootCAs = pool } @@ -614,8 +604,6 @@ func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *htt if err != nil { return nil, nil, err } - - isTLS = true tlsConfig.RootCAs = pool } @@ -624,35 +612,27 @@ func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *htt if err != nil { return nil, nil, err } - - isTLS = true tlsConfig.RootCAs = pool } - // If Host header is set, use it for TLS server name. - if host, hasHost := customHeaders["Host"]; hasHost { + // If Host header is set, use it for TLS server name, unless set with tls_server_name. + if host, hasHost := customHeaders["Host"]; tlsConfig.ServerName == "" && hasHost { // Only default the ServerName if the caller has // specified the host. If we don't specify anything, // Go will default to the target hostname. This name // is not the same as the default that Go populates // `req.Host` with, which is why we don't just set // this unconditionally. - isTLS = true tlsConfig.ServerName, _ = host.(string) } - if tlsServerName != "" { - isTLS = true - tlsConfig.ServerName = tlsServerName - } - var transport *http.Transport if ok, parsedURL, tr := useSocket(url); ok { transport = tr url = parsedURL - } else if isTLS { + } else if hasTLSConfig(tlsConfig) { transport = http.DefaultTransport.(*http.Transport).Clone() - transport.TLSClientConfig = &tlsConfig + transport.TLSClientConfig = tlsConfig transport.DisableKeepAlives = true } @@ -665,25 +645,17 @@ func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *htt // check if redirects are enabled if enableRedirect { client.CheckRedirect = func(req *http.Request, _ []*http.Request) error { - return verifyURLHost(bctx, req.URL.String()) + return verifyURLHost(bctx.Capabilities, req.URL.String()) } } - if rawBody != nil { - body = rawBody - } else if body == nil { - body = bytes.NewBufferString("") - } - // create the http request, use the builtin context's context to ensure // the request is cancelled if evaluation is cancelled. - req, err := http.NewRequest(method, url, body) + req, err := http.NewRequestWithContext(bctx.Context, method, url, util.Or(body, emptyBytesBuffer)) if err != nil { return nil, nil, err } - req = req.WithContext(bctx.Context) - // Add custom headers if len(customHeaders) != 0 { for k, v := range customHeaders { @@ -691,7 +663,6 @@ func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *htt if !ok { return nil, nil, fmt.Errorf("invalid type for headers value %q", v) } - req.Header.Add(k, header) } @@ -703,8 +674,7 @@ func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *htt // If the caller specifies the Host header, use it for the HTTP // request host and the TLS server name. if host, hasHost := customHeaders["Host"]; hasHost { - host := host.(string) // We already checked that it's a string. - req.Host = host + req.Host = host.(string) // We already checked that it's a string. } } @@ -715,6 +685,17 @@ func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *htt return req, client, nil } +func hasTLSConfig(tlsConfig *tls.Config) bool { + return tlsConfig.InsecureSkipVerify || + tlsConfig.ServerName != "" || + tlsConfig.RootCAs != nil || + len(tlsConfig.Certificates) > 0 +} + +func useLastResponseRedirect(*http.Request, []*http.Request) error { + return http.ErrUseLastResponse +} + func executeHTTPRequest(req *http.Request, client *http.Client, inputReqObj ast.Object) (*http.Response, error) { var err error var retry int @@ -753,6 +734,10 @@ func executeHTTPRequest(req *http.Request, client *http.Client, inputReqObj ast. return nil, err } +func emptyBytesBuffer() *bytes.Buffer { + return bytes.NewBuffer([]byte{}) +} + func isJSONType(header http.Header) bool { t, _, err := mime.ParseMediaType(header.Get("Content-Type")) if err != nil { @@ -782,16 +767,6 @@ func isContentType(header http.Header, typ ...string) bool { return false } -type httpSendCacheEntry struct { - response *ast.Value - error error -} - -// The httpSendCache is used for intra-query caching of http.send results. -type httpSendCache struct { - entries *util.HasherMap[ast.Value, httpSendCacheEntry] -} - func newHTTPSendCache() *httpSendCache { return &httpSendCache{ entries: util.NewHasherMap[ast.Value, httpSendCacheEntry](ast.ValueEqual), @@ -883,7 +858,7 @@ func (c *interQueryCache) checkHTTPSendInterQueryCache() (ast.Value, error) { value, cerr := requestCache.Clone(cachedValue) if cerr != nil { - return nil, handleHTTPSendErr(c.bctx, cerr) + return nil, handleHTTPSendErr(c.bctx.Context, c.bctx.Location, cerr) } c.bctx.Metrics.Counter(httpSendInterQueryCacheHits).Incr() @@ -902,17 +877,20 @@ func (c *interQueryCache) checkHTTPSendInterQueryCache() (ast.Value, error) { return nil, nil } - if getCurrentTime(c.bctx).Before(cachedRespData.ExpiresAt) { + if getCurrentTime(c.bctx.Time).Before(cachedRespData.ExpiresAt) { return cachedRespData.formatToAST(c.forceJSONDecode, c.forceYAMLDecode) } var err error c.httpReq, c.httpClient, err = createHTTPRequest(c.bctx, c.key) if err != nil { - return nil, handleHTTPSendErr(c.bctx, err) + return nil, handleHTTPSendErr(c.bctx.Context, c.bctx.Location, err) } - headers := parseResponseHeaders(cachedRespData.Headers) + headers := &responseHeaders{ + etag: cachedRespData.Headers.Get("etag"), + lastModified: cachedRespData.Headers.Get("last-modified"), + } // check with the server if the stale response is still up-to-date. // If server returns a new response (ie. status_code=200), update the cache with the new response @@ -935,8 +913,8 @@ func (c *interQueryCache) checkHTTPSendInterQueryCache() (ast.Value, error) { } if forceCaching(c.forceCacheParams) { - createdAt := getCurrentTime(c.bctx) - cachedRespData.ExpiresAt = createdAt.Add(time.Second * time.Duration(c.forceCacheParams.forceCacheDurationSeconds)) + createdAt := getCurrentTime(c.bctx.Time) + cachedRespData.ExpiresAt = createdAt.Add(time.Second * time.Duration(c.forceCacheParams.forceDurationSeconds)) } else { expiresAt, err := expiryFromHeaders(result.Header) if err != nil { @@ -979,13 +957,17 @@ func (c *interQueryCache) checkHTTPSendInterQueryCache() (ast.Value, error) { } // insertIntoHTTPSendInterQueryCache inserts given key and value in the inter-query cache -func insertIntoHTTPSendInterQueryCache(bctx BuiltinContext, key ast.Value, resp *http.Response, respBody []byte, cacheParams *forceCacheParams) error { +func insertIntoHTTPSendInterQueryCache( + bctx BuiltinContext, + key ast.Value, + resp *http.Response, + respBody []byte, + cacheParams forceCacheParams, +) error { if resp == nil || (!forceCaching(cacheParams) && !canStore(resp.Header)) || !cacheableCodes.Contains(ast.InternedTerm(resp.StatusCode)) { return nil } - requestCache := bctx.InterQueryBuiltinCache - obj, ok := key.(ast.Object) if !ok { return errors.New("interface conversion error") @@ -999,9 +981,9 @@ func insertIntoHTTPSendInterQueryCache(bctx BuiltinContext, key ast.Value, resp var pcv cache.InterQueryCacheValue var pcvData *interQueryCacheData if cachingMode == defaultCachingMode { - pcv, pcvData, err = newInterQueryCacheValue(bctx, resp, respBody, cacheParams) + pcv, pcvData, err = newInterQueryCacheValue(bctx.Time, resp, respBody, cacheParams) } else { - pcvData, err = newInterQueryCacheData(bctx, resp, respBody, cacheParams) + pcvData, err = newInterQueryCacheData(bctx.Time, resp, respBody, cacheParams) pcv = pcvData } @@ -1009,7 +991,7 @@ func insertIntoHTTPSendInterQueryCache(bctx BuiltinContext, key ast.Value, resp return err } - requestCache.InsertWithExpiry(key, pcv, pcvData.ExpiresAt) + bctx.InterQueryBuiltinCache.InsertWithExpiry(key, pcv, pcvData.ExpiresAt) return nil } @@ -1019,32 +1001,26 @@ func createCacheableHTTPStatusCodes() { } } -func parseTimeout(timeoutVal ast.Value) (time.Duration, error) { - var timeout time.Duration +func parseTimeout(timeoutVal ast.Value) (timeout time.Duration, err error) { switch t := timeoutVal.(type) { case ast.Number: - timeoutInt, ok := t.Int64() - if !ok { - return timeout, fmt.Errorf("invalid timeout number value %v, must be int64", timeoutVal) - } - return time.Duration(timeoutInt), nil - case ast.String: - // Support strings without a unit, treat them the same as just a number value (ns) - var err error - timeoutInt, err := strconv.ParseInt(string(t), 10, 64) - if err == nil { + if timeoutInt, ok := t.Int64(); ok { return time.Duration(timeoutInt), nil } - - // Try parsing it as a duration (requires a supported units suffix) - timeout, err = time.ParseDuration(string(t)) - if err != nil { - return timeout, fmt.Errorf("invalid timeout value %v: %s", timeoutVal, err) + err = fmt.Errorf("invalid timeout number value %v, must be int64", timeoutVal) + case ast.String: + // Support strings without a unit, treat them the same as just a number value (ns) + if timeoutInt, ok := util.Atoi64(string(t)); ok { + return time.Duration(timeoutInt), nil + } + // Try parsing it as a duration (requires a supported units suffix) + if timeout, err = time.ParseDuration(string(t)); err != nil { + err = fmt.Errorf("invalid timeout value %v: %s", timeoutVal, err) } - return timeout, nil default: - return timeout, builtins.NewOperandErr(1, "'timeout' must be one of {string, number} but got %s", ast.ValueName(t)) + err = builtins.NewOperandErr(1, "'timeout' must be one of {string, number} but got %s", ast.ValueName(t)) } + return timeout, err } func getBoolValFromReqObj(req ast.Object, key *ast.Term) (bool, error) { @@ -1094,12 +1070,13 @@ func getCachingMode(req ast.Object) (cachingMode, error) { return defaultCachingMode, nil } -type interQueryCacheValue struct { - Data []byte -} - -func newInterQueryCacheValue(bctx BuiltinContext, resp *http.Response, respBody []byte, cacheParams *forceCacheParams) (*interQueryCacheValue, *interQueryCacheData, error) { - data, err := newInterQueryCacheData(bctx, resp, respBody, cacheParams) +func newInterQueryCacheValue( + now *ast.Term, + resp *http.Response, + respBody []byte, + cacheParams forceCacheParams, +) (*interQueryCacheValue, *interQueryCacheData, error) { + data, err := newInterQueryCacheData(now, resp, respBody, cacheParams) if err != nil { return nil, nil, err } @@ -1112,34 +1089,20 @@ func newInterQueryCacheValue(bctx BuiltinContext, resp *http.Response, respBody } func (cb interQueryCacheValue) Clone() (cache.InterQueryCacheValue, error) { - dup := make([]byte, len(cb.Data)) - copy(dup, cb.Data) - return &interQueryCacheValue{Data: dup}, nil + return &interQueryCacheValue{Data: slices.Clone(cb.Data)}, nil } func (cb interQueryCacheValue) SizeInBytes() int64 { return int64(len(cb.Data)) } -func (cb *interQueryCacheValue) copyCacheData() (*interQueryCacheData, error) { - var res interQueryCacheData - err := util.UnmarshalJSON(cb.Data, &res) - if err != nil { - return nil, err - } - return &res, nil +func (cb *interQueryCacheValue) copyCacheData() (res *interQueryCacheData, err error) { + err = util.UnmarshalJSON(cb.Data, &res) + return res, err } -type interQueryCacheData struct { - RespBody []byte - Status string - StatusCode int - Headers http.Header - ExpiresAt time.Time -} - -func forceCaching(cacheParams *forceCacheParams) bool { - return cacheParams != nil && cacheParams.forceCacheDurationSeconds > 0 +func forceCaching(cacheParams forceCacheParams) bool { + return cacheParams.forceDurationSeconds > 0 } func expiryFromHeaders(headers http.Header) (time.Time, error) { @@ -1160,29 +1123,25 @@ func expiryFromHeaders(headers http.Header) (time.Time, error) { return expiresAt, nil } -func newInterQueryCacheData(bctx BuiltinContext, resp *http.Response, respBody []byte, cacheParams *forceCacheParams) (*interQueryCacheData, error) { - var expiresAt time.Time - - if forceCaching(cacheParams) { - createdAt := getCurrentTime(bctx) - expiresAt = createdAt.Add(time.Second * time.Duration(cacheParams.forceCacheDurationSeconds)) - } else { - var err error - expiresAt, err = expiryFromHeaders(resp.Header) - if err != nil { - return nil, err - } - } - - cv := interQueryCacheData{ - ExpiresAt: expiresAt, +func newInterQueryCacheData( + now *ast.Term, + resp *http.Response, + respBody []byte, + cacheParams forceCacheParams, +) (data *interQueryCacheData, err error) { + data = &interQueryCacheData{ RespBody: respBody, Status: resp.Status, StatusCode: resp.StatusCode, Headers: resp.Header, } + if forceCaching(cacheParams) { + data.ExpiresAt = getCurrentTime(now).Add(time.Second * time.Duration(cacheParams.forceDurationSeconds)) + } else { + data.ExpiresAt, err = expiryFromHeaders(resp.Header) + } - return &cv, nil + return data, err } func (c *interQueryCacheData) formatToAST(forceJSONDecode, forceYAMLDecode bool) (ast.Value, error) { @@ -1202,53 +1161,31 @@ func (*interQueryCacheData) SizeInBytes() int64 { } func (c *interQueryCacheData) Clone() (cache.InterQueryCacheValue, error) { - dup := make([]byte, len(c.RespBody)) - copy(dup, c.RespBody) - return &interQueryCacheData{ ExpiresAt: c.ExpiresAt, - RespBody: dup, + RespBody: slices.Clone(c.RespBody), Status: c.Status, StatusCode: c.StatusCode, Headers: c.Headers.Clone(), }, nil } -type responseHeaders struct { - etag string // identifier for a specific version of the response - lastModified string // date and time response was last modified as per origin server -} - -// deltaSeconds specifies a non-negative integer, representing -// time in seconds: http://tools.ietf.org/html/rfc7234#section-1.2.1 -type deltaSeconds int32 - -func parseResponseHeaders(headers http.Header) *responseHeaders { - result := responseHeaders{} - - result.etag = headers.Get("etag") - - result.lastModified = headers.Get("last-modified") - - return &result -} - -func revalidateCachedResponse(req *http.Request, client *http.Client, inputReqObj ast.Object, headers *responseHeaders) (*http.Response, bool, error) { - etag := headers.etag - lastModified := headers.lastModified - - if etag == "" && lastModified == "" { +func revalidateCachedResponse( + req *http.Request, + client *http.Client, + inputReqObj ast.Object, + headers *responseHeaders, +) (*http.Response, bool, error) { + if headers.etag == "" && headers.lastModified == "" { return nil, false, nil } cloneReq := req.Clone(req.Context()) - - if etag != "" { - cloneReq.Header.Set("if-none-match", etag) + if headers.etag != "" { + cloneReq.Header.Set("if-none-match", headers.etag) } - - if lastModified != "" { - cloneReq.Header.Set("if-modified-since", lastModified) + if headers.lastModified != "" { + cloneReq.Header.Set("if-modified-since", headers.lastModified) } response, err := executeHTTPRequest(cloneReq, client, inputReqObj) @@ -1256,49 +1193,29 @@ func revalidateCachedResponse(req *http.Request, client *http.Client, inputReqOb return nil, false, err } - switch response.StatusCode { - case http.StatusOK: - return response, true, nil - - case http.StatusNotModified: - return response, false, nil + if isOK := response.StatusCode == http.StatusOK; isOK || response.StatusCode == http.StatusNotModified { + return response, isOK, nil } + util.Close(response) return nil, false, nil } func canStore(headers http.Header) bool { - ccHeaders := parseCacheControlHeader(headers) - // Check "no-store" cache directive // The "no-store" response directive indicates that a cache MUST NOT // store any part of either the immediate request or response. - if _, ok := ccHeaders["no-store"]; ok { - return false - } - return true + _, ok := parseCacheControlHeader(headers)["no-store"] + return !ok } -func getCurrentTime(bctx BuiltinContext) time.Time { - var current time.Time - - value, err := ast.JSON(bctx.Time.Value) - if err != nil { - return current +func getCurrentTime(now *ast.Term) time.Time { + if valueNum, ok := now.Value.(ast.Number); ok { + if valueNumInt, ok := valueNum.Int64(); ok { + return time.Unix(0, valueNumInt).UTC() + } } - - valueNum, ok := value.(json.Number) - if !ok { - return current - } - - valueNumInt, err := valueNum.Int64() - if err != nil { - return current - } - - current = time.Unix(0, valueNumInt).UTC() - return current + return time.Now().UTC() } func parseCacheControlHeader(headers http.Header) map[string]string { @@ -1311,11 +1228,10 @@ func parseCacheControlHeader(headers http.Header) map[string]string { continue } if strings.ContainsRune(part, '=') { - items := strings.Split(part, "=") - if len(items) != 2 { - continue + if strings.Count(part, "=") == 1 { + left, right, _ := strings.Cut(part, "=") + ccDirectives[strings.Trim(left, " ")] = strings.Trim(right, ",") } - ccDirectives[strings.Trim(items[0], " ")] = strings.Trim(items[1], ",") } else { ccDirectives[part] = "" } @@ -1325,27 +1241,17 @@ func parseCacheControlHeader(headers http.Header) map[string]string { } func getResponseHeaderDate(headers http.Header) (date time.Time, err error) { - dateHeader := headers.Get("date") - if dateHeader == "" { - err = errors.New("no date header") - return + if dateHeader := headers.Get("date"); dateHeader != "" { + return http.ParseTime(dateHeader) } - return http.ParseTime(dateHeader) + return date, errors.New("no date header") } -func getResponseHeaderExpires(headers http.Header) time.Time { - expiresHeader := headers.Get("expires") - if expiresHeader == "" { - return time.Time{} +func getResponseHeaderExpires(headers http.Header) (exp time.Time) { + if expiresHeader := headers.Get("expires"); expiresHeader != "" { + exp, _ = http.ParseTime(expiresHeader) } - - date, err := http.ParseTime(expiresHeader) - if err != nil { - // servers can set `Expires: 0` which is an invalid date to indicate expired content - return time.Time{} - } - - return date + return exp } // parseMaxAgeCacheDirective parses the max-age directive expressed in delta-seconds as per @@ -1358,35 +1264,33 @@ func parseMaxAgeCacheDirective(cc map[string]string) (deltaSeconds, error) { val, err := strconv.ParseUint(maxAge, 10, 32) if err != nil { - if numError, ok := err.(*strconv.NumError); ok { - if numError.Err == strconv.ErrRange { - return deltaSeconds(math.MaxInt32), nil - } + if numError, ok := err.(*strconv.NumError); ok && numError.Err == strconv.ErrRange { + return deltaSeconds(math.MaxInt32), nil } return deltaSeconds(-1), err } - if val > math.MaxInt32 { - return deltaSeconds(math.MaxInt32), nil - } - return deltaSeconds(val), nil + return deltaSeconds(min(val, math.MaxInt32)), nil } func formatHTTPResponseToAST(resp *http.Response, forceJSONDecode, forceYAMLDecode bool) (ast.Value, []byte, error) { - resultRawBody, err := io.ReadAll(resp.Body) + raw, err := io.ReadAll(resp.Body) if err != nil { return nil, nil, err } - resultObj, err := prepareASTResult(resp.Header, forceJSONDecode, forceYAMLDecode, resultRawBody, resp.Status, resp.StatusCode) - if err != nil { - return nil, nil, err - } + resultObj, err := prepareASTResult(resp.Header, forceJSONDecode, forceYAMLDecode, raw, resp.Status, resp.StatusCode) - return resultObj, resultRawBody, nil + return resultObj, raw, err } -func prepareASTResult(headers http.Header, forceJSONDecode, forceYAMLDecode bool, body []byte, status string, statusCode int) (ast.Value, error) { +func prepareASTResult( + headers http.Header, + forceJSONDecode, forceYAMLDecode bool, + body []byte, + status string, + statusCode int, +) (ast.Value, error) { var resultBody any // If the response body cannot be JSON/YAML decoded, @@ -1399,39 +1303,25 @@ func prepareASTResult(headers http.Header, forceJSONDecode, forceYAMLDecode bool _ = util.Unmarshal(body, &resultBody) } - result := make(map[string]any) - result["status"] = status - result["status_code"] = statusCode - result["body"] = resultBody - result["raw_body"] = string(body) - result["headers"] = getResponseHeaders(headers) - - resultObj, err := ast.InterfaceToValue(result) + bodyValue, err := ast.InterfaceToValue(resultBody) if err != nil { return nil, err } - return resultObj, nil + return ast.NewObject( + ast.Item(ast.InternedTerm("status"), ast.InternedTerm(status)), + ast.Item(ast.InternedTerm("status_code"), ast.InternedTerm(statusCode)), + ast.Item(ast.InternedTerm("body"), ast.NewTerm(bodyValue)), + ast.Item(ast.InternedTerm("raw_body"), ast.InternedTerm(util.ByteSliceToString(body))), + ast.Item(ast.InternedTerm("headers"), getResponseHeaders(headers)), + ), nil } -func getResponseHeaders(headers http.Header) map[string]any { - respHeaders := map[string]any{} - for headerName, values := range headers { - var respValues []any - for _, v := range values { - respValues = append(respValues, v) - } - respHeaders[strings.ToLower(headerName)] = respValues +func getResponseHeaders(headers http.Header) *ast.Term { + if len(headers) == 0 { + return ast.InternedEmptyObject } - return respHeaders -} - -// httpRequestExecutor defines an interface for the http send cache -type httpRequestExecutor interface { - CheckCache() (ast.Value, error) - InsertIntoCache(value *http.Response) (ast.Value, error) - InsertErrorIntoCache(err error) - ExecuteHTTPRequest() (*http.Response, error) + return ast.NewTerm(ast.MapToObject(headers, strings.ToLower, arrayFromStringSlice)) } // newHTTPRequestExecutor returns a new HTTP request executor that wraps either an inter-query or @@ -1439,50 +1329,31 @@ type httpRequestExecutor interface { func newHTTPRequestExecutor(bctx BuiltinContext, req ast.Object, key ast.Object) (httpRequestExecutor, error) { useInterQueryCache, forceCacheParams, err := useInterQueryCache(req) if err != nil { - return nil, handleHTTPSendErr(bctx, err) + return nil, handleHTTPSendErr(bctx.Context, bctx.Location, err) } if useInterQueryCache && bctx.InterQueryBuiltinCache != nil { - return newInterQueryCache(bctx, req, key, forceCacheParams) + return newInterQueryCache(bctx, req, key, forceCacheParams), nil } - return newIntraQueryCache(bctx, req, key) + return newIntraQueryCache(bctx, req, key), nil } -type interQueryCache struct { - bctx BuiltinContext - req ast.Object - key ast.Object - httpReq *http.Request - httpClient *http.Client - forceJSONDecode bool - forceYAMLDecode bool - forceCacheParams *forceCacheParams -} - -func newInterQueryCache(bctx BuiltinContext, req ast.Object, key ast.Object, forceCacheParams *forceCacheParams) (*interQueryCache, error) { - return &interQueryCache{bctx: bctx, req: req, key: key, forceCacheParams: forceCacheParams}, nil +func newInterQueryCache(bctx BuiltinContext, req ast.Object, key ast.Object, forceCacheParams forceCacheParams) *interQueryCache { + return &interQueryCache{bctx: bctx, req: req, key: key, forceCacheParams: forceCacheParams} } // CheckCache checks the cache for the value of the key set on this object -func (c *interQueryCache) CheckCache() (ast.Value, error) { - var err error - +func (c *interQueryCache) CheckCache() (resp ast.Value, err error) { // Checking the intra-query cache first ensures consistency of errors and HTTP responses within a query. - resp, err := checkHTTPSendCache(c.bctx, c.key) - if err != nil { - return nil, err - } - if resp != nil { - return resp, nil + if resp, err = checkHTTPSendCache(c.bctx, c.key); err != nil || resp != nil { + return resp, err } - c.forceJSONDecode, err = getBoolValFromReqObj(c.key, ast.InternedTerm("force_json_decode")) - if err != nil { - return nil, handleHTTPSendErr(c.bctx, err) + if c.forceJSONDecode, err = getBoolValFromReqObj(c.key, ast.InternedTerm("force_json_decode")); err != nil { + return nil, handleHTTPSendErr(c.bctx.Context, c.bctx.Location, err) } - c.forceYAMLDecode, err = getBoolValFromReqObj(c.key, ast.InternedTerm("force_yaml_decode")) - if err != nil { - return nil, handleHTTPSendErr(c.bctx, err) + if c.forceYAMLDecode, err = getBoolValFromReqObj(c.key, ast.InternedTerm("force_yaml_decode")); err != nil { + return nil, handleHTTPSendErr(c.bctx.Context, c.bctx.Location, err) } resp, err = c.checkHTTPSendInterQueryCache() @@ -1500,7 +1371,7 @@ func (c *interQueryCache) CheckCache() (ast.Value, error) { func (c *interQueryCache) InsertIntoCache(value *http.Response) (ast.Value, error) { result, respBody, err := formatHTTPResponseToAST(value, c.forceJSONDecode, c.forceYAMLDecode) if err != nil { - return nil, handleHTTPSendErr(c.bctx, err) + return nil, handleHTTPSendErr(c.bctx.Context, c.bctx.Location, err) } // Always insert into the intra-query cache, to maintain consistency within the same query. @@ -1521,7 +1392,7 @@ func (c *interQueryCache) ExecuteHTTPRequest() (*http.Response, error) { var err error c.httpReq, c.httpClient, err = createHTTPRequest(c.bctx, c.req) if err != nil { - return nil, handleHTTPSendErr(c.bctx, err) + return nil, handleHTTPSendErr(c.bctx.Context, c.bctx.Location, err) } // Increment counter for actual network requests @@ -1530,14 +1401,8 @@ func (c *interQueryCache) ExecuteHTTPRequest() (*http.Response, error) { return executeHTTPRequest(c.httpReq, c.httpClient, c.req) } -type intraQueryCache struct { - bctx BuiltinContext - req ast.Object - key ast.Object -} - -func newIntraQueryCache(bctx BuiltinContext, req ast.Object, key ast.Object) (*intraQueryCache, error) { - return &intraQueryCache{bctx: bctx, req: req, key: key}, nil +func newIntraQueryCache(bctx BuiltinContext, req ast.Object, key ast.Object) *intraQueryCache { + return &intraQueryCache{bctx: bctx, req: req, key: key} } // CheckCache checks the cache for the value of the key set on this object @@ -1549,16 +1414,16 @@ func (c *intraQueryCache) CheckCache() (ast.Value, error) { func (c *intraQueryCache) InsertIntoCache(value *http.Response) (ast.Value, error) { forceJSONDecode, err := getBoolValFromReqObj(c.key, ast.InternedTerm("force_json_decode")) if err != nil { - return nil, handleHTTPSendErr(c.bctx, err) + return nil, handleHTTPSendErr(c.bctx.Context, c.bctx.Location, err) } forceYAMLDecode, err := getBoolValFromReqObj(c.key, ast.InternedTerm("force_yaml_decode")) if err != nil { - return nil, handleHTTPSendErr(c.bctx, err) + return nil, handleHTTPSendErr(c.bctx.Context, c.bctx.Location, err) } result, _, err := formatHTTPResponseToAST(value, forceJSONDecode, forceYAMLDecode) if err != nil { - return nil, handleHTTPSendErr(c.bctx, err) + return nil, handleHTTPSendErr(c.bctx.Context, c.bctx.Location, err) } if cacheableCodes.Contains(ast.InternedTerm(value.StatusCode)) { @@ -1576,7 +1441,7 @@ func (c *intraQueryCache) InsertErrorIntoCache(err error) { func (c *intraQueryCache) ExecuteHTTPRequest() (*http.Response, error) { httpReq, httpClient, err := createHTTPRequest(c.bctx, c.req) if err != nil { - return nil, handleHTTPSendErr(c.bctx, err) + return nil, handleHTTPSendErr(c.bctx.Context, c.bctx.Location, err) } // Increment counter for actual network requests @@ -1585,15 +1450,15 @@ func (c *intraQueryCache) ExecuteHTTPRequest() (*http.Response, error) { return executeHTTPRequest(httpReq, httpClient, c.req) } -func useInterQueryCache(req ast.Object) (bool, *forceCacheParams, error) { +func useInterQueryCache(req ast.Object) (bool, forceCacheParams, error) { value, err := getBoolValFromReqObj(req, ast.InternedTerm("cache")) if err != nil { - return false, nil, err + return false, forceCacheParams{}, err } valueForceCache, err := getBoolValFromReqObj(req, ast.InternedTerm("force_cache")) if err != nil { - return false, nil, err + return false, forceCacheParams{}, err } if valueForceCache { @@ -1601,27 +1466,23 @@ func useInterQueryCache(req ast.Object) (bool, *forceCacheParams, error) { return true, forceCacheParams, err } - return value, nil, nil + return value, forceCacheParams{}, nil } -type forceCacheParams struct { - forceCacheDurationSeconds int32 -} - -func newForceCacheParams(req ast.Object) (*forceCacheParams, error) { +func newForceCacheParams(req ast.Object) (p forceCacheParams, err error) { term := req.Get(ast.InternedTerm("force_cache_duration_seconds")) if term == nil { - return nil, errors.New("'force_cache' set but 'force_cache_duration_seconds' parameter is missing") + return p, errors.New("'force_cache' set but 'force_cache_duration_seconds' parameter is missing") } forceCacheDurationSeconds := term.String() value, err := strconv.ParseInt(forceCacheDurationSeconds, 10, 32) if err != nil { - return nil, err + return p, err } - return &forceCacheParams{forceCacheDurationSeconds: int32(value)}, nil + return forceCacheParams{forceDurationSeconds: int32(value)}, nil } func getRaiseErrorValue(req ast.Object) (bool, error) { @@ -1634,3 +1495,7 @@ func getRaiseErrorValue(req ast.Object) (bool, error) { } return bool(result), nil } + +func arrayFromStringSlice(slice []string) *ast.Term { + return ast.ArrayTerm(util.Map(slice, ast.InternedTerm)...) +} diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/json.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/json.go index aeccac825f..53989541b3 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/json.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/json.go @@ -11,6 +11,7 @@ import ( "github.com/open-policy-agent/opa/v1/ast" "github.com/open-policy-agent/opa/v1/topdown/builtins" + "github.com/open-policy-agent/opa/v1/util" "github.com/open-policy-agent/opa/internal/edittree" ) @@ -150,22 +151,15 @@ func getJSONPaths(operand ast.Value) (paths []ast.Ref, err error) { paths = append(paths, filter) } case ast.Set: - paths = make([]ast.Ref, 0, v.Len()) - for _, item := range v.Slice() { - filter, err := parsePath(item) - if err != nil { - return nil, err - } - paths = append(paths, filter) - } + paths, err = util.TryMap(v.Slice(), parsePath) default: return nil, builtins.NewOperandTypeErr(2, v, "set", "array") } - return paths, nil + return paths, err } -// parsePath parses a JSON pointer path or array of path segments into an ast.Ref. +// parsePath parsese a JSON pointer path or array of path segments into an ast.Ref. func parsePath(path *ast.Term) (ast.Ref, error) { // paths can either be a `/` separated json path or // an array or set of values @@ -378,9 +372,7 @@ func builtinJSONPatch(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Ter } func init() { - for _, key := range []string{"op", "path", "from", "value", "add", "remove", "replace", "move", "copy", "test"} { - ast.InternStringTerm(key) - } + ast.InternStringTerm("op", "path", "from", "value", "add", "remove", "replace", "move", "copy", "test") RegisterBuiltinFunc(ast.JSONFilter.Name, builtinJSONFilter) RegisterBuiltinFunc(ast.JSONRemove.Name, builtinJSONRemove) diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/jsonschema.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/jsonschema.go index 0236e5be09..d6fa329473 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/jsonschema.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/jsonschema.go @@ -54,15 +54,26 @@ func newResultTerm(valid bool, data *ast.Term) *ast.Term { // type-checking path, where pattern validation is disabled to tolerate // schemas containing ECMA-262 regex features that Go's RE2 dialect can't // compile. -func newPatternValidatingSchemaLoader() *gojsonschema.SchemaLoader { +// +// Remote reference fetching is restricted to the hosts in the caller's +// allow_net capability. Schemas reaching these built-ins come from the policy +// or, worse, from input, so an unrestricted loader would let a `$ref` drive +// outbound requests from wherever OPA happens to be deployed. The query's +// context comes along so that those fetches are abandoned when evaluation is +// cancelled. +func newPatternValidatingSchemaLoader(bctx BuiltinContext) *gojsonschema.SchemaLoader { sl := gojsonschema.NewSchemaLoader() sl.ValidatePatterns = true + sl.Context = bctx.Context + if bctx.Capabilities != nil { + sl.AllowNet = bctx.Capabilities.AllowNet + } return sl } // builtinJSONSchemaVerify accepts 1 argument which can be string or object and checks if it is valid JSON schema. // Returns array [false, ] with error string at index 1, or [true, ""] with empty string at index 1 otherwise. -func builtinJSONSchemaVerify(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { +func builtinJSONSchemaVerify(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { // Take first argument and make JSON Loader from it. loader, err := astValueToJSONSchemaLoader(operands[0].Value) if err != nil { @@ -70,7 +81,7 @@ func builtinJSONSchemaVerify(_ BuiltinContext, operands []*ast.Term, iter func(* } // Check that schema is correct and parses without errors. - if _, err = newPatternValidatingSchemaLoader().Compile(loader); err != nil { + if _, err = newPatternValidatingSchemaLoader(bctx).Compile(loader); err != nil { return iter(newResultTerm(false, ast.StringTerm("jsonschema: "+err.Error()))) } @@ -80,6 +91,11 @@ func builtinJSONSchemaVerify(_ BuiltinContext, operands []*ast.Term, iter func(* // builtinJSONMatchSchema accepts 2 arguments both can be string or object and verifies if the document matches the JSON schema. // Returns an array where first element is a boolean indicating a successful match, and the second is an array of errors that is empty on success and populated on failure. // In case of internal error returns empty array. +// +// Cached schemas are keyed on the schema value alone. A compiled schema has +// already resolved its remote references, so a cache shared between callers +// with differing allow_net would leak across them. That needs a deliberately +// shared cache, an assumption http.send makes as well. func builtinJSONMatchSchema(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { var schema *gojsonschema.Schema @@ -106,7 +122,7 @@ func builtinJSONMatchSchema(bctx BuiltinContext, operands []*ast.Term, iter func return err } - schema, err = newPatternValidatingSchemaLoader().Compile(schemaLoader) + schema, err = newPatternValidatingSchemaLoader(bctx).Compile(schemaLoader) if err != nil { return err } diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/net.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/net.go index 6caa068b47..2245e06948 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/net.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/net.go @@ -24,7 +24,7 @@ func builtinLookupIPAddr(bctx BuiltinContext, operands []*ast.Term, iter func(*a } name := string(a) - err = verifyHost(bctx, name) + err = verifyHost(bctx.Capabilities, name) if err != nil { return err } diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/parse_bytes.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/parse_bytes.go index f912d276c2..73e2d940eb 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/parse_bytes.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/parse_bytes.go @@ -37,7 +37,7 @@ func parseNumBytesError(msg string) error { } func errBytesUnitNotRecognized(unit string) error { - return parseNumBytesError(fmt.Sprintf("byte unit %s not recognized", unit)) + return parseNumBytesError("byte unit " + unit + " not recognized") } var ( @@ -116,9 +116,7 @@ func builtinNumBytes(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term // Makes the string lower case and removes quotation marks func formatString(s ast.String) string { - str := string(s) - lower := strings.ToLower(str) - return strings.ReplaceAll(lower, "\"", "") + return strings.ReplaceAll(strings.ToLower(string(s)), "\"", "") } // Splits the string into a number string à la "10" or "10.2" and a unit diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/providers.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/providers.go index 29d721e4b2..511797ac8e 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/providers.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/providers.go @@ -203,11 +203,7 @@ func builtinAWSSigV4SignReq(_ BuiltinContext, operands []*ast.Term, iter func(*a } func init() { - for _, key := range []string{ - "aws_service", "aws_access_key", "aws_secret_access_key", "aws_region", "disable_payload_signing", - } { - ast.InternStringTerm(key) - } + ast.InternStringTerm("aws_service", "aws_access_key", "aws_secret_access_key", "aws_region", "disable_payload_signing") awsRequiredConfigKeyNames = ast.NewSet( ast.InternedTerm("aws_service"), diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/query.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/query.go index 971d62b33b..399b7f0a5b 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/query.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/query.go @@ -1,10 +1,11 @@ package topdown import ( + "cmp" "context" "crypto/rand" "io" - "sort" + "slices" "time" "github.com/open-policy-agent/opa/v1/ast" @@ -16,6 +17,7 @@ import ( "github.com/open-policy-agent/opa/v1/topdown/copypropagation" "github.com/open-policy-agent/opa/v1/topdown/print" "github.com/open-policy-agent/opa/v1/tracing" + "github.com/open-policy-agent/opa/v1/util" ) // QueryResultSet represents a collection of results returned by a query. @@ -56,6 +58,7 @@ type Query struct { interQueryBuiltinValueCache cache.InterQueryValueCache ndBuiltinCache builtins.NDBCache strictBuiltinErrors bool + stackTraces bool builtinErrorList *[]Error strictObjects bool roundTripper CustomizeRoundTripper @@ -271,6 +274,21 @@ func (q *Query) WithStrictBuiltinErrors(yes bool) *Query { return q } +// WithStackTraces tells the evaluator to record the stack of queries being +// evaluated when an error occurred on the returned *Error. The stack is exposed +// as Error.StackTrace and left out of the error message, so callers render it +// themselves. +// +// Off by default because capture is not free: each *Error costs a walk of the +// parent chain and a frame per query on it, resolved against the bindings in +// scope, which on a query collecting one built-in error per row runs from +60% +// to +203% in time (see BenchmarkStackTraceCollectedBuiltinErrors). OPA's own +// CLI and server accept that cost and turn it on. +func (q *Query) WithStackTraces(yes bool) *Query { + q.stackTraces = yes + return q +} + // WithBuiltinErrorList supplies a pointer to an Error slice to store built-in function errors // encountered during evaluation. This error slice can be inspected after evaluation to determine // which built-in function errors occurred. @@ -281,9 +299,7 @@ func (q *Query) WithBuiltinErrorList(list *[]Error) *Query { // WithResolver configures an external resolver to use for the given ref. func (q *Query) WithResolver(ref ast.Ref, r resolver.Resolver) *Query { - if q.external == nil { - q.external = newResolverTrie() - } + q.external = util.Or(q.external, newResolverTrie) q.external.Put(ref, r) return q } @@ -366,9 +382,7 @@ func (q *Query) WithEvaluatedRuleTracker(t *EvaluatedRuleTracker) *Query { // evaluation may produce additional support modules that should be used in // conjunction with the partially evaluated queries. func (q *Query) PartialRun(ctx context.Context) (partials []ast.Body, support []*ast.Module, err error) { - if q.partialNamespace == "" { - q.partialNamespace = "partial" // lazily initialize partial namespace - } + q.partialNamespace = cmp.Or(q.partialNamespace, "partial") if q.evaluated != nil && q.compiler != nil { q.evaluated.WithAnnotationSet(q.compiler.GetAnnotationSet()) } @@ -378,26 +392,10 @@ func (q *Query) PartialRun(ctx context.Context) (partials []ast.Body, support [] if q.time.IsZero() { q.time = time.Now() } - if q.metrics == nil { - q.metrics = metrics.New() - } + q.metrics = util.Or(q.metrics, metrics.New) f := &queryIDFactory{} - b := newBindings(0, q.instr) - - var vc VirtualCache - if q.virtualCache != nil { - vc = q.virtualCache - } else { - vc = NewVirtualCache() - } - - var bc BaseCache - if q.baseCache != nil { - bc = q.baseCache - } else { - bc = newBaseCache() - } + b := newBindings(q.instr) e := &eval{ ctx: ctx, @@ -412,12 +410,13 @@ func (q *Query) PartialRun(ctx context.Context) (partials []ast.Body, support [] bindings: b, compiler: q.compiler, store: q.store, - baseCache: bc, + baseCache: util.Or(q.baseCache, newBaseCache), txn: q.txn, input: q.input, external: q.external, tracers: q.tracers, traceEnabled: len(q.tracers) > 0, + stackCapture: q.newStackCapture(), plugTraceVars: q.plugTraceVars, instr: q.instr, builtins: q.builtins, @@ -425,7 +424,7 @@ func (q *Query) PartialRun(ctx context.Context) (partials []ast.Body, support [] interQueryBuiltinCache: q.interQueryBuiltinCache, interQueryBuiltinValueCache: q.interQueryBuiltinValueCache, ndBuiltinCache: q.ndBuiltinCache, - virtualCache: vc, + virtualCache: util.Or(q.virtualCache, NewVirtualCache), saveSet: newSaveSet(q.unknowns, b, q.instr), saveStack: newSaveStack(), saveSupport: newSaveSupport(), @@ -465,40 +464,37 @@ func (q *Query) PartialRun(ctx context.Context) (partials []ast.Body, support [] } } - ast.WalkVars(q.query, func(x ast.Var) bool { - if !x.IsGenerated() { - livevars.Add(x) - } - return false - }) + // iterate expressions to avoid Body -> any boxing in WalkVars argument + for _, expr := range q.query { + ast.WalkVars(expr, func(x ast.Var) bool { + if !x.IsGenerated() { + livevars.Add(x) + } + return false + }) + } p := copypropagation.New(livevars).WithCompiler(q.compiler) err = e.Run(func(e *eval) error { - // Build output from saved expressions. - body := ast.NewBody() - - for _, elem := range e.saveStack.Peek() { - body.Append(elem.Plug(e.bindings)) + saved := e.saveStack.Peek() + exprs := make([]*ast.Expr, 0, len(saved)+e.bindings.size()) + for _, elem := range saved { + exprs = append(exprs, elem.Plug(e.bindings)) } // Include bindings as exprs so that when caller evals the result, they // can obtain values for the vars in their query. - bindingExprs := []*ast.Expr{} _ = e.bindings.Iter(e.bindings, func(a, b *ast.Term) error { - bindingExprs = append(bindingExprs, ast.Equality.Expr(a, b)) + exprs = append(exprs, ast.Equality.Expr(a, b)) return nil }) // cannot return error // Sort binding expressions so that results are deterministic. - sort.Slice(bindingExprs, func(i, j int) bool { - return bindingExprs[i].Compare(bindingExprs[j]) < 0 - }) + slices.SortFunc(exprs[len(saved):], (*ast.Expr).Compare) - for i := range bindingExprs { - body.Append(bindingExprs[i]) - } + body := ast.NewBody(exprs...) // Skip this rule body if it fails to type-check. // Type-checking failure means the rule body will never succeed. @@ -514,8 +510,6 @@ func (q *Query) PartialRun(ctx context.Context) (partials []ast.Body, support [] return nil }) - support = e.saveSupport.List() - if len(e.builtinErrors.errs) > 0 { if q.strictBuiltinErrors { err = e.builtinErrors.errs[0] @@ -537,14 +531,13 @@ func (q *Query) PartialRun(ctx context.Context) (partials []ast.Body, support [] } } + support = e.saveSupport.List() + for i, m := range support { if regoVersion := q.compiler.DefaultRegoVersion(); regoVersion != ast.RegoUndefined { ast.SetModuleRegoVersion(m, q.compiler.DefaultRegoVersion()) } - - sort.Slice(support[i].Rules, func(j, k int) bool { - return support[i].Rules[j].Compare(support[i].Rules[k]) < 0 - }) + slices.SortFunc(support[i].Rules, (*ast.Rule).Compare) } return partials, support, err @@ -565,42 +558,20 @@ func (q *Query) Run(ctx context.Context) (QueryResultSet, error) { func (q *Query) Iter(ctx context.Context, iter func(QueryResult) error) error { // Query evaluation must not be allowed if the compiler has errors and is in an undefined, possibly inconsistent state if q.compiler != nil && len(q.compiler.Errors) > 0 { - return &Error{ - Code: InternalErr, - Message: "compiler has errors", - } + return &Error{Code: InternalErr, Message: "compiler has errors"} } - if q.evaluated != nil && q.compiler != nil { q.evaluated.WithAnnotationSet(q.compiler.GetAnnotationSet()) } - if q.seed == nil { q.seed = rand.Reader } if q.time.IsZero() { q.time = time.Now() } - if q.metrics == nil { - q.metrics = metrics.New() - } + q.metrics = util.Or(q.metrics, metrics.New) f := &queryIDFactory{} - - var vc VirtualCache - if q.virtualCache != nil { - vc = q.virtualCache - } else { - vc = NewVirtualCache() - } - - var bc BaseCache - if q.baseCache != nil { - bc = q.baseCache - } else { - bc = newBaseCache() - } - e := &eval{ ctx: ctx, metrics: q.metrics, @@ -611,15 +582,16 @@ func (q *Query) Iter(ctx context.Context, iter func(QueryResult) error) error { queryCompiler: q.queryCompiler, queryIDFact: f, queryID: f.Next(), - bindings: newBindings(0, q.instr), + bindings: newBindings(q.instr), compiler: q.compiler, store: q.store, - baseCache: bc, + baseCache: util.Or(q.baseCache, newBaseCache), txn: q.txn, input: q.input, external: q.external, tracers: q.tracers, traceEnabled: len(q.tracers) > 0, + stackCapture: q.newStackCapture(), plugTraceVars: q.plugTraceVars, instr: q.instr, builtins: q.builtins, @@ -627,7 +599,7 @@ func (q *Query) Iter(ctx context.Context, iter func(QueryResult) error) error { interQueryBuiltinCache: q.interQueryBuiltinCache, interQueryBuiltinValueCache: q.interQueryBuiltinValueCache, ndBuiltinCache: q.ndBuiltinCache, - virtualCache: vc, + virtualCache: util.Or(q.virtualCache, NewVirtualCache), genvarprefix: q.genvarprefix, runtime: q.runtime, indexing: q.indexing, @@ -647,7 +619,7 @@ func (q *Query) Iter(ctx context.Context, iter func(QueryResult) error) error { e.caller = e q.metrics.Timer(metrics.RegoQueryEval).Start() err := e.Run(func(e *eval) error { - qr := QueryResult{} + qr := make(QueryResult, e.bindings.size()) _ = e.bindings.Iter(nil, func(k, v *ast.Term) error { qr[k.Value.(ast.Var)] = v return nil diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/reachable.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/reachable.go index 683c31c6c6..3a3271e39e 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/reachable.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/reachable.go @@ -40,10 +40,12 @@ func builtinReachable(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Ter var queue []*ast.Term switch initial := operands[1].Value.(type) { - case *ast.Array, ast.Set: + case *ast.Array: foreachVertex(ast.NewTerm(initial), func(t *ast.Term) { queue = append(queue, t) }) + case ast.Set: + queue = append(queue, initial.Slice()...) default: return builtins.NewOperandTypeErr(2, initial, "{array, set}") } @@ -113,10 +115,12 @@ func builtinReachablePaths(_ BuiltinContext, operands []*ast.Term, iter func(*as // initialised to the initial set of nodes we start out with. var queue []*ast.Term switch initial := operands[1].Value.(type) { - case *ast.Array, ast.Set: + case *ast.Array: foreachVertex(ast.NewTerm(initial), func(t *ast.Term) { queue = append(queue, t) }) + case ast.Set: + queue = append(queue, initial.Slice()...) default: return builtins.NewOperandTypeErr(2, initial, "{array, set}") } diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/regex.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/regex.go index 0313452033..6c6de304dc 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/regex.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/regex.go @@ -26,6 +26,41 @@ var ( regexpCache = make(map[string]*regexp.Regexp) ) +func regexpCacheGet(pat string) (*regexp.Regexp, error) { + regexpCacheLock.RLock() + v, ok := regexpCache[pat] + regexpCacheLock.RUnlock() + if ok { + return v, nil + } + + // cache miss! + re, err := regexp.Compile(pat) + if err != nil { + return nil, err + } + + regexpCacheLock.Lock() + + // Ensure the cache is below the max size. + for len(regexpCache) >= regexCacheMaxSize { + // Since every caller inserts at most one item, we expect this + // loop to run for at most one iteration. + for k := range regexpCache { + // Go map iteration is semi-random, so this deletes a + // more or less arbitrary key. + delete(regexpCache, k) + break + } + } + + regexpCache[pat] = re + + regexpCacheLock.Unlock() + return re, nil + +} + func builtinRegexIsValid(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { if s, err := builtins.StringOperand(operands[0].Value, 1); err == nil { if _, err = syntax.Parse(string(s), syntax.Perl); err == nil { @@ -129,46 +164,16 @@ func getRegexp(bctx BuiltinContext, pat string) (*regexp.Regexp, error) { return re, nil } - regexpCacheLock.RLock() - re, ok := regexpCache[pat] - numCached := len(regexpCache) - regexpCacheLock.RUnlock() - if !ok { - var err error - re, err = regexp.Compile(pat) - if err != nil { - return nil, err - } - - regexpCacheLock.Lock() - if numCached >= regexCacheMaxSize { - // Delete a (semi-)random key to make room for the new one. - for k := range regexpCache { - delete(regexpCache, k) - break - } - } - regexpCache[pat] = re - regexpCacheLock.Unlock() - } - return re, nil + return regexpCacheGet(pat) } func getRegexpTemplate(pat string, delimStart, delimEnd byte) (*regexp.Regexp, error) { - regexpCacheLock.RLock() - re, ok := regexpCache[pat] - regexpCacheLock.RUnlock() - if !ok { - var err error - re, err = compileRegexTemplate(pat, delimStart, delimEnd) - if err != nil { - return nil, err - } - regexpCacheLock.Lock() - regexpCache[pat] = re - regexpCacheLock.Unlock() + gen, err := generateRegexTemplate(pat, delimStart, delimEnd) + if err != nil { + return nil, err } - return re, nil + + return regexpCacheGet(gen) } func builtinGlobsMatch(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/regex_template.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/regex_template.go index a1d946fd59..f8cc550876 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/regex_template.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/regex_template.go @@ -67,25 +67,16 @@ func delimiterIndices(s string, delimiterStart, delimiterEnd byte) ([]int, error return idxs, nil } -// compileRegexTemplate parses a template and returns a Regexp. -// -// You can define your own delimiters. It is e.g. common to use curly braces {} but I recommend using characters -// which have no special meaning in Regex, e.g.: <, > -// -// reg, err := compiler.CompileRegex("foo:bar.baz:<[0-9]{2,10}>", '<', '>') -// // if err != nil ... -// reg.MatchString("foo:bar.baz:123") -func compileRegexTemplate(tpl string, delimiterStart, delimiterEnd byte) (*regexp.Regexp, error) { +// generateRegexTemplate creates and returns the pattern string compiled by +// compileRegexTemplate(). +func generateRegexTemplate(tpl string, delimiterStart, delimiterEnd byte) (string, error) { // Check if it is well-formed. idxs, errBraces := delimiterIndices(tpl, delimiterStart, delimiterEnd) if errBraces != nil { - return nil, errBraces + return "", errBraces } varsR := make([]*regexp.Regexp, len(idxs)/2) - pattern := bytes.NewBufferString("") - - // WriteByte's error value is always nil for bytes.Buffer, no need to check it. - pattern.WriteByte('^') + pattern := bytes.NewBufferString("^") var end int var err error @@ -99,7 +90,7 @@ func compileRegexTemplate(tpl string, delimiterStart, delimiterEnd byte) (*regex fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt) varsR[varIdx], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) if err != nil { - return nil, err + return "", err } } @@ -112,11 +103,23 @@ func compileRegexTemplate(tpl string, delimiterStart, delimiterEnd byte) (*regex // WriteByte's error value is always nil for bytes.Buffer, no need to check it. pattern.WriteByte('$') - // Compile full regexp. - reg, errCompile := regexp.Compile(pattern.String()) - if errCompile != nil { - return nil, errCompile + return pattern.String(), nil +} + +// compileRegexTemplate parses a template and returns a Regexp. +// +// You can define your own delimiters. It is e.g. common to use curly braces {} but I recommend using characters +// which have no special meaning in Regex, e.g.: <, > +// +// reg, err := compiler.CompileRegex("foo:bar.baz:<[0-9]{2,10}>", '<', '>') +// // if err != nil ... +// reg.MatchString("foo:bar.baz:123") +func compileRegexTemplate(tpl string, delimiterStart, delimiterEnd byte) (*regexp.Regexp, error) { + pattern, err := generateRegexTemplate(tpl, delimiterStart, delimiterEnd) + if err != nil { + return nil, err } - return reg, nil + // Compile full regexp. + return regexp.Compile(pattern) } diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/save.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/save.go index a1c0fdd65a..5d23623392 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/save.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/save.go @@ -91,6 +91,76 @@ func (ss *saveSet) containsrec(t *ast.Term, b *bindings) bool { return found } +// ContainsOverlapping is a conservative Contains: it also reports terms that +// may refer to an unknown once resolved, like input[k] when input.x is unknown. +// Callers saving whole expressions need it; evalTerm saves per branch instead. +func (ss *saveSet) ContainsOverlapping(t *ast.Term, b *bindings) bool { + if ss == nil { + return false + } + ss.instr.startTimer(partialOpSaveSetContains) + defer ss.instr.stopTimer(partialOpSaveSetContains) + + other, ok := t.Value.(ast.Ref) + if !ok { + return ss.contains(t, b) + } + + for el := ss.l.Back(); el != nil; el = el.Prev() { + elem := el.Value.(*saveSetElem) + for _, ref := range elem.refs { + if refsMayOverlap(ref, other) { + return true + } + } + if elem.containsVar(other[0], b) { + return true + } + } + return false +} + +// Covers reports whether an unknown sits at or above path, i.e. the whole +// sub-document is unknown. Directional half of Contains: input.z.a being +// unknown leaves input.z.b known, so a walk through input.z keeps descending. +func (ss *saveSet) Covers(path ast.Ref) bool { + if ss == nil { + return false + } + for el := ss.l.Back(); el != nil; el = el.Prev() { + if slices.ContainsFunc(el.Value.(*saveSetElem).refs, path.HasPrefix) { + return true + } + } + return false +} + +// Keys returns the ground keys unknowns contribute directly below prefix. With +// input.x unknown and input = {"y": 2}, iterating input[k] must still produce a +// branch for k = "x". A non-ground prefix matches no unknown, hence no keys. +func (ss *saveSet) Keys(prefix ast.Ref) []*ast.Term { + if ss == nil { + return nil + } + + var keys []*ast.Term + for el := ss.l.Back(); el != nil; el = el.Prev() { + for _, ref := range el.Value.(*saveSetElem).refs { + if len(ref) <= len(prefix) || !ref.HasPrefix(prefix) { + continue + } + k := ref[len(prefix)] + if !k.IsGround() { + continue + } + if !slices.ContainsFunc(keys, k.Equal) { + keys = append(keys, k) + } + } + } + return keys +} + func (ss *saveSet) Vars(caller *bindings) ast.VarSet { result := ast.NewVarSet() for x := ss.l.Front(); x != nil; x = x.Next() { @@ -158,6 +228,31 @@ func (sse *saveSetElem) Contains(t *ast.Term, b *bindings) bool { return false } +// refsMayOverlap returns true if ref (an unknown) and other could refer to the +// same document. Non-ground positions act as wildcards: input[k] may hit +// input.x. Heads are exempt -- ref[0] names a root doc, not an unbound position. +func refsMayOverlap(ref, other ast.Ref) bool { + if len(ref) == 0 || len(other) == 0 { + return true + } + + if !ref[0].Equal(other[0]) { + return false + } + + for i, n := 1, min(len(ref), len(other)); i < n; i++ { + // Two ground positions that differ are the only thing ruling an overlap + // out; a non-ground position on either side acts as a wildcard. `other` + // is the side carrying variables, so test its groundness first. + x, y := ref[i], other[i] + if !x.Equal(y) && y.IsGround() && x.IsGround() { + return false + } + } + + return true +} + func (sse *saveSetElem) String() string { return fmt.Sprintf("(refs: %v, vars: %v, b: %v)", sse.refs, sse.vars, sse.b) } @@ -275,11 +370,7 @@ func newSaveSupport() *saveSupport { } func (s *saveSupport) List() []*ast.Module { - result := make([]*ast.Module, 0, len(s.modules)) - for _, module := range s.modules { - result = append(result, module) - } - return result + return util.Values(s.modules) } func (s *saveSupport) Exists(path ast.Ref) bool { @@ -378,18 +469,22 @@ func saveRequired(compilerTree *ast.TreeNode, extStack *externalTreeStack, ic *i found = true } case ast.Ref: - if ss.Contains(node, b) { + if ss.ContainsOverlapping(node, b) { found = true } else if ic.Disabled(v.ConstantPrefix(), icIgnoreInternal) { found = true } else { - rules := getRulesDynamic(compilerTree, extStack, v, ast.RulesOptions{IncludeHiddenModules: false}) - for _, rule := range rules { - if saveRequired(compilerTree, extStack, ic, icIgnoreInternal, ss, b, rule, true) { - found = true - break - } + // Only terms from the call site can be plugged: once traversal + // recurses into a rule, that rule's variables belong to another + // binding list and could resolve to unrelated values in b. + lookup := v + if !rec { + lookup = plugRefForRuleLookup(v, b) } + found = anyRuleDynamic(compilerTree, extStack, lookup, ast.RulesOptions{IncludeHiddenModules: false}, + func(rule *ast.Rule) bool { + return saveRequired(compilerTree, extStack, ic, icIgnoreInternal, ss, b, rule, true) + }) } } } @@ -401,10 +496,41 @@ func saveRequired(compilerTree *ast.TreeNode, extStack *externalTreeStack, ic *i return found } -// getRulesDynamic looks up rules in both the compiler tree and external sources. -func getRulesDynamic(compilerTree *ast.TreeNode, extStack *externalTreeStack, ref ast.Ref, opts ast.RulesOptions) []*ast.Rule { - var rules []*ast.Rule +// plugRefForRuleLookup replaces variables in ref that are bound to a scalar with +// that value, narrowing rule lookup to the sub-tree that will actually be +// evaluated. Positions left as-is, because they are unbound or bound to a +// composite, fan out over all children as before. +func plugRefForRuleLookup(ref ast.Ref, b *bindings) ast.Ref { + if b == nil { + return ref + } + cpy := ref + + for i := 1; i < len(ref); i++ { + if _, ok := ref[i].Value.(ast.Var); !ok { + continue + } + plugged := b.Plug(ref[i]) + if !ast.IsScalar(plugged.Value) { + continue + } + if len(cpy) == len(ref) && &cpy[0] == &ref[0] { + cpy = make(ast.Ref, len(ref)) + copy(cpy, ref) + } + cpy[i] = plugged + } + + return cpy +} + +// anyRuleDynamic invokes f for the rules matching ref in the external trees and +// the compiler tree, stopping as soon as f returns true. Rules are streamed to f +// rather than collected so that callers only interested in whether *some* rule +// satisfies a predicate don't pay for walking the whole matching sub-tree, which +// for refs with non-constant elements can mean every rule loaded. +func anyRuleDynamic(compilerTree *ast.TreeNode, extStack *externalTreeStack, ref ast.Ref, opts ast.RulesOptions, f func(*ast.Rule) bool) bool { // Check external trees if extStack != nil { for i := range extStack.entries { @@ -412,63 +538,75 @@ func getRulesDynamic(compilerTree *ast.TreeNode, extStack *externalTreeStack, re if entry.tree != nil && ref.HasPrefix(entry.ref) { // Navigate into the external tree using the remaining path remaining := ref[len(entry.ref):] - rules = append(rules, getRulesFromTree(entry.tree, remaining, opts)...) + if anyRuleFromTree(entry.tree, remaining, opts, f) { + return true + } } } } // Then check compiler tree - rules = append(rules, getRulesFromTree(compilerTree, ref, opts)...) - - return rules + return anyRuleFromTree(compilerTree, ref, opts, f) } -// getRulesFromTree walks a tree to find all rules matching the given ref. -func getRulesFromTree(node *ast.TreeNode, ref ast.Ref, opts ast.RulesOptions) []*ast.Rule { - set := map[*ast.Rule]struct{}{} - var walk func(*ast.TreeNode, int) - walk = func(nav *ast.TreeNode, i int) { +// anyRuleFromTree walks a tree to find rules matching the given ref, invoking f +// for each and stopping early if f returns true. +func anyRuleFromTree(node *ast.TreeNode, ref ast.Ref, opts ast.RulesOptions, f func(*ast.Rule) bool) bool { + var walk func(*ast.TreeNode, int) bool + walk = func(nav *ast.TreeNode, i int) bool { switch { case i >= len(ref): - nav.DepthFirst(func(descendant *ast.TreeNode) bool { - for _, rule := range descendant.Values { - set[rule] = struct{}{} - } - if opts.IncludeHiddenModules { - return false - } - return descendant.Hide - }) + // The rules on nav itself have already been passed to f by the caller, + // unless nav is where the walk started. + return anyRuleDescendant(nav, opts, f, i == 0) case i == 0 || ast.IsConstant(ref[i].Value): - if child := nav.Child(ref[i].Value); child != nil { - for _, rule := range child.Values { - set[rule] = struct{}{} - } - walk(child, i+1) - } else { - return + child := nav.Child(ref[i].Value) + if child == nil { + return false } + return anyRule(child.Values, f) || walk(child, i+1) default: for _, child := range nav.Children { if child.Hide && !opts.IncludeHiddenModules { continue } - for _, rule := range child.Values { - set[rule] = struct{}{} + if anyRule(child.Values, f) || walk(child, i+1) { + return true } - walk(child, i+1) } + return false } } - walk(node, 0) - rules := make([]*ast.Rule, 0, len(set)) - for rule := range set { - rules = append(rules, rule) + return walk(node, 0) +} + +// anyRuleDescendant invokes f for every rule in node's sub-tree, stopping early +// if f returns true. The rules on node itself are only visited if visitSelf is +// set. Hidden nodes are not descended into unless opts.IncludeHiddenModules is +// set. +func anyRuleDescendant(node *ast.TreeNode, opts ast.RulesOptions, f func(*ast.Rule) bool, visitSelf bool) bool { + if visitSelf && anyRule(node.Values, f) { + return true } - return rules + + if node.Hide && !opts.IncludeHiddenModules { + return false + } + + for _, child := range node.Children { + if anyRuleDescendant(child, opts, f, true) { + return true + } + } + + return false +} + +func anyRule(rules []*ast.Rule, f func(*ast.Rule) bool) bool { + return slices.ContainsFunc(rules, f) } func ignoreExprDuringPartial(expr *ast.Expr) bool { diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/sets.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/sets.go index 6ee467efc8..e92f5882b2 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/sets.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/sets.go @@ -11,7 +11,6 @@ import ( // Deprecated: deprecated in v0.4.2 in favour of minus/infix "-" operation. func builtinSetDiff(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { - s1, err := builtins.SetOperand(operands[0].Value, 1) if err != nil { return err @@ -27,7 +26,6 @@ func builtinSetDiff(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) // builtinSetIntersection returns the intersection of the given input sets func builtinSetIntersection(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { - inputSet, err := builtins.SetOperand(operands[0].Value, 1) if err != nil { return err @@ -40,8 +38,8 @@ func builtinSetIntersection(_ BuiltinContext, operands []*ast.Term, iter func(*a var result ast.Set - err = inputSet.Iter(func(x *ast.Term) error { - n, err := builtins.SetOperand(x.Value, 1) + for _, term := range inputSet.Slice() { + n, err := builtins.SetOperand(term.Value, 1) if err != nil { return err } @@ -51,10 +49,6 @@ func builtinSetIntersection(_ BuiltinContext, operands []*ast.Term, iter func(*a } else { result = result.Intersect(n) } - return nil - }) - if err != nil { - return err } return iter(ast.NewTerm(result)) } @@ -72,31 +66,22 @@ func builtinSetUnion(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term // First pass: count total elements for pre-allocation totalSize := 0 - err = inputSet.Iter(func(x *ast.Term) error { - item, err := builtins.SetOperand(x.Value, 1) + for _, term := range inputSet.Slice() { + item, err := builtins.SetOperand(term.Value, 1) if err != nil { return err } totalSize += item.Len() - return nil - }) - if err != nil { - return err } // Pre-allocate result set with estimated capacity - result := ast.NewSetWithCapacity(totalSize) - - err = inputSet.Iter(func(x *ast.Term) error { - item, _ := builtins.SetOperand(x.Value, 1) // error checked above - item.Foreach(result.Add) - return nil - }) - if err != nil { - return err + terms := make([]*ast.Term, 0, totalSize) + for _, term := range inputSet.Slice() { + item, _ := builtins.SetOperand(term.Value, 1) // error checked above + terms = append(terms, item.Slice()...) } - return iter(ast.NewTerm(result)) + return iter(ast.SetTerm(terms...)) } func init() { diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/stacktrace.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/stacktrace.go new file mode 100644 index 0000000000..cf019a9cfc --- /dev/null +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/stacktrace.go @@ -0,0 +1,336 @@ +// Copyright 2026 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package topdown + +import ( + "bytes" + "slices" + "strconv" + "strings" + + "github.com/open-policy-agent/opa/v1/ast" +) + +// maxStackFrameTextLength keeps frames with large literals in them readable. +const maxStackFrameTextLength = 80 + +// StackFrame is one query in a StackTrace. +type StackFrame struct { + // QueryID matches the QueryID of the trace events for the same query. + QueryID uint64 `json:"query_id"` + + // Location is the expression being evaluated, nil if the query has no + // location information. + Location *ast.Location `json:"location,omitempty"` + + // text is the source at Location with the values bound to its variables + // spliced in, empty when nothing was bound. Unexported to keep the policy + // source out of the marshaled frame; String reports it. + text string +} + +// String returns the frame's position and, when the query has source, the +// expression at it with the values its variables were bound to spliced in. +func (f StackFrame) String() string { + s := strings.Builder{} + f.writeTo(&s) + return s.String() +} + +func (f StackFrame) writeTo(s *strings.Builder) { + loc := f.Location + if loc == nil { + s.WriteString("") + return + } + + if loc.File != "" { + s.WriteString(loc.File) + s.WriteByte(':') + s.WriteString(strconv.Itoa(loc.Row)) + } else { + s.WriteString(strconv.Itoa(loc.Row)) + s.WriteByte(':') + s.WriteString(strconv.Itoa(loc.Col)) + } + + src := f.text + if src == "" && loc.Text != nil { + src = string(loc.Text) + } + + if text := frameText(src); text != "" { + s.WriteString(": ") + s.WriteString(text) + } +} + +// StackTrace is the stack of queries being evaluated when an error occurred, +// innermost first. +type StackTrace []StackFrame + +// String returns one indented frame per line. +func (st StackTrace) String() string { + s := strings.Builder{} + for i := range st { + if i > 0 { + s.WriteByte('\n') + } + s.WriteString(" ") + st[i].writeTo(&s) + } + return s.String() +} + +// frameText collapses src onto one line and truncates it. +func frameText(src string) string { + text := strings.Join(strings.Fields(src), " ") + + var runes int + for i := range text { + if runes == maxStackFrameTextLength { + return text[:i] + "..." + } + runes++ + } + + return text +} + +// stackTrace captures the evaluation stack, innermost first. Enclosing queries +// are still suspended on the call stack here, so their expression indices have +// not been unwound yet. +// +// The chain is walked twice to size the slice: append would cost an allocation +// per doubling, on every error a deep stack raises. +func (e *eval) stackTrace() StackTrace { + var depth int + for curr := e; curr != nil; curr = curr.parent { + depth++ + } + + st := make(StackTrace, 0, depth) + for curr := e; curr != nil; curr = curr.parent { + expr := curr.currentExpr() + if expr == nil { + st = append(st, StackFrame{QueryID: curr.queryID}) + continue + } + st = append(st, StackFrame{ + QueryID: curr.queryID, + Location: expr.Location, + text: curr.resolvedText(expr), + }) + } + return st +} + +// currentExpr returns the expression e is evaluating. Once the whole body has +// succeeded e.index sits one past the end, leaving nothing to point at, so the +// last expression is reported as the nearest position. +func (e *eval) currentExpr() *ast.Expr { + if len(e.query) == 0 { + return nil + } + return e.query[min(e.index, len(e.query)-1)] +} + +// resolvedText renders the source of expr with the values its variables are +// bound to spliced in, so a frame reads fn(1) where the policy wrote fn(x) - the +// argument a call failed on is usually the reason it failed. Returns "" when +// nothing was substituted and the source stands on its own. +// +// Bindings are unwound as evaluation backtracks, so this has to run while the +// error is being raised rather than when the frame is rendered. +func (e *eval) resolvedText(expr *ast.Expr) string { + loc := expr.Location + if loc == nil || len(loc.Text) == 0 { + return "" + } + + capture := e.stackCapture + capture.vars = appendReadVars(capture.vars[:0], expr) + subs := capture.subs[:0] + + for _, t := range capture.vars { + start, ok := sourceSpan(loc, t.Location) + if !ok { + continue + } + + // An unbound var plugs to itself, and a value too long to fit in a frame + // is better left as the name the policy gave it. + bound := e.bindings.Plug(t) + if bound == t || !bound.IsGround() || bound.StringLength() > maxStackFrameTextLength { + continue + } + + subs = append(subs, textSub{start: start, end: start + len(t.Location.Text), value: bound.String()}) + } + + capture.subs = subs + + if len(subs) == 0 { + return "" + } + + slices.SortFunc(subs, func(a, b textSub) int { return a.start - b.start }) + + s := strings.Builder{} + prev := 0 + for _, sub := range subs { + // A var reached twice, or one nested in the span of another, resolves to + // the same text the first one already wrote. + if sub.start < prev { + continue + } + s.Write(loc.Text[prev:sub.start]) + s.WriteString(sub.value) + prev = sub.end + } + s.Write(loc.Text[prev:]) + + return s.String() +} + +// textSub is a span of an expression's source to replace with a value. +type textSub struct { + start, end int + value string +} + +// stackTraceCapture is the state Query.WithStackTraces turns on. A non-nil one +// on an eval means capture is enabled, so the feature adds a single field to a +// struct that is copied for every query. +type stackTraceCapture struct { + // builtinErrors records whether collected built-in errors have a consumer. + // Without one query.go drops them, and a policy over messy data reaches that + // path for every row. + builtinErrors bool + + // Working space for resolvedText, shared by every eval of the query - + // evaluation is single threaded - rather than reallocated per stack. + vars []*ast.Term + subs []textSub +} + +// newStackCapture returns the capture state to share across q's evals, nil when +// stack traces are off and nothing will ask for it. +func (q *Query) newStackCapture() *stackTraceCapture { + if !q.stackTraces { + return nil + } + return &stackTraceCapture{builtinErrors: q.strictBuiltinErrors || q.builtinErrorList != nil} +} + +// appendReadVars appends every variable expr reads to dst. ast.WalkTerms would +// find the same ones, but it allocates a visitor and a closure per call and this +// runs once per frame of every captured stack. +// +// Positions that declare a variable rather than read one are skipped, so an +// every keeps its own name in the head instead of reading `every 1 in [1]`. So +// are comprehension bodies: their variables belong to a child query's bindings, +// which the frame cannot resolve. +func appendReadVars(dst []*ast.Term, expr *ast.Expr) []*ast.Term { + switch terms := expr.Terms.(type) { + case *ast.Term: + dst = appendVarsInTerm(dst, terms) + case []*ast.Term: + // terms[0] is the operator, a ref to a built-in or to a rule. + for _, t := range terms[1:] { + dst = appendVarsInTerm(dst, t) + } + case *ast.Every: + // Key and Value are the every's to declare, but its body reads them. + dst = appendVarsInTerm(dst, terms.Domain) + for _, e := range terms.Body { + dst = appendReadVars(dst, e) + } + } + + for _, w := range expr.With { + dst = appendVarsInTerm(dst, w.Value) + } + + return dst +} + +func appendVarsInTerm(dst []*ast.Term, t *ast.Term) []*ast.Term { + // Composites track their groundness, so this prunes most of the walk for + // the cost of a field read. + if t.IsGround() { + return dst + } + + switch v := t.Value.(type) { + case ast.Var: + dst = append(dst, t) + case ast.Ref: + for _, t := range v { + dst = appendVarsInTerm(dst, t) + } + case ast.Call: + for _, t := range v[1:] { + dst = appendVarsInTerm(dst, t) + } + case *ast.Array: + for i := range v.Len() { + dst = appendVarsInTerm(dst, v.Elem(i)) + } + case ast.Set: + // Slice and Keys allocate where Foreach wouldn't, but a closure over dst + // would put it on the heap for every call, ground terms included. + for _, t := range v.Slice() { + dst = appendVarsInTerm(dst, t) + } + case ast.Object: + for _, k := range v.Keys() { + dst = appendVarsInTerm(dst, k) + dst = appendVarsInTerm(dst, v.Get(k)) + } + } + + return dst +} + +// sourceSpan returns where term's source sits within expr's, and false when the +// two don't line up. Terms shared across a policy - the data root document, for +// one - carry the location of wherever they were first parsed, so matching the +// offset alone isn't enough. +func sourceSpan(expr, term *ast.Location) (int, bool) { + if term == nil || len(term.Text) == 0 || term.File != expr.File { + return 0, false + } + + start := term.Offset - expr.Offset + end := start + len(term.Text) + if start < 0 || end > len(expr.Text) || !bytes.Equal(expr.Text[start:end], term.Text) { + return 0, false + } + + return start, true +} + +// withStackTrace records the evaluation stack on err, if enabled. Kept small +// enough to inline, so the disabled case costs only a branch. +func (e *eval) withStackTrace(err error) error { + if err == nil || e.stackCapture == nil { + return err + } + return e.attachStackTrace(err) +} + +// attachStackTrace returns err carrying the evaluation stack, or unchanged if it +// already has one - the innermost stack wins. err is copied, not annotated in +// place: shared errors like errInScopeWithStmt would otherwise race, and leak +// one query's stack into every later occurrence. +func (e *eval) attachStackTrace(err error) error { + if tdErr, ok := err.(*Error); ok && tdErr.StackTrace == nil { + cpy := *tdErr + cpy.StackTrace = e.stackTrace() + return &cpy + } + return err +} diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/strings.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/strings.go index 4ffd307c78..e26ec2c39c 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/strings.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/strings.go @@ -20,6 +20,11 @@ import ( "github.com/open-policy-agent/opa/v1/util" ) +var ( + trueAny any = true + errEmptySearchCharacter = errors.New("empty search character") +) + func builtinAnyPrefixMatch(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { a, b := operands[0].Value, operands[1].Value @@ -66,10 +71,7 @@ func builtinAnySuffixMatch(_ BuiltinContext, operands []*ast.Term, iter func(*as if err != nil { return err } - strsReversed = make([]string, len(strs)) - for i := range strs { - strsReversed[i] = reverseString(strs[i]) - } + strsReversed = util.Map(strs, reverseString) default: return builtins.NewOperandTypeErr(1, a, "string", "set", "array") } @@ -83,10 +85,7 @@ func builtinAnySuffixMatch(_ BuiltinContext, operands []*ast.Term, iter func(*as if err != nil { return err } - suffixesReversed = make([]string, len(suffixes)) - for i := range suffixes { - suffixesReversed[i] = reverseString(suffixes[i]) - } + suffixesReversed = util.Map(suffixes, reverseString) default: return builtins.NewOperandTypeErr(2, b, "string", "set", "array") } @@ -102,13 +101,17 @@ func anyStartsWithAny(strs []string, prefixes []string) bool { return strings.HasPrefix(strs[0], prefixes[0]) } + // The trie is local, and only ever inserted into and searched, so it's safe + // to hand it byte slices aliasing the operand strings' memory. Note that + // patricia's compact() writes through the key slices it retains, so Delete + // and DeleteSubtree must not be used here: they'd corrupt those strings. trie := patricia.NewTrie() for i := range strs { - trie.Insert([]byte(strs[i]), true) + trie.Insert(util.StringToByteSlice(strs[i]), trueAny) } for i := range prefixes { - if trie.MatchSubtree([]byte(prefixes[i])) { + if trie.MatchSubtree(util.StringToByteSlice(prefixes[i])) { return true } } @@ -312,12 +315,12 @@ func builtinIndexOf(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) return err } if len(string(search)) == 0 { - return errors.New("empty search character") + return errEmptySearchCharacter } if isASCII(string(base)) && isASCII(string(search)) { - // this is a false positive in the indexAlloc rule that thinks - // we're converting byte arrays to strings + // this is a false positive in the indexAlloc rule that thinks we're converting + // byte arrays to strings. still a false positive as of 2026-08-19. //nolint:gocritic return iter(ast.InternedTerm(strings.Index(string(base), string(search)))) } @@ -350,7 +353,7 @@ func builtinIndexOfN(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term return err } if len(string(search)) == 0 { - return errors.New("empty search character") + return errEmptySearchCharacter } baseRunes := []rune(string(base)) @@ -372,7 +375,6 @@ func builtinIndexOfN(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term } func builtinSubstring(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { - base, err := builtins.StringOperand(operands[0].Value, 1) if err != nil { return err @@ -583,24 +585,14 @@ func builtinSplitN(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) limit = -1 } parts := strings.SplitN(text, delim, limit) - end := n - if end > len(parts) { - end = len(parts) - } - result = make([]*ast.Term, end) + result = make([]*ast.Term, min(n, len(parts))) for i := range result { result[i] = ast.InternedTerm(parts[i]) } } else { parts := strings.Split(text, delim) - start := len(parts) + n - if start < 0 { - start = 0 - } - result = make([]*ast.Term, len(parts)-start) - for i, p := range parts[start:] { - result[i] = ast.InternedTerm(p) - } + start := max(len(parts)+n, 0) + result = util.Map(parts[start:], ast.InternedTerm) } return iter(ast.ArrayTerm(result...)) @@ -793,7 +785,7 @@ func builtinSprintf(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) // Optimized path for where sprintf is used as a "to_string" function for // a single integer, i.e. sprintf("%d", [x]) where x is an integer. if s == "%d" && a.Len() == 1 { - if n, ok := a.Elem(0).Value.(ast.Number); ok { + if n, ok := a.Elem(0).Value.(ast.Number); ok && !isFloatNumber(string(n)) { if i, ok := n.Int(); ok { if interned := ast.InternedIntegerString(i); interned != nil { return iter(interned) @@ -810,23 +802,18 @@ func builtinSprintf(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) switch v := t.Value.(type) { case ast.Number: ns := string(v) - if x, ok := util.Atoi64(ns); ok { - args[i] = x - } else { - if strings.ContainsRune(ns, '.') { - if f, ok := v.Float64(); ok { - args[i] = f - continue - } else { - args[i] = ns - } + if isFloatNumber(ns) { + if f, ok := v.Float64(); ok { + args[i] = f } else { - if b, ok := new(big.Int).SetString(ns, 10); ok { - args[i] = b - } else { - args[i] = ns - } + args[i] = ns } + } else if x, ok := util.Atoi64(ns); ok { + args[i] = x + } else if b, ok := new(big.Int).SetString(ns, 10); ok { + args[i] = b + } else { + args[i] = ns } case ast.String: args[i] = string(v) @@ -838,6 +825,15 @@ func builtinSprintf(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) return iter(ast.InternedTerm(fmt.Sprintf(string(s), args...))) } +// isFloatNumber reports whether the textual representation of a number is that +// of a floating point value, i.e. it has a fraction or an exponent. Since +// util.Atoi64 parses numbers with only zeros past the decimal point (1.0) as +// integers, the text, and not the parsed value, decides how a number is +// formatted by sprintf. +func isFloatNumber(s string) bool { + return strings.ContainsAny(s, ".eE") +} + func builtinReverse(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { s, err := builtins.StringOperand(operands[0].Value, 1) if err != nil { @@ -864,7 +860,7 @@ func reverseString(str string) string { utf8.EncodeRune(buf[size-start:], r) } - return string(buf) + return util.ByteSliceToString(buf) } func init() { diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/subset.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/subset.go index d50dc2db77..93fd75e13e 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/subset.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/subset.go @@ -182,7 +182,7 @@ func arraySubset(super, sub *ast.Array) bool { } subElem := sub.Elem(subCursor) - if superElem.Value.Compare(subElem.Value) == 0 { + if superElem.Equal(subElem) { subCursor++ } else { superCursor++ diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/template_string.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/template_string.go index 0e705b81bf..c9a4984a76 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/template_string.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/template_string.go @@ -20,7 +20,7 @@ func builtinTemplateString(bctx BuiltinContext, operands []*ast.Term, iter func( buf := make([]string, arr.Len()) var count int - err = builtinPrintCrossProductOperands(bctx.Location, buf, arr, 0, func(buf []string) error { + err = builtinPrintCrossProductOperands(bctx.Location, buf, arr, 0, func([]string) error { count += 1 // Precautionary run-time assertion that template-strings can't produce multiple outputs; e.g. for custom relation type built-ins not known at compile-time. if count > 1 { diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/test.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/test.go index 02958d2264..e3287b0eaa 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/test.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/test.go @@ -13,7 +13,8 @@ func builtinTestCase(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.T Op: TestCaseOp, QueryID: bctx.QueryID, Node: ast.NewExpr([]*ast.Term{ - ast.NewTerm(ast.InternalTestCase.Ref()), + // Copied, as tracers may transform the node. + ast.NewTerm(ast.Interned.Refs.InternalTestCase.Copy()), ast.NewTerm(operands[0].Value), }), } diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/time.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/time.go index 7171d5c0b2..b41904712f 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/time.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/time.go @@ -21,20 +21,22 @@ import ( "github.com/open-policy-agent/opa/v1/topdown/durationparser" ) -var tzCache map[string]*time.Location -var tzCacheMutex *sync.Mutex +var ( + tzCache = make(map[string]*time.Location) + tzCacheMutex = &sync.Mutex{} -// 1677-09-21T00:12:43.145224192-00:00 -var minDateAllowedForNsConversion = time.Unix(0, math.MinInt64) + // 1677-09-21T00:12:43.145224192-00:00 + minDateAllowedForNsConversion = time.Unix(0, math.MinInt64) -// 2262-04-11T23:47:16.854775807-00:00 -var maxDateAllowedForNsConversion = time.Unix(0, math.MaxInt64) + // 2262-04-11T23:47:16.854775807-00:00 + maxDateAllowedForNsConversion = time.Unix(0, math.MaxInt64) -var durationCoefficients = map[string]int{ - "d": 24, - "w": 7 * 24, - "y": 365 * 24, -} + durationCoefficients = map[string]int{ + "d": 24, + "w": 7 * 24, + "y": 365 * 24, + } +) // parseExtendedDuration parses a duration string that may contain extended // units (d, w, y) mixed with standard Go duration units (h, m, s, ms, us, ns). @@ -46,10 +48,7 @@ func parseExtendedDuration(s string) (int64, error) { if !strings.ContainsAny(s, "dwy") { v, err := time.ParseDuration(s) - if err != nil { - return 0, err - } - return int64(v), nil + return int64(v), err } result, err := durationparser.Parse("", []byte(s)) @@ -420,6 +419,4 @@ func init() { RegisterBuiltinFunc(ast.Weekday.Name, builtinWeekday) RegisterBuiltinFunc(ast.AddDate.Name, builtinAddDate) RegisterBuiltinFunc(ast.Diff.Name, builtinDiff) - tzCacheMutex = &sync.Mutex{} - tzCache = make(map[string]*time.Location) } diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/tokens.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/tokens.go index 622f376c77..ffd3d5e008 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/tokens.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/tokens.go @@ -822,9 +822,7 @@ func tokenHeaderString(name string, where *string, value ast.Value) error { // parseTokenHeader parses the JWT header. func parseTokenHeader(token *JSONWebToken) (*tokenHeader, error) { - header := tokenHeader{ - unknown: []string{}, - } + header := tokenHeader{} if err := token.decodedHeader.Iter(func(k *ast.Term, v *ast.Term) error { ks := string(k.Value.(ast.String)) handler, ok := tokenHeaderTypes[ks] diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/trace.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/trace.go index 8b672da54e..ceb8f7e108 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/trace.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/trace.go @@ -8,6 +8,7 @@ import ( "bytes" "fmt" "io" + "maps" "slices" "strings" @@ -15,10 +16,11 @@ import ( "github.com/open-policy-agent/opa/v1/ast" "github.com/open-policy-agent/opa/v1/topdown/builtins" + "github.com/open-policy-agent/opa/v1/util" ) const ( - minLocationWidth = 5 // len("query") + minLocationWidth = len("query") maxIdealLocationWidth = 64 columnPadding = 4 maxExprVarWidth = 32 @@ -265,10 +267,6 @@ type PrettyTraceOptions struct { type traceRow []string -func (r *traceRow) add(s string) { - *r = append(*r, s) -} - type traceTable struct { rows []traceRow maxWidths []int @@ -292,7 +290,7 @@ func (t *traceTable) write(w io.Writer, padding int) { if i < len(row)-1 { _, _ = fmt.Fprintf(w, "%-*s ", width, cell) } else { - _, _ = fmt.Fprintf(w, "%s", cell) + _, _ = w.Write(util.StringToByteSlice(cell)) } } _, _ = fmt.Fprintln(w) @@ -306,50 +304,66 @@ func PrettyTraceWithOpts(w io.Writer, trace []*Event, opts PrettyTraceOptions) { filePathAliases, _ := getShortenedFileNames(trace) table := traceTable{} + buf := new(bytes.Buffer) for _, event := range trace { depth := depths.GetOrSet(event.QueryID, event.ParentID) row := traceRow{} if opts.Locations { - location := formatLocation(event, filePathAliases) - row.add(location) + row = append(row, formatLocation(event, filePathAliases)) } - row.add(formatEvent(event, depth)) + row = append(row, formatEvent(event, depth)) if opts.ExprVariables { vars := exprLocalVars(event) keys := sortedKeys(vars) - buf := new(bytes.Buffer) - buf.WriteString("{") - for i, k := range keys { - if i > 0 { + buf.Reset() + buf.WriteByte('{') + + if len(keys) > 0 { + k := keys[0] + buf.WriteString(k.String()) + buf.WriteString(": ") + buf.WriteString(iStrs.Truncate(vars.Get(k).String(), maxExprVarWidth)) + + for _, k := range keys[1:] { buf.WriteString(", ") + buf.WriteString(k.String()) + buf.WriteString(": ") + buf.WriteString(iStrs.Truncate(vars.Get(k).String(), maxExprVarWidth)) } - _, _ = fmt.Fprintf(buf, "%v: %s", k, iStrs.Truncate(vars.Get(k).String(), maxExprVarWidth)) } - buf.WriteString("}") - row.add(buf.String()) + + buf.WriteByte('}') + row = append(row, buf.String()) } if opts.LocalVariables { - if locals := event.Locals; locals != nil { + if locals := event.Locals; locals.Len() > 0 { keys := sortedKeys(locals) - buf := new(bytes.Buffer) - buf.WriteString("{") - for i, k := range keys { - if i > 0 { - buf.WriteString(", ") - } - _, _ = fmt.Fprintf(buf, "%v: %s", k, iStrs.Truncate(locals.Get(k).String(), maxExprVarWidth)) + buf.Reset() + buf.WriteByte('{') + + k := keys[0] + buf.WriteString(k.String()) + buf.WriteString(": ") + buf.WriteString(iStrs.Truncate(locals.Get(k).String(), maxExprVarWidth)) + + for _, k := range keys[1:] { + buf.WriteString(", ") + buf.WriteString(k.String()) + buf.WriteString(": ") + buf.WriteString(iStrs.Truncate(locals.Get(k).String(), maxExprVarWidth)) } - buf.WriteString("}") - row.add(buf.String()) + + buf.WriteByte('}') + row = append(row, buf.String()) } else { - row.add("{}") + row = append(row, "{}") } } @@ -365,21 +379,18 @@ func sortedKeys(vm *ast.ValueMap) []ast.Value { keys = append(keys, k) return false }) - slices.SortFunc(keys, func(a, b ast.Value) int { + return util.SortedFunc(keys, func(a, b ast.Value) int { return strings.Compare(a.String(), b.String()) }) - return keys } func exprLocalVars(e *Event) *ast.ValueMap { vars := ast.NewValueMap() - findVars := func(term *ast.Term) bool { - if name, ok := term.Value.(ast.Var); ok { - if meta, ok := e.LocalMetadata[name]; ok { - if val := e.Locals.Get(name); val != nil { - vars.Put(meta.Name, val) - } + findVars := func(name ast.Var) bool { + if meta, ok := e.LocalMetadata[name]; ok { + if val := e.Locals.Get(name); val != nil { + vars.Put(meta.Name, val) } } return false @@ -387,7 +398,7 @@ func exprLocalVars(e *Event) *ast.ValueMap { if r, ok := e.Node.(*ast.Rule); ok { // We're only interested in vars in the head, not the body - ast.WalkTerms(r.Head, findVars) + ast.WalkVars(r.Head, findVars) return vars } @@ -398,43 +409,47 @@ func exprLocalVars(e *Event) *ast.ValueMap { return false }) - ast.WalkTerms(e.Node, findVars) + ast.WalkVars(e.Node, findVars) return vars } func formatEvent(event *Event, depth int) string { - padding := formatEventPadding(event, depth) + buf := new(bytes.Buffer) + formatEventPaddingAppend(buf, event, depth) + buf.WriteString(string(event.Op)) + buf.WriteByte(' ') + if event.Op == NoteOp { - return fmt.Sprintf("%v%v %q", padding, event.Op, event.Message) + buf.WriteByte('"') + buf.WriteString(event.Message) + buf.WriteByte('"') + + return buf.String() } - var details any if node, ok := event.Node.(*ast.Rule); ok { - details = ast.RulePath(node) + bs, _ := node.Ref().ConstantPrefix().AppendText(buf.AvailableBuffer()) + buf.Write(bs) } else if event.Ref != nil { - details = event.Ref + bs, _ := event.Ref.AppendText(buf.AvailableBuffer()) + buf.Write(bs) } else { - details = rewrite(event).Node + fmt.Fprint(buf, rewrite(event).Node) } - template := "%v%v %v" - opts := []any{padding, event.Op, details} - if event.Message != "" { - template += " %v" - opts = append(opts, event.Message) + buf.WriteByte(' ') + buf.WriteString(event.Message) } - return fmt.Sprintf(template, opts...) + return buf.String() } -func formatEventPadding(event *Event, depth int) string { - spaces := formatEventSpaces(event, depth) - if spaces > 1 { - return strings.Repeat("| ", spaces-1) +func formatEventPaddingAppend(buf *bytes.Buffer, event *Event, depth int) { + for range formatEventSpaces(event, depth) - 1 { + buf.WriteString("| ") } - return "" } func formatEventSpaces(event *Event, depth int) int { @@ -461,11 +476,7 @@ func getShortenedFileNames(trace []*Event) (map[string]string, int) { if event.Location != nil { if event.Location.File != "" { // length of ":" - curLen := len(event.Location.File) + numDigits10(event.Location.Row) + 1 - if curLen > longestLocation { - longestLocation = curLen - } - + longestLocation = max(longestLocation, event.Location.StringLength()) if _, ok := fpAliases[event.Location.File]; ok { continue } @@ -476,10 +487,7 @@ func getShortenedFileNames(trace []*Event) (map[string]string, int) { fpAliases[event.Location.File] = event.Location.File } else { // length of ":" - curLen := minLocationWidth + numDigits10(event.Location.Row) + 1 - if curLen > longestLocation { - longestLocation = curLen - } + longestLocation = max(longestLocation, minLocationWidth+util.NumDigitsInt(event.Location.Row)+1) } } } @@ -491,25 +499,16 @@ func getShortenedFileNames(trace []*Event) (map[string]string, int) { return fpAliases, longestLocation } -func numDigits10(n int) int { - if n < 10 { - return 1 - } - return numDigits10(n/10) + 1 -} - func formatLocation(event *Event, fileAliases map[string]string) string { - - location := event.Location - if location == nil { + if event.Location == nil { return "" } - if location.File == "" { - return fmt.Sprintf("query:%v", location.Row) + if event.Location.File == "" { + return fmt.Sprintf("query:%v", event.Location.Row) } - return fmt.Sprintf("%v:%v", fileAliases[location.File], location.Row) + return fmt.Sprintf("%v:%v", fileAliases[event.Location.File], event.Location.Row) } // depths is a helper for computing the depth of an event. Events within the @@ -528,7 +527,6 @@ func (ds depths) GetOrSet(qid uint64, pqid uint64) int { } func builtinTrace(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { - str, err := builtins.StringOperand(operands[0].Value, 1) if err != nil { return handleBuiltinErr(ast.Trace.Name, bctx.Location, err) @@ -650,7 +648,7 @@ func (v varInfo) Value() string { func (v varInfo) Title() string { if v.exprLoc != nil && v.exprLoc.Text != nil { - return string(v.exprLoc.Text) + return util.ByteSliceToString(v.exprLoc.Text) } return string(v.Name) } @@ -660,8 +658,7 @@ func padLocationText(loc *ast.Location) string { return "" } - text := string(loc.Text) - + text := util.ByteSliceToString(loc.Text) if loc.Col == 0 { return text } @@ -839,13 +836,13 @@ func PrettyEvent(w io.Writer, e *Event, opts PrettyEventOpts) error { } printPrettyVars(buf, exprVars) - _, _ = fmt.Fprint(w, buf.String()) + w.Write(buf.Bytes()) return nil } func printPrettyVars(w *bytes.Buffer, exprVars map[string]varInfo) { containsTabs := false - varRows := make(map[int]any) + varRows := make(map[int]any, len(exprVars)) for _, info := range exprVars { if len(info.exprLoc.Tabs) > 0 { containsTabs = true @@ -856,15 +853,9 @@ func printPrettyVars(w *bytes.Buffer, exprVars map[string]varInfo) { if containsTabs && len(varRows) > 1 { // We can't (currently) reliably point to var locations when they are on different rows that contain tabs. // So we'll just print them in alphabetical order instead. - byName := make([]varInfo, 0, len(exprVars)) - for _, info := range exprVars { - byName = append(byName, info) - } - slices.SortStableFunc(byName, func(a, b varInfo) int { - return strings.Compare(a.Title(), b.Title()) - }) - w.WriteString("\n\nWhere:\n") + + byName := slices.SortedStableFunc(maps.Values(exprVars), cmpVarInfoTitle) for _, info := range byName { fmt.Fprintf(w, "\n%s: %s", info.Title(), iStrs.Truncate(info.Value(), maxPrettyExprVarWidth)) } @@ -872,11 +863,7 @@ func printPrettyVars(w *bytes.Buffer, exprVars map[string]varInfo) { return } - byCol := make([]varInfo, 0, len(exprVars)) - for _, info := range exprVars { - byCol = append(byCol, info) - } - slices.SortFunc(byCol, func(a, b varInfo) int { + byCol := util.SortedFunc(util.Values(exprVars), func(a, b varInfo) int { // sort first by column, then by reverse row (to present vars in the same order they appear in the expr) if a.col == b.col { if a.exprLoc.Row == b.exprLoc.Row { @@ -891,14 +878,18 @@ func printPrettyVars(w *bytes.Buffer, exprVars map[string]varInfo) { return } - w.WriteString("\n") + w.WriteByte('\n') printArrows(w, byCol, -1) - for i := len(byCol) - 1; i >= 0; i-- { - w.WriteString("\n") + for i := range slices.Backward(byCol) { + w.WriteByte('\n') printArrows(w, byCol, i) } } +func cmpVarInfoTitle(a, b varInfo) int { + return strings.Compare(a.Title(), b.Title()) +} + func printArrows(w *bytes.Buffer, l []varInfo, printValueAt int) { prevCol := 0 var slice []varInfo @@ -909,7 +900,6 @@ func printArrows(w *bytes.Buffer, l []varInfo, printValueAt int) { } isFirst := true for i, info := range slice { - isLast := i >= len(slice)-1 col := info.col @@ -924,26 +914,25 @@ func printArrows(w *bytes.Buffer, l []varInfo, printValueAt int) { } for j := range spaces { - tab := false + var space byte = ' ' if slices.Contains(info.exprLoc.Tabs, j+prevCol+1) { - w.WriteString("\t") - tab = true - } - if !tab { - w.WriteString(" ") + space = '\t' } + w.WriteByte(space) } if isLast && printValueAt >= 0 { valueStr := iStrs.Truncate(info.Value(), maxPrettyExprVarWidth) if (i > 0 && col == l[i-1].col) || (i < len(l)-1 && col == l[i+1].col) { // There is another var on this column, so we need to include the name to differentiate them. - fmt.Fprintf(w, "%s: %s", info.Title(), valueStr) + w.WriteString(info.Title()) + w.WriteString(": ") + w.WriteString(valueStr) } else { w.WriteString(valueStr) } } else { - w.WriteString("|") + w.WriteByte('|') } prevCol = col isFirst = false diff --git a/vendor/github.com/open-policy-agent/opa/v1/topdown/uuid.go b/vendor/github.com/open-policy-agent/opa/v1/topdown/uuid.go index 141fb908bd..87229f0785 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/topdown/uuid.go +++ b/vendor/github.com/open-policy-agent/opa/v1/topdown/uuid.go @@ -10,12 +10,10 @@ import ( "github.com/open-policy-agent/opa/v1/topdown/builtins" ) -type uuidCachingKey string +type uuidCachingKey int func builtinUUIDRFC4122(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { - - var key = uuidCachingKey(operands[0].Value.String()) - + key := uuidCachingKey(operands[0].Value.Hash()) val, ok := bctx.Cache.Get(key) if ok { return iter(val.(*ast.Term)) @@ -51,6 +49,11 @@ func builtinUUIDParse(_ BuiltinContext, operands []*ast.Term, iter func(term *as } func init() { + ast.InternStringTerm( + "version", "variant", "nodeid", "macvariables", "time", "clocksequence", "domain", "id", + "local:multicast", "global:multicast", "local:unicast", "global:unicast", "RFC4122", + "Person", "Group", "Org", + ) RegisterBuiltinFunc(ast.UUIDRFC4122.Name, builtinUUIDRFC4122) RegisterBuiltinFunc(ast.UUIDParse.Name, builtinUUIDParse) } diff --git a/vendor/github.com/open-policy-agent/opa/v1/types/decode.go b/vendor/github.com/open-policy-agent/opa/v1/types/decode.go index f2515d5df1..a571d565f4 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/types/decode.go +++ b/vendor/github.com/open-policy-agent/opa/v1/types/decode.go @@ -25,8 +25,7 @@ const ( ) // Unmarshal deserializes bs and returns the resulting type. -func Unmarshal(bs []byte) (result Type, err error) { - +func Unmarshal[T byte, BS ~[]byte](bs BS) (result Type, err error) { var hint rawtype if err = util.UnmarshalJSON(bs, &hint); err == nil { @@ -45,7 +44,7 @@ func Unmarshal(bs []byte) (result Type, err error) { var err error var static []Type var dynamic Type - if static, err = unmarshalSlice(arr.Static); err != nil { + if static, err = util.TryMap(arr.Static, Unmarshal); err != nil { return nil, err } if len(arr.Dynamic) != 0 { @@ -81,14 +80,14 @@ func Unmarshal(bs []byte) (result Type, err error) { var union rawunion if err = util.UnmarshalJSON(bs, &union); err == nil { var of []Type - if of, err = unmarshalSlice(union.Of); err == nil { + if of, err = util.TryMap(union.Of, Unmarshal); err == nil { result = NewAny(of...) } } case typeFunction: var decl rawdecl if err = util.UnmarshalJSON(bs, &decl); err == nil { - args, err := unmarshalSlice(decl.Args) + args, err := util.TryMap(decl.Args, Unmarshal) if err != nil { return nil, err } @@ -155,16 +154,6 @@ type rawdecl struct { Variadic json.RawMessage `json:"variadic"` } -func unmarshalSlice(elems []json.RawMessage) (result []Type, err error) { - result = make([]Type, len(elems)) - for i := range elems { - if result[i], err = Unmarshal(elems[i]); err != nil { - return nil, err - } - } - return result, err -} - func unmarshalStaticPropertySlice(elems []rawstaticproperty) (result []*StaticProperty, err error) { result = make([]*StaticProperty, len(elems)) for i := range elems { diff --git a/vendor/github.com/open-policy-agent/opa/v1/types/types.go b/vendor/github.com/open-policy-agent/opa/v1/types/types.go index aff4fde39e..c9f958161a 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/types/types.go +++ b/vendor/github.com/open-policy-agent/opa/v1/types/types.go @@ -11,7 +11,6 @@ import ( "errors" "fmt" "slices" - "sort" "strings" "github.com/open-policy-agent/opa/v1/util" @@ -27,10 +26,15 @@ var ( // N represents an instance of the number type. N Type = NewNumber() // A represents the superset of all types. - A Type = NewAny() + A Type = Any{} // Boxed set types. SetOfAny, SetOfStr, SetOfNum Type = NewSet(A), NewSet(S), NewSet(N) + + jsonString = [...]byte{'{', '"', 't', 'y', 'p', 'e', '"', ':', '"', 's', 't', 'r', 'i', 'n', 'g', '"', '}'} + jsonBoolean = [...]byte{'{', '"', 't', 'y', 'p', 'e', '"', ':', '"', 'b', 'o', 'o', 'l', 'e', 'a', 'n', '"', '}'} + jsonNumber = [...]byte{'{', '"', 't', 'y', 'p', 'e', '"', ':', '"', 'n', 'u', 'm', 'b', 'e', 'r', '"', '}'} + jsonNull = [...]byte{'{', '"', 't', 'y', 'p', 'e', '"', ':', '"', 'n', 'u', 'l', 'l', '"', '}'} ) // Sprint returns the string representation of the type. @@ -109,10 +113,8 @@ func Named(name string, t Type) *NamedType { } // MarshalJSON returns the JSON encoding of t. -func (t Null) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]any{ - "type": t.typeMarker(), - }) +func (Null) MarshalJSON() ([]byte, error) { + return jsonNull[:], nil } func unwrap(t Type) Type { @@ -144,11 +146,8 @@ func NewBoolean() Boolean { } // MarshalJSON returns the JSON encoding of t. -func (t Boolean) MarshalJSON() ([]byte, error) { - repr := map[string]any{ - "type": t.typeMarker(), - } - return json.Marshal(repr) +func (Boolean) MarshalJSON() ([]byte, error) { + return jsonBoolean[:], nil } func (t Boolean) String() string { @@ -164,10 +163,8 @@ func NewString() String { } // MarshalJSON returns the JSON encoding of t. -func (t String) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]any{ - "type": t.typeMarker(), - }) +func (String) MarshalJSON() ([]byte, error) { + return jsonString[:], nil } func (String) String() string { @@ -183,10 +180,8 @@ func NewNumber() Number { } // MarshalJSON returns the JSON encoding of t. -func (t Number) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]any{ - "type": t.typeMarker(), - }) +func (Number) MarshalJSON() ([]byte, error) { + return jsonNumber[:], nil } func (Number) String() string { @@ -227,10 +222,7 @@ func (t *Array) toMap() map[string]any { func (t *Array) String() string { prefix := "array" - buf := make([]string, 0, len(t.static)) - for _, tpe := range t.static { - buf = append(buf, Sprint(tpe)) - } + buf := util.Map(t.static, Sprint) repr := prefix if len(buf) > 0 { repr += "<" + strings.Join(buf, ", ") + ">" @@ -296,8 +288,10 @@ func (t *Set) toMap() map[string]any { } func (t *Set) String() string { - prefix := typeSet - return prefix + "[" + Sprint(t.of) + "]" + if t.of == nil { + return typeSet + } + return typeSet + "[" + Sprint(t.of) + "]" } // StaticProperty represents a static object property. @@ -345,7 +339,7 @@ func (p *DynamicProperty) MarshalJSON() ([]byte, error) { } func (p *DynamicProperty) String() string { - return fmt.Sprintf("%s: %s", Sprint(p.Key), Sprint(p.Value)) + return Sprint(p.Key) + ": " + Sprint(p.Value) } // Object represents the object type. @@ -356,11 +350,8 @@ type Object struct { // NewObject returns a new Object type. func NewObject(static []*StaticProperty, dynamic *DynamicProperty) *Object { - slices.SortFunc(static, func(a, b *StaticProperty) int { - return util.Compare(a.Key, b.Key) - }) return &Object{ - static: static, + static: util.SortedFunc(static, cmpSpKey), dynamic: dynamic, } } @@ -428,18 +419,12 @@ func (t *Object) toMap() map[string]any { // Select returns the type of the named property. func (t *Object) Select(name any) Type { - pos := sort.Search(len(t.static), func(x int) bool { - return util.Compare(t.static[x].Key, name) >= 0 - }) - - if pos < len(t.static) && util.Compare(t.static[pos].Key, name) == 0 { + if pos, found := slices.BinarySearchFunc(t.static, name, cmpSpKeyName); found { return t.static[pos].Value } - if t.dynamic != nil { - if Contains(t.dynamic.Key, TypeOf(name)) { - return t.dynamic.Value - } + if t.dynamic != nil && Contains(t.dynamic.Key, TypeOf(name)) { + return t.dynamic.Value } return nil @@ -553,8 +538,7 @@ type Any []Type func NewAny(of ...Type) Any { sl := make(Any, len(of)) copy(sl, of) - sort.Sort(typeSlice(sl)) - return sl + return util.SortedFunc(sl, Compare) } // Contains returns true if t is a superset of other. @@ -562,16 +546,8 @@ func (t Any) Contains(other Type) bool { if _, ok := other.(*Function); ok { return false } - // Note(philipc): We used to do this as a linear search. - // Since this is always sorted, we can use a binary search instead. - i := sort.Search(len(t), func(i int) bool { - return Compare(t[i], other) >= 0 - }) - if i < len(t) && Compare(t[i], other) == 0 { - // x is present at t[i] - return true - } - return len(t) == 0 + _, found := slices.BinarySearchFunc(t, other, Compare) + return found || len(t) == 0 } // MarshalJSON returns the JSON encoding of t. @@ -598,9 +574,8 @@ func (t Any) Merge(other Type) Any { return t } cpy := make(Any, len(t)+1) - idx := sort.Search(len(t), func(i int) bool { - return Compare(t[i], other) >= 0 - }) + idx, _ := slices.BinarySearchFunc(t, other, Compare) + copy(cpy, t[:idx]) cpy[idx] = other copy(cpy[idx+1:], t[idx:]) @@ -675,15 +650,11 @@ func (t Any) Union(other Any) Any { } func (t Any) String() string { - prefix := "any" if len(t) == 0 { - return prefix + return "any" } - buf := make([]string, len(t)) - for i := range t { - buf[i] = Sprint(t[i]) - } - return prefix + "<" + strings.Join(buf, ", ") + ">" + buf := util.Map(t, Sprint) + return "any<" + strings.Join(buf, ", ") + ">" } // Function represents a function type. @@ -748,20 +719,14 @@ func (t *Function) FuncArgs() FuncArgs { // NamedFuncArgs returns the function's arguments, with a name and // description if available. func (t *Function) NamedFuncArgs() FuncArgs { - args := make([]Type, len(t.args)) - copy(args, t.args) - return FuncArgs{Args: args, Variadic: t.variadic} + return FuncArgs{Args: slices.Clone(t.args), Variadic: t.variadic} } // Args returns the function's arguments as a slice, ignoring variadic arguments. // // Deprecated: Use FuncArgs instead. func (t *Function) Args() []Type { - cpy := make([]Type, len(t.args)) - for i := range t.args { - cpy[i] = unwrap(t.args[i]) - } - return cpy + return util.Map(t.args, unwrap) } // Arity returns the number of arguments in the function signature. @@ -884,8 +849,7 @@ func (a FuncArgs) Arg(x int) Type { // Compare returns -1, 0, 1 based on comparison between a and b. func Compare(a, b Type) int { a, b = unwrapRecursive(unwrap(a)), unwrapRecursive(unwrap(b)) - x := typeOrder(a) - y := typeOrder(b) + x, y := typeOrder(a), typeOrder(b) if x > y { return 1 } else if x < y { @@ -910,7 +874,7 @@ func Compare(a, b Type) int { return cmp } } - return typeSliceCompare(arrA.static, arrB.static) + return slices.CompareFunc(arrA.static, arrB.static, Compare) case *Object: objA := a.(*Object) objB := b.(*Object) @@ -964,9 +928,7 @@ func Compare(a, b Type) int { } return Compare(setA.of, setB.of) case Any: - sl1 := typeSlice(a.(Any)) - sl2 := typeSlice(b.(Any)) - return typeSliceCompare(sl1, sl2) + return slices.CompareFunc([]Type(a.(Any)), []Type(b.(Any)), Compare) case *Function: fA := a.(*Function) fB := b.(*Function) @@ -1212,36 +1174,11 @@ func TypeOf(x any) Type { } return NewObject(static, nil) case []any: - static := make([]Type, len(x)) - for i := range x { - static[i] = TypeOf(x[i]) - } - return NewArray(static, nil) + return NewArray(util.Map(x, TypeOf), nil) } panic("unreachable") } -type typeSlice []Type - -func (s typeSlice) Less(i, j int) bool { return Compare(s[i], s[j]) < 0 } -func (s typeSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s typeSlice) Len() int { return len(s) } - -func typeSliceCompare(a, b []Type) int { - minLen := min(len(b), len(a)) - for i := range minLen { - if cmp := Compare(a[i], b[i]); cmp != 0 { - return cmp - } - } - if len(a) < len(b) { - return -1 - } else if len(b) < len(a) { - return 1 - } - return 0 -} - func typeOrder(x Type) int { switch unwrapRecursive(unwrap(x)).(type) { case Null: @@ -1267,3 +1204,11 @@ func typeOrder(x Type) int { } panic("unreachable") } + +func cmpSpKeyName(p *StaticProperty, name any) int { + return util.Compare(p.Key, name) +} + +func cmpSpKey(a, b *StaticProperty) int { + return util.Compare(a.Key, b.Key) +} diff --git a/vendor/github.com/open-policy-agent/opa/v1/util/channel.go b/vendor/github.com/open-policy-agent/opa/v1/util/channel.go deleted file mode 100644 index e2653ac7fd..0000000000 --- a/vendor/github.com/open-policy-agent/opa/v1/util/channel.go +++ /dev/null @@ -1,32 +0,0 @@ -package util - -import ( - "github.com/open-policy-agent/opa/v1/metrics" -) - -// This prevents getting blocked forever writing to a full buffer, in case another routine fills the last space. -// Retrying maxEventRetry times to drop the oldest event. Dropping the incoming event if there still isn't room. -const maxEventRetry = 1000 - -// PushFIFO pushes data into a buffered channel without blocking when full, making room by dropping the oldest data. -// An optional metric can be recorded when data is dropped. -func PushFIFO[T any](buffer chan T, data T, metrics metrics.Metrics, metricName string) { - - for range maxEventRetry { - // non-blocking send to the buffer, to prevent blocking if buffer is full so room can be made. - select { - case buffer <- data: - return - default: - } - - // non-blocking drop from the buffer to make room for incoming event - select { - case <-buffer: - if metrics != nil && metricName != "" { - metrics.Counter(metricName).Incr() - } - default: - } - } -} diff --git a/vendor/github.com/open-policy-agent/opa/v1/util/compare.go b/vendor/github.com/open-policy-agent/opa/v1/util/compare.go index ec12210c6e..3825ef24b3 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/util/compare.go +++ b/vendor/github.com/open-policy-agent/opa/v1/util/compare.go @@ -35,6 +35,17 @@ func Or[T comparable](val T, suppliers ...func() T) T { return val } +// NilOr returns the first non-nil value from the provided list of pointers, or nil if all are nil. +func NilOr[T any](vals ...*T) *T { + for _, val := range vals { + if val != nil { + return val + } + } + + return nil +} + // SliceLenCompare is a convenience function for comparing / sorting // slices by their length using the various slices.SortX functions. func SliceLenCompare[T any, S ~[]T](a, b S) int { @@ -48,6 +59,14 @@ func SliceLenCompare[T any, S ~[]T](a, b S) int { return 1 } +// CmpEqual is a functional helper for equals comparison of comparable values +// (i.e. using ==), meant to be used for stdlib funtions like [slices.DeleteFunc]. +func CmpEqual[T comparable](a T) func(b T) bool { + return func(b T) bool { + return a == b + } +} + // Compare returns 0 if a equals b, -1 if a is less than b, and 1 if b is than a. // // For comparison between values of different types, the following ordering is used: @@ -66,8 +85,7 @@ func Compare(a, b any) int { case nil: return 0 case bool: - switch b := b.(type) { - case bool: + if b, ok := b.(bool); ok { if a == b { return 0 } @@ -77,13 +95,11 @@ func Compare(a, b any) int { return 1 } case json.Number: - switch b := b.(type) { - case json.Number: + if b, ok := b.(json.Number); ok { return compareJSONNumber(a, b) } case int: - switch b := b.(type) { - case int: + if b, ok := b.(int); ok { if a == b { return 0 } else if a < b { @@ -92,8 +108,7 @@ func Compare(a, b any) int { return 1 } case float64: - switch b := b.(type) { - case float64: + if b, ok := b.(float64); ok { if a == b { return 0 } else if a < b { @@ -102,8 +117,7 @@ func Compare(a, b any) int { return 1 } case string: - switch b := b.(type) { - case string: + if b, ok := b.(string); ok { if a == b { return 0 } else if a < b { @@ -112,8 +126,7 @@ func Compare(a, b any) int { return 1 } case []any: - switch b := b.(type) { - case []any: + if b, ok := b.([]any); ok { bLen := len(b) aLen := len(a) minLen := min(bLen, aLen) @@ -131,8 +144,7 @@ func Compare(a, b any) int { return 1 } case map[string]any: - switch b := b.(type) { - case map[string]any: + if b, ok := b.(map[string]any); ok { aKeys := KeysSorted(a) bKeys := KeysSorted(b) aLen := len(aKeys) @@ -164,6 +176,15 @@ func Compare(a, b any) int { } func compareJSONNumber(a, b json.Number) int { + if a == b { + return 0 + } + if ai, ok := Atoi(string(a)); ok { + if bi, ok := Atoi(string(b)); ok { + return ai - bi + } + return -1 + } bigA, ok := new(big.Float).SetString(string(a)) if !ok { panic("illegal value") diff --git a/vendor/github.com/open-policy-agent/opa/v1/util/constraints.go b/vendor/github.com/open-policy-agent/opa/v1/util/constraints.go new file mode 100644 index 0000000000..3ad837f07f --- /dev/null +++ b/vendor/github.com/open-policy-agent/opa/v1/util/constraints.go @@ -0,0 +1,19 @@ +package util + +type ( + Number interface { + Integer | Float + } + Integer interface { + SignedInteger | UnsignedInteger + } + SignedInteger interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 + } + UnsignedInteger interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr + } + Float interface { + ~float32 | ~float64 + } +) diff --git a/vendor/github.com/open-policy-agent/opa/v1/util/errors.go b/vendor/github.com/open-policy-agent/opa/v1/util/errors.go new file mode 100644 index 0000000000..18558129ff --- /dev/null +++ b/vendor/github.com/open-policy-agent/opa/v1/util/errors.go @@ -0,0 +1,11 @@ +package util + +import "errors" + +// ErrorIs is a shorthand for [errors.AsType] returning only the boolean result. +// This is typically cheaper than [errors.Is], but it's not a drop-in replacement +// for scenario where e.g. custom `Is` methods need to be taken into account. +func ErrorIs[E error](err error) bool { + _, ok := errors.AsType[E](err) + return ok +} diff --git a/vendor/github.com/open-policy-agent/opa/v1/util/json.go b/vendor/github.com/open-policy-agent/opa/v1/util/json.go index de95ed50bf..cc0e5793f4 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/util/json.go +++ b/vendor/github.com/open-policy-agent/opa/v1/util/json.go @@ -9,11 +9,12 @@ import ( "encoding/json" "fmt" "io" + "maps" "reflect" + "slices" "strconv" - "sigs.k8s.io/yaml" - + "github.com/open-policy-agent/opa/internal/yaml" "github.com/open-policy-agent/opa/v1/loader/extension" ) @@ -140,7 +141,16 @@ func RoundTrip(x *any) error { if err != nil { return err } - return UnmarshalJSON(bs, x) + + // Decode into a fresh value instead of reusing *x: if *x holds a non-nil + // pointer, json.Unmarshal decodes into the pointed-to value in place + // rather than replacing it. + var y any + if err := UnmarshalJSON(bs, &y); err != nil { + return err + } + *x = y + return nil } // NeedsRoundTrip returns true if the value won't change as a result of @@ -156,6 +166,90 @@ func NeedsRoundTrip(x any) bool { return true } +// RoundTripFast is equivalent to [RoundTrip], but recurses natively through +// map[string]any and []any instead of going through JSON bytes, falling +// back to [RoundTrip] for any other type. +func RoundTripFast(x *any) error { + if x == nil { + return nil + } + y, err := roundTripFastValue(*x, 0, nil) + if err != nil { + return err + } + *x = y + return nil +} + +// startDetectingCyclesAfter matches encoding/json's own threshold. +const startDetectingCyclesAfter = 1000 + +// depth/seen detect cycles the way encoding/json does: native recursion +// doesn't get that check for free from json.Marshal like RoundTrip's +// fallback path does. +func roundTripFastValue(v any, depth int, seen map[uintptr]struct{}) (any, error) { + switch x := v.(type) { + case nil, bool, string, json.Number: + return x, nil + case map[string]any: + if x == nil { + return nil, nil + } + ptr := uintptr(reflect.ValueOf(x).UnsafePointer()) + if depth >= startDetectingCyclesAfter { + if _, ok := seen[ptr]; ok { + return nil, fmt.Errorf("json: unsupported value: encountered a cycle via %T", x) + } + seen = markSeen(seen, ptr) + } + cpy := maps.Clone(x) + for k, e := range cpy { + c, err := roundTripFastValue(e, depth+1, seen) + if err != nil { + return nil, err + } + cpy[k] = c + } + delete(seen, ptr) + return cpy, nil + case []any: + if x == nil { + return nil, nil + } + ptr := uintptr(reflect.ValueOf(x).UnsafePointer()) + if depth >= startDetectingCyclesAfter { + if _, ok := seen[ptr]; ok { + return nil, fmt.Errorf("json: unsupported value: encountered a cycle via %T", x) + } + seen = markSeen(seen, ptr) + } + cpy := slices.Clone(x) + for i, e := range cpy { + c, err := roundTripFastValue(e, depth+1, seen) + if err != nil { + return nil, err + } + cpy[i] = c + } + delete(seen, ptr) + return cpy, nil + default: + y := v + if err := RoundTrip(&y); err != nil { + return nil, err + } + return y, nil + } +} + +func markSeen(seen map[uintptr]struct{}, ptr uintptr) map[uintptr]struct{} { + if seen == nil { + seen = map[uintptr]struct{}{} + } + seen[ptr] = struct{}{} + return seen +} + // Reference returns a pointer to its argument unless the argument already is // a pointer. If the argument is **t, or ***t, etc, it will return *t. // diff --git a/vendor/github.com/open-policy-agent/opa/v1/util/maps.go b/vendor/github.com/open-policy-agent/opa/v1/util/maps.go index c56fbe98ac..bbcb769754 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/util/maps.go +++ b/vendor/github.com/open-policy-agent/opa/v1/util/maps.go @@ -16,11 +16,22 @@ func Keys[M ~map[K]V, K comparable, V any](m M) []K { // KeysSorted returns a slice of keys from any map, sorted in ascending order. func KeysSorted[M ~map[K]V, K cmp.Ordered, V any](m M) []K { - r := make([]K, 0, len(m)) + return Sorted(Keys(m)) +} + +// KeysSortedFunc returns a slice of keys from any map, sorted by cmp. +func KeysSortedFunc[M ~map[K]V, K comparable, V any](m M, cmp func(K, K) int) []K { + keys := Keys(m) + slices.SortFunc(keys, cmp) + return keys +} + +// MapKeys returns a slice of keys from m, transformed by f. +func MapKeys[M ~map[K]V, K comparable, V, R any](m M, f func(K) R) []R { + r := make([]R, 0, len(m)) for k := range m { - r = append(r, k) + r = append(r, f(k)) } - slices.Sort(r) return r } diff --git a/vendor/github.com/open-policy-agent/opa/v1/util/performance.go b/vendor/github.com/open-policy-agent/opa/v1/util/performance.go index f269a2e1d7..ea31d16a34 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/util/performance.go +++ b/vendor/github.com/open-policy-agent/opa/v1/util/performance.go @@ -1,6 +1,10 @@ package util import ( + "bytes" + "cmp" + "encoding" + "io" "slices" "strconv" "strings" @@ -8,17 +12,19 @@ import ( "unsafe" ) +var emptyByteSlice = []byte{} + // SyncPool is a generic sync.Pool for type T, providing some convenience // over sync.Pool directly: [SyncPool.Put] ensures that nil values are not // put into the pool, and [SyncPool.Get] returns a pointer to T without having // to do a type assertion at the call site. type SyncPool[T any] struct { - pool sync.Pool + Pool sync.Pool } func NewSyncPool[T any]() *SyncPool[T] { return &SyncPool[T]{ - pool: sync.Pool{ + Pool: sync.Pool{ New: func() any { return new(T) }, @@ -27,12 +33,12 @@ func NewSyncPool[T any]() *SyncPool[T] { } func (p *SyncPool[T]) Get() *T { - return p.pool.Get().(*T) + return p.Pool.Get().(*T) } func (p *SyncPool[T]) Put(x *T) { if x != nil { - p.pool.Put(x) + p.Pool.Put(x) } } @@ -101,8 +107,15 @@ func ByteSliceToString(bs []byte) string { } // Allocation free conversion from ~string to []byte (unsafe) -// Note that the byte slice must not be modified after conversion +// Note that the byte slice must not be modified after conversion, and that it +// aliases the string's memory: it is a view of s, not a copy like []byte(s). func StringToByteSlice[T ~string](s T) []byte { + if len(s) == 0 { + // unsafe.StringData's return value is unspecified for the empty string, + // so don't build a slice on top of it. Doing so currently yields a nil + // slice, which callers may treat differently from an empty one. + return emptyByteSlice + } return unsafe.Slice(unsafe.StringData(string(s)), len(s)) } @@ -147,10 +160,32 @@ func NumDigitsUint(n uint64) int { } // AppendInt is a less messy version of strconv.AppendInt for base 10 ints. -func AppendInt(buf []byte, n int) []byte { +func AppendInt[T Integer](buf []byte, n T) []byte { return strconv.AppendInt(buf, int64(n), 10) } +// WriteInt writes the string form of n to out. +func WriteInt[T Integer](out io.Writer, n T) (int, error) { + var buf []byte + if b, ok := out.(*bytes.Buffer); ok { + buf = b.AvailableBuffer() + } + return out.Write(AppendInt(buf, n)) +} + +// WriteAppender writes the appended text of appender to out. +func WriteAppender[T encoding.TextAppender](out io.Writer, appender T) (int, error) { + var buf []byte + if b, ok := out.(*bytes.Buffer); ok { + buf = b.AvailableBuffer() + } + b, err := appender.AppendText(buf) + if err != nil { + return 0, err + } + return out.Write(b) +} + // Atoi is a convenience function for [Atoi64] where an int is preferable to an int64. // See the documentation of [Atoi64] for details on the performance benefits of this // function over strconv.Atoi. @@ -168,6 +203,8 @@ func Atoi(s string) (int, bool) { // codebase — most notably ast.Number's Int() and Int64() methods — have no interest in the // details of the failure, and keeping this allocation free means both methods can be used // not only for conversion, but as a most efficient "IsInt64" check. +// Additionally this function accepts trailing decimal zeroes ("10.00", not "10.01") as that +// makes sense in the context of us using JSON numbers. func Atoi64(s string) (int64, bool) { sLen := len(s) if sLen > 0 { @@ -180,9 +217,23 @@ func Atoi64(s string) (int64, bool) { return 0, false } + var pastDecimal bool var n int64 for _, ch := range []byte(s) { + if ch == '.' { + if !pastDecimal { + pastDecimal = true + continue + } + return 0, false + } ch -= '0' + if pastDecimal { + if ch == 0 { + continue + } + return 0, false + } if ch > 9 { return 0, false } @@ -205,7 +256,7 @@ func Atoi64(s string) (int64, bool) { // SplitMap calls fn for each delim-separated part of text and returns a slice of the results. // Cheaper than calling fn on strings.Split(text, delim), as it avoids allocating an intermediate slice of strings. -func SplitMap[T any](text string, delim string, fn func(string) T) []T { +func SplitMap[T any](text, delim string, fn func(string) T) []T { sl := make([]T, 0, strings.Count(text, delim)+1) for s := range strings.SplitSeq(text, delim) { sl = append(sl, fn(s)) @@ -265,3 +316,15 @@ func SortedFunc[T any, S ~[]T](s S, cmp func(a, b T) int) S { slices.SortFunc(s, cmp) return s } + +// SortedStableFunc is simply a shorthand for [slices.SortStableFunc] which also returns the sorted slice. +func SortedStableFunc[T any, S ~[]T](s S, cmp func(a, b T) int) S { + slices.SortStableFunc(s, cmp) + return s +} + +// Sorted is simply a shorthand for [slices.Sort] which also returns the sorted slice. +func Sorted[T cmp.Ordered, S ~[]T](s S) S { + slices.Sort(s) + return s +} diff --git a/vendor/github.com/open-policy-agent/opa/v1/util/slices.go b/vendor/github.com/open-policy-agent/opa/v1/util/slices.go index 57163a5649..6184feb15c 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/util/slices.go +++ b/vendor/github.com/open-policy-agent/opa/v1/util/slices.go @@ -4,6 +4,9 @@ package util +import "slices" + +// Map applies f to each element of s and returns a new slice containing the results. func Map[T any, U any](s []T, f func(T) U) []U { if s == nil { return nil @@ -14,3 +17,75 @@ func Map[T any, U any](s []T, f func(T) U) []U { } return r } + +// MapAppend applies f to each element of src and appends the results to dst, returning the resulting slice. +func MapAppend[T any, U any](dst []U, src []T, f func(T) U) []U { + if len(src) > 0 { + dst = slices.Grow(dst, len(src)) + for _, v := range src { + dst = append(dst, f(v)) + } + } + return dst +} + +// Every returns true if pred is true for every element of a, otherwise false. +// Returns true for empty / nil slices. +func Every[T any, S ~[]T](a S, pred func(T) bool) bool { + for _, v := range a { + if !pred(v) { + return false + } + } + return true +} + +// TryMap returns a new slice of type U by applying the function f to each element of the input slice s. +// If f returns an error for any element, the function aborts and returns the error. +func TryMap[T any, U any](s []T, f func(T) (U, error)) (r []U, err error) { + if s == nil { + return nil, nil + } + r = make([]U, len(s)) + for i, v := range s { + if r[i], err = f(v); err != nil { + return nil, err + } + } + return r, nil +} + +// ToSliceOf converts a slice of T to slice of R, via round-trip +// through any. Needless to say, T must be assignable to R. +func ToSliceOf[R, T any](s []T) []R { + if s == nil { + return nil + } + r := make([]R, len(s)) + for i, v := range s { + r[i] = any(v).(R) + } + return r +} + +// Count returns the number of elements in items that satisfy pred. +func Count[T any](pred func(T) bool, items ...T) (c int) { + for i := range items { + if pred(items[i]) { + c++ + } + } + return c +} + +// Not returns a new predicate function that negates the result of pred. +func Not[T any](pred func(T) bool) func(T) bool { + return func(v T) bool { + return !pred(v) + } +} + +// Identity returns the input value unchanged. +func Identity[T any](v T) T { + return v +} diff --git a/vendor/github.com/open-policy-agent/opa/v1/version/version.go b/vendor/github.com/open-policy-agent/opa/v1/version/version.go index caf4b9f59e..37415e463c 100644 --- a/vendor/github.com/open-policy-agent/opa/v1/version/version.go +++ b/vendor/github.com/open-policy-agent/opa/v1/version/version.go @@ -10,7 +10,7 @@ import ( "runtime/debug" ) -var Version = "1.19.1" +var Version = "1.21.0" // GoVersion is the version of Go this was built with var GoVersion = runtime.Version() diff --git a/vendor/github.com/vektah/gqlparser/v2/ast/value.go b/vendor/github.com/vektah/gqlparser/v2/ast/value.go index 83b471633d..a2f28db9ef 100644 --- a/vendor/github.com/vektah/gqlparser/v2/ast/value.go +++ b/vendor/github.com/vektah/gqlparser/v2/ast/value.go @@ -42,6 +42,18 @@ type ChildValue struct { Comment *CommentGroup } +// isUnsetVariable reports whether v is a variable reference with no supplied +// value and no default — it should be treated as absent, not null. +func (v *Value) isUnsetVariable(vars map[string]any) bool { + if v.Kind != Variable { + return false + } + if _, ok := vars[v.Raw]; ok { + return false + } + return v.VariableDefinition == nil || v.VariableDefinition.DefaultValue == nil +} + func (v *Value) Value(vars map[string]any) (any, error) { if v == nil { return nil, nil @@ -78,6 +90,9 @@ func (v *Value) Value(vars map[string]any) (any, error) { case ObjectValue: val := map[string]any{} for _, elem := range v.Children { + if elem.Value.isUnsetVariable(vars) { + continue + } elemVal, err := elem.Value.Value(vars) if err != nil { return val, err diff --git a/vendor/github.com/vektah/gqlparser/v2/gqlerror/error.go b/vendor/github.com/vektah/gqlparser/v2/gqlerror/error.go index b2ba01bbe5..0615f3d266 100644 --- a/vendor/github.com/vektah/gqlparser/v2/gqlerror/error.go +++ b/vendor/github.com/vektah/gqlparser/v2/gqlerror/error.go @@ -1,6 +1,7 @@ package gqlerror import ( + "encoding/json" "errors" "fmt" "strconv" @@ -35,6 +36,202 @@ type Location struct { Column int `json:"column,omitempty"` } +// SourceLocation pairs a GraphQL line and column with its source document. +// Source is nil when the location has no source document. +type SourceLocation struct { + Line int `json:"line,omitempty"` + Column int `json:"column,omitempty"` + Source *ast.Source `json:"-"` +} + +// ErrorWithSources is the source-aware validation error returned by the +// opt-in validator API. It retains the standard GraphQL error fields while +// Locations stores each location together with its source document. Source is +// omitted from JSON, leaving the standard GraphQL line and column fields. +type ErrorWithSources struct { + Err error `json:"-"` + Message string `json:"message"` + Path ast.Path `json:"path,omitempty"` + Locations []SourceLocation `json:"locations,omitempty"` + Extensions map[string]any `json:"extensions,omitempty"` + Rule string `json:"-"` + + legacyLocations bool +} + +// NewErrorWithSources pairs an existing GraphQL error with source-aware +// locations. The location slice is copied so callers cannot change the error's +// source associations by mutating their input slice. +func NewErrorWithSources(err *Error, locations []SourceLocation) *ErrorWithSources { + if err == nil { + return nil + } + legacyLocations := locations == nil + if len(locations) > 0 && len(err.Locations) > 0 { + if len(locations) != len(err.Locations) { + panic(fmt.Sprintf( + "gqlerror: source location count %d does not match location count %d", + len(locations), + len(err.Locations), + )) + } + for i, location := range err.Locations { + if locations[i].Line != location.Line || locations[i].Column != location.Column { + panic(fmt.Sprintf( + "gqlerror: source location %d does not match location coordinates", + i, + )) + } + } + } + if len(locations) == 0 && len(err.Locations) > 0 { + locations = make([]SourceLocation, len(err.Locations)) + for i, location := range err.Locations { + locations[i] = SourceLocation{ + Line: location.Line, + Column: location.Column, + } + } + } + return &ErrorWithSources{ + Err: err.Err, + Message: err.Message, + Path: err.Path, + Locations: append([]SourceLocation(nil), locations...), + Extensions: err.Extensions, + Rule: err.Rule, + legacyLocations: legacyLocations, + } +} + +// UnmarshalJSON discards source documents because GraphQL error JSON does not +// encode them. +func (err *ErrorWithSources) UnmarshalJSON(data []byte) error { + type errorWithoutMethods ErrorWithSources + err.Locations = nil + err.legacyLocations = true + return json.Unmarshal(data, (*errorWithoutMethods)(err)) +} + +func (err *ErrorWithSources) Error() string { + if err == nil { + return "" + } + locations := make([]Location, len(err.Locations)) + for i, sourceLocation := range err.Locations { + locations[i] = Location{ + Line: sourceLocation.Line, + Column: sourceLocation.Column, + } + } + base := &Error{ + Err: err.Err, + Message: err.Message, + Path: err.Path, + Locations: locations, + Extensions: cloneExtensions(err.Extensions), + Rule: err.Rule, + } + if base.Extensions == nil { + base.Extensions = map[string]any{} + } + filename, _ := base.Extensions["file"].(string) + if len(err.Locations) == 1 { + if filename == "" { + if source := err.Locations[0].Source; source != nil && source.Name != "" { + filename = source.Name + } + } + } else if len(err.Locations) > 1 { + if source := err.Locations[0].Source; source != nil { + filename = source.Name + } else if !err.legacyLocations { + filename = "" + } + } + if filename != "" { + base.Extensions["file"] = filename + } else { + delete(base.Extensions, "file") + } + return base.Error() +} + +func cloneExtensions(extensions map[string]any) map[string]any { + if extensions == nil { + return nil + } + clone := make(map[string]any, len(extensions)) + for key, value := range extensions { + clone[key] = value + } + return clone +} + +func (err *ErrorWithSources) Unwrap() error { + if err == nil { + return nil + } + return err.Err +} + +func (err *ErrorWithSources) AsError() error { + if err == nil { + return nil + } + return err +} + +// SourceLocations returns a shallow copy of the source-aware locations in +// validation order. +func (err *ErrorWithSources) SourceLocations() []SourceLocation { + if err == nil || len(err.Locations) == 0 { + return nil + } + locations := make([]SourceLocation, len(err.Locations)) + copy(locations, err.Locations) + return locations +} + +// SourceList is the result type returned by the opt-in source-aware validator +// APIs. +type SourceList []*ErrorWithSources + +func (errs SourceList) Error() string { + var buf strings.Builder + for _, err := range errs { + buf.WriteString(err.Error()) + buf.WriteByte('\n') + } + return buf.String() +} + +func (errs SourceList) Is(target error) bool { + for _, err := range errs { + if errors.Is(err, target) { + return true + } + } + return false +} + +func (errs SourceList) As(target any) bool { + for _, err := range errs { + if errors.As(err, target) { + return true + } + } + return false +} + +func (errs SourceList) Unwrap() []error { + l := make([]error, len(errs)) + for i, err := range errs { + l[i] = err + } + return l +} + type List []*Error func (err *Error) Error() string { diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/core/helpers.go b/vendor/github.com/vektah/gqlparser/v2/validator/core/helpers.go index f977d00a81..963eba6eaa 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/core/helpers.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/core/helpers.go @@ -28,6 +28,10 @@ func At(position *ast.Position) ErrorOption { Line: position.Line, Column: position.Column, }) + recordSourceLocation(err, gqlerror.Location{ + Line: position.Line, + Column: position.Column, + }, position.Src) if position.Src.Name != "" { err.SetFile(position.Src.Name) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/core/source_capture.go b/vendor/github.com/vektah/gqlparser/v2/validator/core/source_capture.go new file mode 100644 index 0000000000..3cc3b1ef1e --- /dev/null +++ b/vendor/github.com/vektah/gqlparser/v2/validator/core/source_capture.go @@ -0,0 +1,82 @@ +package core + +import ( + "fmt" + "sync" + + "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" +) + +type sourceCapture struct { + locations []gqlerror.SourceLocation +} + +// sourceCaptures bridges the legacy ErrorOption API: At receives only an +// *gqlerror.Error and cannot access CaptureSourceLocations' local capture. +// Entries exist only while source-aware options are being applied. +var sourceCaptures sync.Map // map[*gqlerror.Error]*sourceCapture + +// CaptureSourceLocations applies error options while recording the source +// documents supplied to At. It returns source-aware locations in the same +// order as err.Locations. +// Source-aware validation uses this helper; the regular validation API does +// not install a capture and keeps its existing behavior. +func CaptureSourceLocations(err *gqlerror.Error, apply func()) []gqlerror.SourceLocation { + if err == nil { + panic("gqlparser: cannot capture source locations for a nil error") + } + capture := &sourceCapture{} + sourceCaptures.Store(err, capture) + defer sourceCaptures.Delete(err) + + apply() + if len(err.Locations) == 0 { + if len(capture.locations) > 0 { + panic(fmt.Sprintf( + "gqlparser: captured source location %d does not match the final error locations", + 0, + )) + } + return nil + } + locations := make([]gqlerror.SourceLocation, len(err.Locations)) + for i, location := range err.Locations { + locations[i] = gqlerror.SourceLocation{ + Line: location.Line, + Column: location.Column, + } + } + recorded := 0 + for i, location := range err.Locations { + if recorded == len(capture.locations) { + break + } + captured := capture.locations[recorded] + if captured.Line != location.Line || captured.Column != location.Column { + continue + } + locations[i].Source = captured.Source + recorded++ + } + if recorded != len(capture.locations) { + panic(fmt.Sprintf( + "gqlparser: captured source location %d does not match the final error locations", + recorded, + )) + } + return locations +} + +func recordSourceLocation(err *gqlerror.Error, location gqlerror.Location, source *ast.Source) { + value, ok := sourceCaptures.Load(err) + if !ok { + return + } + capture := value.(*sourceCapture) + capture.locations = append(capture.locations, gqlerror.SourceLocation{ + Line: location.Line, + Column: location.Column, + Source: source, + }) +} diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/schema.go b/vendor/github.com/vektah/gqlparser/v2/validator/schema.go index f8d9472754..3a0d1377a5 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/schema.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/schema.go @@ -189,6 +189,10 @@ func ValidateSchemaDocument(sd *SchemaDocument) (*Schema, error) { return nil, err } + if err := validateInputObjectCircularRefs(&schema); err != nil { + return nil, err + } + if err := validateDirectiveDefinitions(&schema); err != nil { return nil, err } @@ -245,6 +249,75 @@ func validateTypeDefinitions(schema *Schema) *gqlerror.Error { return nil } +// validateInputObjectCircularRefs rejects input object cycles of non-null fields. +// https://spec.graphql.org/October2021/#sec-Input-Objects.Circular-References +func validateInputObjectCircularRefs(schema *Schema) *gqlerror.Error { + types := make([]string, 0, len(schema.Types)) + for typ := range schema.Types { + types = append(types, typ) + } + sort.Strings(types) + + // A type still on fieldPath means a cycle. A visited type was already checked. + visited := make(map[string]bool, len(schema.Types)) + fieldPath := make([]*FieldDefinition, 0, len(schema.Types)) + fieldPathIndexByTypeName := make(map[string]int, len(schema.Types)) + + var detectCycle func(def *Definition) *gqlerror.Error + detectCycle = func(def *Definition) *gqlerror.Error { + if visited[def.Name] { + return nil + } + visited[def.Name] = true + fieldPathIndexByTypeName[def.Name] = len(fieldPath) + + for _, field := range def.Fields { + // A nullable field or any list breaks the chain. + if !field.Type.NonNull || field.Type.NamedType == "" { + continue + } + fieldType := schema.Types[field.Type.NamedType] + if fieldType == nil || fieldType.Kind != InputObject { + continue + } + + fieldPath = append(fieldPath, field) + if cycleIndex, ok := fieldPathIndexByTypeName[fieldType.Name]; ok { + cyclePath := fieldPath[cycleIndex:] + fieldNames := make([]string, len(cyclePath)) + for i, cycleField := range cyclePath { + fieldNames[i] = cycleField.Name + } + return gqlerror.ErrorPosf( + cyclePath[0].Position, + "Cannot reference Input Object %s within itself through "+ + "a series of non-null fields: %s.", + strconv.Quote(fieldType.Name), + strconv.Quote(strings.Join(fieldNames, ".")), + ) + } + if err := detectCycle(fieldType); err != nil { + return err + } + fieldPath = fieldPath[:len(fieldPath)-1] + } + + delete(fieldPathIndexByTypeName, def.Name) + return nil + } + + for _, typ := range types { + def := schema.Types[typ] + if def.Kind != InputObject { + continue + } + if err := detectCycle(def); err != nil { + return err + } + } + return nil +} + func validateDirectiveDefinitions(schema *Schema) *gqlerror.Error { directives := make([]string, 0, len(schema.Directives)) for directive := range schema.Directives { diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/schema_test.yml b/vendor/github.com/vektah/gqlparser/v2/validator/schema_test.yml index b66e1e3d5a..589df95cc2 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/schema_test.yml +++ b/vendor/github.com/vektah/gqlparser/v2/validator/schema_test.yml @@ -386,6 +386,116 @@ inputs: message: 'UNION a: field must be one of SCALAR, ENUM, INPUT_OBJECT.' locations: [{line: 3, column: 13}] + - name: cannot reference itself through a non-null field + input: | + input T { + self: T! + } + error: + message: 'Cannot reference Input Object "T" within itself through a series of non-null fields: "self".' + locations: [{line: 2, column: 3}] + + - name: cannot reference itself through a chain of non-null fields + input: | + input A { + startLoop: B! + } + input B { + nextInLoop: C! + } + input C { + closeLoop: A! + } + error: + message: 'Cannot reference Input Object "A" within itself through a series of non-null fields: "startLoop.nextInLoop.closeLoop".' + locations: [{line: 2, column: 3}] + + - name: cannot reference itself through a non-null field added by an extension + input: | + input T { + id: ID + } + extend input T { + self: T! + } + error: + message: 'Cannot reference Input Object "T" within itself through a series of non-null fields: "self".' + locations: [{line: 5, column: 3}] + + - name: cannot reference itself through a non-null field on a nested cycle + input: | + input Outer { + inner: Inner! + } + input Inner { + self: Inner! + } + error: + message: 'Cannot reference Input Object "Inner" within itself through a series of non-null fields: "self".' + locations: [{line: 5, column: 3}] + + - name: cannot reference itself when an earlier input object is cycle free + input: | + input Entry { + maybe: Loop + } + input Loop { + self: Loop! + } + error: + message: 'Cannot reference Input Object "Loop" within itself through a series of non-null fields: "self".' + locations: [{line: 5, column: 3}] + + - name: can reference itself through a nullable field + input: | + input T { + self: T + } + + - name: can reference itself through a list field + input: | + input T { + a: [T!]! + b: [T]! + c: [T!] + d: [T] + e: [[T!]!]! + } + + - name: can reference itself when the chain is broken by a list + input: | + input A { + startLoop: B! + } + input B { + loopBack: [A!]! + } + + - name: can reference itself when the chain is broken by a nullable field + input: | + input A { + startLoop: B! + } + input B { + nextInLoop: C + } + input C { + closeLoop: A! + } + + - name: can reference the same input object twice without a cycle + input: | + input A { + b: B! + c: C! + } + input B { + c: C! + } + input C { + id: ID! + } + args: - name: Valid arg types input: | diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/validator.go b/vendor/github.com/vektah/gqlparser/v2/validator/validator.go index 9fb40d6a18..8efacf6769 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/validator.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/validator.go @@ -118,6 +118,16 @@ func Validate(schema *Schema, doc *QueryDocument, rules ...Rule) gqlerror.List { return errs } +// ValidateWithSources is the source-aware counterpart to Validate. It keeps +// source documents for every location recorded by the built-in At option while +// leaving Error and the regular validation API unchanged. +func ValidateWithSources(schema *Schema, doc *QueryDocument, rules ...Rule) gqlerror.SourceList { + if rules == nil { + rules = specifiedRules + } + return validateWithSources(schema, doc, rules) +} + func ValidateWithRules( schema *Schema, doc *QueryDocument, @@ -161,3 +171,59 @@ func ValidateWithRules( Walk(schema, doc, observers) return errs } + +// ValidateWithRulesWithSources is the source-aware counterpart to +// ValidateWithRules. +func ValidateWithRulesWithSources( + schema *Schema, + doc *QueryDocument, + rules *validatorrules.Rules, +) gqlerror.SourceList { + if rules == nil { + rules = validatorrules.NewDefaultRules() + } + + var currentRules []Rule //nolint:prealloc // would require extra local refs for len + for name, ruleFunc := range rules.GetInner() { + currentRules = append(currentRules, Rule{Name: name, RuleFunc: ruleFunc}) + // ensure deterministic order evaluation + sort.Sort(core.NameSorter(currentRules)) + } + return validateWithSources(schema, doc, currentRules) +} + +func validateWithSources(schema *Schema, doc *QueryDocument, rules []Rule) gqlerror.SourceList { + var errs gqlerror.SourceList + if schema == nil { + errs = append(errs, gqlerror.NewErrorWithSources( + gqlerror.Errorf("cannot validate as Schema is nil"), + nil, + )) + } + if doc == nil { + errs = append(errs, gqlerror.NewErrorWithSources( + gqlerror.Errorf("cannot validate as QueryDocument is nil"), + nil, + )) + } + if len(errs) > 0 { + return errs + } + + observers := &core.Events{} + for i := range rules { + rule := rules[i] + rule.RuleFunc(observers, func(options ...ErrorOption) { + err := &gqlerror.Error{Rule: rule.Name} + sources := core.CaptureSourceLocations(err, func() { + for _, option := range options { + option(err) + } + }) + errs = append(errs, gqlerror.NewErrorWithSources(err, sources)) + }) + } + + Walk(schema, doc, observers) + return errs +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 560217a8cb..a77d2c4f05 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -637,16 +637,11 @@ github.com/go-task/slim-sprig/v3 ## explicit; go 1.18 github.com/go-viper/mapstructure/v2 github.com/go-viper/mapstructure/v2/internal/errors -# github.com/gobwas/glob v0.2.3 -## explicit +# github.com/gobwas/glob v1.0.0 +## explicit; go 1.22.0 github.com/gobwas/glob -github.com/gobwas/glob/compiler -github.com/gobwas/glob/match +github.com/gobwas/glob/internal/debug github.com/gobwas/glob/syntax -github.com/gobwas/glob/syntax/ast -github.com/gobwas/glob/syntax/lexer -github.com/gobwas/glob/util/runes -github.com/gobwas/glob/util/strings # github.com/gobwas/httphead v0.1.0 ## explicit; go 1.15 github.com/gobwas/httphead @@ -905,8 +900,8 @@ github.com/leonelquinteros/gotext/plurals # github.com/lestrrat-go/blackmagic v1.0.4 ## explicit; go 1.23 github.com/lestrrat-go/blackmagic -# github.com/lestrrat-go/dsig v1.2.1 -## explicit; go 1.23.0 +# github.com/lestrrat-go/dsig v1.4.0 +## explicit; go 1.25.0 github.com/lestrrat-go/dsig github.com/lestrrat-go/dsig/internal/ecutil # github.com/lestrrat-go/dsig-secp256k1 v1.0.0 @@ -915,13 +910,13 @@ github.com/lestrrat-go/dsig-secp256k1 # github.com/lestrrat-go/httpcc v1.0.1 ## explicit; go 1.16 github.com/lestrrat-go/httpcc -# github.com/lestrrat-go/httprc/v3 v3.0.5 +# github.com/lestrrat-go/httprc/v3 v3.0.6 ## explicit; go 1.23.0 github.com/lestrrat-go/httprc/v3 github.com/lestrrat-go/httprc/v3/errsink github.com/lestrrat-go/httprc/v3/proxysink github.com/lestrrat-go/httprc/v3/tracesink -# github.com/lestrrat-go/jwx/v3 v3.1.1 +# github.com/lestrrat-go/jwx/v3 v3.3.0 ## explicit; go 1.25.0 github.com/lestrrat-go/jwx/v3 github.com/lestrrat-go/jwx/v3/cert @@ -944,6 +939,8 @@ github.com/lestrrat-go/jwx/v3/jwk/ecdsa github.com/lestrrat-go/jwx/v3/jwk/internal/registry github.com/lestrrat-go/jwx/v3/jwk/jwkbb github.com/lestrrat-go/jwx/v3/jws +github.com/lestrrat-go/jwx/v3/jws/internal/jwsbb +github.com/lestrrat-go/jwx/v3/jws/internal/keyalg github.com/lestrrat-go/jwx/v3/jws/internal/keytype github.com/lestrrat-go/jwx/v3/jws/jwsbb github.com/lestrrat-go/jwx/v3/jws/legacy @@ -1197,7 +1194,7 @@ github.com/olekukonko/errors github.com/olekukonko/ll github.com/olekukonko/ll/lh github.com/olekukonko/ll/lx -# github.com/olekukonko/tablewriter v1.1.4 +# github.com/olekukonko/tablewriter v1.1.5 ## explicit; go 1.21 github.com/olekukonko/tablewriter github.com/olekukonko/tablewriter/pkg/twcache @@ -1272,8 +1269,8 @@ github.com/onsi/gomega/matchers/support/goraph/edge github.com/onsi/gomega/matchers/support/goraph/node github.com/onsi/gomega/matchers/support/goraph/util github.com/onsi/gomega/types -# github.com/open-policy-agent/opa v1.19.1 -## explicit; go 1.25.0 +# github.com/open-policy-agent/opa v1.21.0 +## explicit; go 1.26.0 github.com/open-policy-agent/opa/ast github.com/open-policy-agent/opa/ast/json github.com/open-policy-agent/opa/bundle @@ -1315,6 +1312,7 @@ github.com/open-policy-agent/opa/internal/wasm/opcode github.com/open-policy-agent/opa/internal/wasm/sdk/opa/capabilities github.com/open-policy-agent/opa/internal/wasm/types github.com/open-policy-agent/opa/internal/wasm/util +github.com/open-policy-agent/opa/internal/yaml github.com/open-policy-agent/opa/loader github.com/open-policy-agent/opa/rego github.com/open-policy-agent/opa/storage @@ -2101,7 +2099,7 @@ github.com/urfave/cli/v2 ## explicit; go 1.24 github.com/valyala/fastjson github.com/valyala/fastjson/fastfloat -# github.com/vektah/gqlparser/v2 v2.5.36 +# github.com/vektah/gqlparser/v2 v2.5.37 ## explicit; go 1.22 github.com/vektah/gqlparser/v2/ast github.com/vektah/gqlparser/v2/gqlerror