diff --git a/go.mod b/go.mod index 605d9f81fa..ba66a3b562 100644 --- a/go.mod +++ b/go.mod @@ -3,38 +3,38 @@ module codeberg.org/thomas-mangin/ze go 1.26 require ( - charm.land/bubbles/v2 v2.1.0 - charm.land/bubbletea/v2 v2.0.2 - charm.land/lipgloss/v2 v2.0.2 + charm.land/bubbles/v2 v2.1.1 + charm.land/bubbletea/v2 v2.0.7 + charm.land/lipgloss/v2 v2.0.4 charm.land/wish/v2 v2.0.1 github.com/beevik/ntp v1.5.0 github.com/charmbracelet/colorprofile v0.4.3 github.com/charmbracelet/ssh v0.0.0-20250826160808-ebfa259c7309 - github.com/gaissmai/bart v0.26.1 + github.com/gaissmai/bart v0.29.0 github.com/google/nftables v0.3.0 github.com/insomniacslk/dhcp v0.0.0-20260326115832-991c7910cf36 github.com/miekg/dns v1.1.72 github.com/muesli/reflow v0.3.0 github.com/openconfig/goyang v1.6.3 github.com/packetcap/go-pcap v0.0.0-20251215121130-f2cf9f991e7c - github.com/prometheus/client_golang v1.23.2 + github.com/prometheus/client_golang v1.24.0 github.com/stretchr/testify v1.11.1 github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netns v0.0.5 go.fd.io/govpp v0.13.0 - golang.org/x/crypto v0.53.0 - golang.org/x/net v0.56.0 - golang.org/x/sys v0.46.0 - golang.org/x/term v0.44.0 - golang.org/x/tools v0.45.0 + golang.org/x/crypto v0.54.0 + golang.org/x/net v0.57.0 + golang.org/x/sys v0.47.0 + golang.org/x/term v0.45.0 + golang.org/x/tools v0.47.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/grpc v1.82.1 - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 google.golang.org/protobuf v1.36.11 ) require ( - github.com/cilium/ebpf v0.19.0 + github.com/cilium/ebpf v0.22.0 github.com/gokrazy/tools v0.0.0-20260703063348-3fe400c13246 github.com/gokrazy/updater v0.0.0-20260620140544-0a84d8ab3878 github.com/openconfig/gnmi v0.14.1 @@ -63,8 +63,8 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/keygen v0.5.4 // indirect - github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect - github.com/charmbracelet/x/ansi v0.11.6 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20260525132238-948f4557a654 // indirect + github.com/charmbracelet/x/ansi v0.11.7 // indirect github.com/charmbracelet/x/conpty v0.1.1 // indirect github.com/charmbracelet/x/errors v0.0.0-20251110184232-6ab307057ac7 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect @@ -73,36 +73,35 @@ require ( github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/creack/pty v1.1.24 - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff // indirect github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/josharian/native v1.1.0 // indirect - github.com/lucasb-eyer/go-colorful v1.3.0 // indirect + github.com/lucasb-eyer/go-colorful v1.4.0 // indirect github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe // indirect - github.com/mattn/go-runewidth v0.0.21 // indirect - github.com/mdlayher/genetlink v1.3.2 - github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 + github.com/mattn/go-runewidth v0.0.24 // indirect + github.com/mdlayher/genetlink v1.4.0 + github.com/mdlayher/netlink v1.9.0 github.com/mdlayher/packet v1.1.2 - github.com/mdlayher/socket v0.5.1 // indirect + github.com/mdlayher/socket v0.6.0 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 + github.com/prometheus/common v0.70.0 // indirect + github.com/prometheus/procfs v0.21.1 github.com/rivo/uniseg v0.4.7 // indirect - github.com/sirupsen/logrus v1.9.3 + github.com/sirupsen/logrus v1.9.4 github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect - golang.org/x/mod v0.37.0 - golang.org/x/sync v0.21.0 // indirect - golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 // indirect - golang.org/x/text v0.38.0 // indirect + golang.org/x/mod v0.38.0 + golang.org/x/sync v0.22.0 // indirect + golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57 // indirect + golang.org/x/text v0.40.0 // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 7d870c28f2..e39c9d43d2 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,9 @@ -charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g= -charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY= -charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0= -charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ= -charm.land/lipgloss/v2 v2.0.2 h1:xFolbF8JdpNkM2cEPTfXEcW1p6NRzOWTSamRfYEw8cs= -charm.land/lipgloss/v2 v2.0.2/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM= +charm.land/bubbles/v2 v2.1.1 h1:7r55WzBxpo/R3z98hGmY7KKPd3ET6vsf0Fb9sDHOV60= +charm.land/bubbles/v2 v2.1.1/go.mod h1:GE6M31gaWZVXzGw73OeuTTgy4lX+OtkH0E5ymnNsHxo= +charm.land/bubbletea/v2 v2.0.7 h1:7qw2tTAVar7m7klOPBYfTB0mniv/RuexsYwMRNxSeL0= +charm.land/bubbletea/v2 v2.0.7/go.mod h1:DGW2q8gvzHnOpMpZTORs0aySVHCox5C+2Svk0fci1qs= +charm.land/lipgloss/v2 v2.0.4 h1:lcPeVtcp23SNra7lHy8iYE4UC2aIipVQ47sbGyyxR5Q= +charm.land/lipgloss/v2 v2.0.4/go.mod h1:0653x8epbZSzdDfO/XPS1a/uYPOBeSsCssOpJOqDzik= charm.land/log/v2 v2.0.0 h1:SY3Cey7ipx86/MBXQHwsguOT6X1exT94mmJRdzTNs+s= charm.land/log/v2 v2.0.0/go.mod h1:c3cZSRqm20qUVVAR1WmS/7ab8bgha3C6G7DjPcaVZz0= charm.land/wish/v2 v2.0.1 h1:xYOsvQG/bYNRoKUBt0AEyMiCv9rksmCOG/fD5WifAzg= @@ -28,10 +28,10 @@ github.com/charmbracelet/keygen v0.5.4 h1:XQYgf6UEaTGgQSSmiPpIQ78WfseNQp4Pz8N/c1 github.com/charmbracelet/keygen v0.5.4/go.mod h1:t4oBRr41bvK7FaJsAaAQhhkUuHslzFXVjOBwA55CZNM= github.com/charmbracelet/ssh v0.0.0-20250826160808-ebfa259c7309 h1:dCVbCRRtg9+tsfiTXTp0WupDlHruAXyp+YoxGVofHHc= github.com/charmbracelet/ssh v0.0.0-20250826160808-ebfa259c7309/go.mod h1:R9cISUs5kAH4Cq/rguNbSwcR+slE5Dfm8FEs//uoIGE= -github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA= -github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98= -github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= -github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= +github.com/charmbracelet/ultraviolet v0.0.0-20260525132238-948f4557a654 h1:FpSYhY28ucg9ZRr+2wj67FAQ0Ey5yiK0072PmRDJNek= +github.com/charmbracelet/ultraviolet v0.0.0-20260525132238-948f4557a654/go.mod h1:hFpumms29Smx3LStRfku8vcCTBe1Kq8aCXtHUJa3mjY= +github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI= +github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs= github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk= github.com/charmbracelet/x/errors v0.0.0-20251110184232-6ab307057ac7 h1:4EG8pCHK5fa8dIxv97VHC8hdkJAz6QNm1WB9BuD/WhY= @@ -44,8 +44,8 @@ github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8 github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= -github.com/cilium/ebpf v0.19.0 h1:Ro/rE64RmFBeA9FGjcTc+KmCeY6jXmryu6FfnzPRIao= -github.com/cilium/ebpf v0.19.0/go.mod h1:fLCgMo3l8tZmAdM3B2XqdFzXBpwkcSTroaVqN08OWVY= +github.com/cilium/ebpf v0.22.0 h1:v2ktp0roffpMOj2MMf3idtCQZOsAoC4BJbAJN+ke2bY= +github.com/cilium/ebpf v0.22.0/go.mod h1:CDzZbe2hC5JjlDC+CY3KFCzlYwN4gbxppYM+Z10bQt4= github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= @@ -53,17 +53,16 @@ github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 h1:C7t6eeMaEQVy6e8CarIhscYQlNmw5e3G36y7l7Y21Ao= github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0/go.mod h1:56wL82FO0bfMU5RvfXoIwSOP2ggqqxT+tAfNEIyxuHw= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff h1:zk1wwii7uXmI0znwU+lqg+wFL9G5+vm5I+9rv2let60= github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff/go.mod h1:yUhRXHewUVJ1k89wHKP68xfzk7kwXUx/DV1nx4EBMbw= -github.com/gaissmai/bart v0.26.1 h1:+w4rnLGNlA2GDVn382Tfe3jOsK5vOr5n4KmigJ9lbTo= -github.com/gaissmai/bart v0.26.1/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c= +github.com/gaissmai/bart v0.29.0 h1:wO6HGE8g9YE0Wm0bCpYxwRzfQ4+fbJKOhL64e5ACGCI= +github.com/gaissmai/bart v0.29.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c= github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= @@ -112,23 +111,23 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= -github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= +github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe h1:ewr1srjRCmcQogPQ/NCx6XCk6LGVmsVCc9Y3vvPZj+Y= github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= -github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= -github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= +github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU= +github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mdlayher/genetlink v1.4.0 h1:f/Xs7Y2T+GyX9b3dbiUhnLE9InGs5F9RxJ2JwBMl71o= +github.com/mdlayher/genetlink v1.4.0/go.mod h1:d1hrKr8fwZU2JkcAtQUAzeTrI7nbgQSl+5k1cC0biSA= +github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco= +github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg= github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY= github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4= -github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= -github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/mdlayher/socket v0.6.0 h1:ScZPaAGyO1icQnbFrhPM8mnXyMu9qukC1K4ZoM2IQKU= +github.com/mdlayher/socket v0.6.0/go.mod h1:q7vozUAnxSqnjHc12Fik5yUKIzfZ8ITCfMkhOtE9z18= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= @@ -149,25 +148,25 @@ github.com/packetcap/go-pcap v0.0.0-20251215121130-f2cf9f991e7c h1:B5gWB1LB6Oxpo github.com/packetcap/go-pcap v0.0.0-20251215121130-f2cf9f991e7c/go.mod h1:1jryUz9E2ndKwZBNHzVhLMzS3WHO0fOKydYi9XWWu9w= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI= +github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLAMMI= +github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY= +github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= +github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sivchari/gomu v0.2.1 h1:w+CwPZXXeYFlLnbcNQ/3VbSClx5yf5pite56V+m9Xuo= github.com/sivchari/gomu v0.2.1/go.mod h1:kHwGRQvI3lCwm880iN9fwz/gT0oy9dc9SctSnmPZMis= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= @@ -175,10 +174,8 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= @@ -205,38 +202,37 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09 go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/crypto/x509roots/fallback v0.0.0-20260630172432-7626c5025624 h1:qtosn20yWXr+4B4x23stHOMEoDZrcSR3KjGBTNKV+74= golang.org/x/crypto/x509roots/fallback v0.0.0-20260630172432-7626c5025624/go.mod h1:+UoQFNBq2p2wO+Q6ddVtYc25GZ6VNdOMyyrd4nrqrKs= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 h1:HjU6IWBiAgRIdAJ9/y1rwCn+UELEmwV+VsTLzj/W4sE= -golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57 h1:nwGZBCt+FnXUrGsj5vjzAsEmkcaFvd82BbOjECiFYZc= +golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57/go.mod h1:3AWMyWHS+caVoiEXpiq6+tzKA40J4vQT3MYr80ZtQpc= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU= @@ -247,13 +243,12 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 h1:/WILD1UcXj/ujCxgoL/DvRgt2CP3txG8+FwkUbb9110= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1/go.mod h1:YNKnb2OAApgYn2oYY47Rn7alMr1zWjb2U8Q0aoGWiNc= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/charm.land/bubbletea/v2/.golangci.yml b/vendor/charm.land/bubbletea/v2/.golangci.yml index 929cb0ac99..c90f031611 100644 --- a/vendor/charm.land/bubbletea/v2/.golangci.yml +++ b/vendor/charm.land/bubbletea/v2/.golangci.yml @@ -33,6 +33,9 @@ linters: generated: lax presets: - common-false-positives + settings: + exhaustive: + default-signifies-exhaustive: true issues: max-issues-per-linter: 0 max-same-issues: 0 diff --git a/vendor/charm.land/bubbletea/v2/README.md b/vendor/charm.land/bubbletea/v2/README.md index 23cfad00ce..4d08d355bf 100644 --- a/vendor/charm.land/bubbletea/v2/README.md +++ b/vendor/charm.land/bubbletea/v2/README.md @@ -64,7 +64,7 @@ import will be the Bubble Tea library, which we'll call `tea` for short. ```go package main -// These imports will be used later on the tutorial. If you save the file +// These imports will be used later in the tutorial. If you save the file // now, Go might complain they are unused, but that's fine. // You may also need to run `go mod tidy` to download bubbletea and its // dependencies. @@ -397,6 +397,6 @@ of days past. Part of [Charm](https://charm.sh). -The Charm logo +The Charm logo Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة diff --git a/vendor/charm.land/bubbletea/v2/Taskfile.yaml b/vendor/charm.land/bubbletea/v2/Taskfile.yaml index 35072035df..10e47f306d 100644 --- a/vendor/charm.land/bubbletea/v2/Taskfile.yaml +++ b/vendor/charm.land/bubbletea/v2/Taskfile.yaml @@ -1,6 +1,6 @@ # https://taskfile.dev -version: '3' +version: "3" tasks: lint: @@ -11,4 +11,28 @@ tasks: test: desc: Run tests cmds: - - go test ./... {{.CLI_ARGS}} + - go test -race -count 4 -cpu 1,4 ./... {{.CLI_ARGS}} + + release: + desc: Create and push a new tag following semver + vars: + NEXT: + sh: svu next --always || go run github.com/caarlos0/svu/v3@latest next --always + prompt: "This will release {{.NEXT}}. Continue?" + preconditions: + - sh: '[ $(git symbolic-ref --short HEAD) = "main" ]' + msg: Not on main branch + - sh: "[ $(git status --porcelain=2 | wc -l) = 0 ]" + msg: "Git is dirty" + - sh: 'gh run list --workflow build.yml --commit $(git rev-parse HEAD) --status success --json conclusion -q ".[0].conclusion" | grep -q success' + msg: "Test build for this commit failed or not present" + cmds: + - task: fetch-tags + - git commit --allow-empty -m "{{.NEXT}}" + - git tag --annotate --sign -m "{{.NEXT}}" {{.NEXT}} {{.CLI_ARGS}} + - echo "Pushing {{.NEXT}}..." + - git push origin main --follow-tags + + fetch-tags: + cmds: + - git fetch --tags diff --git a/vendor/charm.land/bubbletea/v2/cursed_renderer.go b/vendor/charm.land/bubbletea/v2/cursed_renderer.go index c954873033..f45fee3205 100644 --- a/vendor/charm.land/bubbletea/v2/cursed_renderer.go +++ b/vendor/charm.land/bubbletea/v2/cursed_renderer.go @@ -61,7 +61,11 @@ func (s *cursedRenderer) setOptimizations(hardTabs, backspace, mapnl bool) { s.hardTabs = hardTabs s.backspace = backspace s.mapnl = mapnl - s.scr.SetTabStops(s.width) + if s.hardTabs { + s.scr.SetTabStops(s.width) + } else { + s.scr.SetTabStops(-1) + } s.scr.SetBackspace(s.backspace) s.scr.SetMapNewline(s.mapnl) s.mu.Unlock() @@ -131,10 +135,7 @@ func (s *cursedRenderer) start() { // Both can coexist; terminals ignore what they don't support. _, _ = s.scr.WriteString(ansi.SetModifyOtherKeys2) - kittyFlags := ansi.KittyDisambiguateEscapeCodes - if s.lastView.KeyboardEnhancements.ReportEventTypes { - kittyFlags |= ansi.KittyReportEventTypes - } + kittyFlags := keyboardEnhancementsFlags(s.lastView.KeyboardEnhancements) _, _ = s.scr.WriteString(ansi.KittyKeyboard(kittyFlags, 1)) } @@ -278,6 +279,11 @@ func (s *cursedRenderer) flush(closing bool) error { } } + // Restore tab stops if we have tab optimizations enabled. + if s.starting && s.hardTabs { + _, _ = s.scr.WriteString(ansi.SetTabEvery8Columns) + } + if !s.starting && !closing && s.lastView != nil && viewEquals(s.lastView, &view) && frameArea == s.cellbuf.Bounds() { // No changes, nothing to do. return nil @@ -379,10 +385,7 @@ func (s *cursedRenderer) flush(closing bool) error { // Enable modifyOtherKeys and Kitty keyboard protocol. _, _ = s.scr.WriteString(ansi.SetModifyOtherKeys2) - kittyFlags := ansi.KittyDisambiguateEscapeCodes // always enable basic key disambiguation - if view.KeyboardEnhancements.ReportEventTypes { - kittyFlags |= ansi.KittyReportEventTypes - } + kittyFlags := keyboardEnhancementsFlags(view.KeyboardEnhancements) _, _ = s.scr.WriteString(ansi.KittyKeyboard(kittyFlags, 1)) if !closing { // Request keyboard enhancements when they change @@ -593,7 +596,11 @@ func reset(s *cursedRenderer) { scr.SetColorProfile(s.profile) scr.SetRelativeCursor(true) // Always start in inline mode scr.SetFullscreen(false) // Always start in inline mode - scr.SetTabStops(s.width) + if s.hardTabs { + scr.SetTabStops(s.width) + } else { + scr.SetTabStops(-1) + } scr.SetBackspace(s.backspace) scr.SetMapNewline(s.mapnl) scr.SetScrollOptim(runtime.GOOS != "windows") // disable scroll optimization on Windows due to bugs in some terminals @@ -757,8 +764,14 @@ func (s *cursedRenderer) insertAbove(str string) error { // onMouse implements renderer. func (s *cursedRenderer) onMouse(m MouseMsg) Cmd { - if s.lastView != nil && s.lastView.OnMouse != nil { - return s.lastView.OnMouse(m) + var onMouse func(MouseMsg) Cmd + s.mu.Lock() + if s.lastView != nil { + onMouse = s.lastView.OnMouse + } + s.mu.Unlock() + if onMouse != nil { + return onMouse(m) } return nil } @@ -828,3 +841,20 @@ func viewEquals(a, b *View) bool { return true } + +func keyboardEnhancementsFlags(ke KeyboardEnhancements) int { + flags := 1 // always enable basic key disambiguation + if ke.ReportEventTypes { + flags |= ansi.KittyReportEventTypes + } + if ke.ReportAlternateKeys { + flags |= ansi.KittyReportAlternateKeys + } + if ke.ReportAllKeysAsEscapeCodes { + flags |= ansi.KittyReportAllKeysAsEscapeCodes + } + if ke.ReportAssociatedText { + flags |= ansi.KittyReportAssociatedKeys + } + return flags +} diff --git a/vendor/charm.land/bubbletea/v2/keyboard.go b/vendor/charm.land/bubbletea/v2/keyboard.go index dfc433496f..33747a4563 100644 --- a/vendor/charm.land/bubbletea/v2/keyboard.go +++ b/vendor/charm.land/bubbletea/v2/keyboard.go @@ -39,3 +39,21 @@ func (k KeyboardEnhancementsMsg) SupportsKeyDisambiguation() bool { func (k KeyboardEnhancementsMsg) SupportsEventTypes() bool { return k.Flags&ansi.KittyReportEventTypes != 0 } + +// SupportsAlternateKeys returns whether the terminal supports reporting +// alternate key codes. +func (k KeyboardEnhancementsMsg) SupportsAlternateKeys() bool { + return k.Flags&ansi.KittyReportAlternateKeys != 0 +} + +// SupportsAllKeysAsEscapeCodes returns whether the terminal supports reporting +// all keys as escape codes. +func (k KeyboardEnhancementsMsg) SupportsAllKeysAsEscapeCodes() bool { + return k.Flags&ansi.KittyReportAllKeysAsEscapeCodes != 0 +} + +// SupportsAssociatedText returns whether the terminal supports reporting +// associated text with key events. +func (k KeyboardEnhancementsMsg) SupportsAssociatedText() bool { + return k.Flags&ansi.KittyReportAssociatedKeys != 0 +} diff --git a/vendor/charm.land/bubbletea/v2/tea.go b/vendor/charm.land/bubbletea/v2/tea.go index e1dfc97d9f..e249d71bba 100644 --- a/vendor/charm.land/bubbletea/v2/tea.go +++ b/vendor/charm.land/bubbletea/v2/tea.go @@ -245,6 +245,25 @@ type KeyboardEnhancements struct { // [KeyPressMsg] with the [Key.IsRepeat] field set indicating that this is // a it's part of a key repeat sequence. ReportEventTypes bool + + // ReportAlternateKeys requests the terminal to report alternate key values + // in addition to the main ones. + // Note that only key events represented as escape codes will affected by + // this enhancement. + ReportAlternateKeys bool + + // ReportAllKeysAsEscapeCodes requests the terminal to report all key + // events, including plain text keys, as escape codes. + // When this is enabled, text won't be sent as plain text but instead as + // escape codes that encode the key value and modifiers. + ReportAllKeysAsEscapeCodes bool + + // ReportAssociatedText requests the terminal to report the text associated + // with key events. + // Note that this is an enhancement to + // [KeyboardEnhancements.ReportAllKeysAsEscapeCodes] and only has an effect + // if that is enabled. + ReportAssociatedText bool } // SetContent is a helper method to set the content of a [View] with a styled @@ -1328,8 +1347,10 @@ func (p *Program) RestoreTerminal() error { if err := p.initTerminal(); err != nil { return err } - if err := p.initInputReader(false); err != nil { - return err + if p.input != nil { + if err := p.initInputReader(false); err != nil { + return err + } } p.startRenderer() diff --git a/vendor/charm.land/bubbletea/v2/tty_unix.go b/vendor/charm.land/bubbletea/v2/tty_unix.go index bf1b49a185..4981a560a0 100644 --- a/vendor/charm.land/bubbletea/v2/tty_unix.go +++ b/vendor/charm.land/bubbletea/v2/tty_unix.go @@ -40,6 +40,7 @@ const suspendSupported = true func suspendProcess() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGCONT) + defer signal.Stop(c) _ = syscall.Kill(0, syscall.SIGTSTP) // blocks until a CONT happens... <-c diff --git a/vendor/charm.land/lipgloss/v2/.golangci.yml b/vendor/charm.land/lipgloss/v2/.golangci.yml index c90f031611..f2e6be0640 100644 --- a/vendor/charm.land/lipgloss/v2/.golangci.yml +++ b/vendor/charm.land/lipgloss/v2/.golangci.yml @@ -30,6 +30,9 @@ linters: - text: '(slog|log)\.\w+' linters: - noctx + - text: "naming" + linters: + - revive generated: lax presets: - common-false-positives diff --git a/vendor/charm.land/lipgloss/v2/README.md b/vendor/charm.land/lipgloss/v2/README.md index 16d4970547..4fda640039 100644 --- a/vendor/charm.land/lipgloss/v2/README.md +++ b/vendor/charm.land/lipgloss/v2/README.md @@ -893,7 +893,7 @@ thatColor := lightDark(lipgloss.Color("#37CD96"), lipgloss.Color("#22C78A")) a := lipgloss.NewStyle().Foreground(thisColor).Render("this") b := lipgloss.NewStyle().Foreground(thatColor).Render("that") -// Render the appriate colors at runtime: +// Render the appropriate colors at runtime: lipgloss.Fprintf(os.Stderr, "my fave colors are %s and %s", a, b) ``` diff --git a/vendor/charm.land/lipgloss/v2/position.go b/vendor/charm.land/lipgloss/v2/position.go index cea67763fb..ce91442d6f 100644 --- a/vendor/charm.land/lipgloss/v2/position.go +++ b/vendor/charm.land/lipgloss/v2/position.go @@ -39,7 +39,7 @@ func Place(width, height int, hPos, vPos Position, str string, opts ...Whitespac // PlaceHorizontal places a string or text block horizontally in an unstyled // block of a given width. If the given width is shorter than the max width of -// the string (measured by its longest line) this will be a noop. +// the string (measured by its longest line) this will be a noöp. func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string { lines, contentWidth := getLines(str) gap := width - contentWidth @@ -86,7 +86,7 @@ func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOpti // PlaceVertical places a string or text block vertically in an unstyled block // of a given height. If the given height is shorter than the height of the -// string (measured by its newlines) then this will be a noop. +// string (measured by its newlines) then this will be a noöp. func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string { contentHeight := strings.Count(str, "\n") + 1 gap := height - contentHeight diff --git a/vendor/charm.land/lipgloss/v2/query.go b/vendor/charm.land/lipgloss/v2/query.go index 25e0d9de99..96cd5b9454 100644 --- a/vendor/charm.land/lipgloss/v2/query.go +++ b/vendor/charm.land/lipgloss/v2/query.go @@ -54,13 +54,16 @@ func BackgroundColor(in term.File, out term.File) (bg color.Color, err error) { } // NOTE: On Unix, one of the given files must be a tty. + if !term.IsTerminal(in.Fd()) || !term.IsTerminal(out.Fd()) { + return nil, fmt.Errorf("input/output is not a terminal") + } for _, f := range []term.File{in, out} { if bg, err = backgroundColor(f, f); err == nil { return bg, nil } } - return + return bg, err } // HasDarkBackground detects whether the terminal has a light or dark diff --git a/vendor/charm.land/lipgloss/v2/wrap.go b/vendor/charm.land/lipgloss/v2/wrap.go index ca0a1e239b..ccffe89b18 100644 --- a/vendor/charm.land/lipgloss/v2/wrap.go +++ b/vendor/charm.land/lipgloss/v2/wrap.go @@ -63,6 +63,12 @@ func (w *WrapWriter) Link() uv.Link { // Write writes to the buffer. func (w *WrapWriter) Write(p []byte) (int, error) { + if w.p == nil { + // The writer has been closed and its parser returned to the pool. + // Writing after close can happen during out-of-order teardown of + // nested writer chains; treat it as a no-op rather than panicking. + return len(p), nil + } for i := range p { b := p[i] w.p.Advance(b) diff --git a/vendor/github.com/charmbracelet/ultraviolet/.golangci.yml b/vendor/github.com/charmbracelet/ultraviolet/.golangci.yml index c90f031611..64474889ef 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/.golangci.yml +++ b/vendor/github.com/charmbracelet/ultraviolet/.golangci.yml @@ -1,41 +1,42 @@ version: "2" -run: - tests: false linters: enable: - bodyclose - - exhaustive - - goconst - - godot - - gomoddirectives + # - exhaustive + # - goconst + # - godot + # - godox + # - gomoddirectives - goprintffuncname - - gosec + # - gosec - misspell - - nakedret - - nestif - - nilerr + # - nakedret + # - nestif + # - nilerr - noctx - nolintlint - - prealloc - - revive + # - prealloc + # - revive - rowserrcheck - sqlclosecheck + - staticcheck - tparallel - - unconvert - - unparam + # - unconvert + # - unparam - whitespace - - wrapcheck + # - wrapcheck + disable: + - errcheck + - ineffassign + - unused exclusions: + generated: lax + presets: + - common-false-positives rules: - text: '(slog|log)\.\w+' linters: - noctx - generated: lax - presets: - - common-false-positives - settings: - exhaustive: - default-signifies-exhaustive: true issues: max-issues-per-linter: 0 max-same-issues: 0 diff --git a/vendor/github.com/charmbracelet/ultraviolet/README.md b/vendor/github.com/charmbracelet/ultraviolet/README.md index eb83cc5ef5..b28ccdd611 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/README.md +++ b/vendor/github.com/charmbracelet/ultraviolet/README.md @@ -7,108 +7,141 @@ Build Status

-Ultraviolet is a set of primitives for manipulating terminal emulators, with a focus on terminal user interfaces (TUIs). It provides a set of tools and abstractions for interaction that can handle user input and display dynamic, cell-based content. It’s the product of many years of research, development, collaboration and ingenuity. +Ultraviolet is a set of primitives for building terminal user interfaces in Go. +It provides cell-based rendering, cross-platform input handling, and a diffing +renderer inspired by [ncurses](https://invisible-island.net/ncurses/)—without +the need for `terminfo` or `termcap` databases. -Ultraviolet is not a framework by design, however it can be used standalone to create powerful terminal applications. It’s in use in production and powers critical portions of [Bubble Tea v2][bbt] and [Lip Gloss v2][lg], and was instrumental in the development of [Crush][crush]. +Ultraviolet powers [Bubble Tea v2][bbt] and [Lip Gloss v2][lg]. It replaces the +ad-hoc terminal primitives from earlier versions with a cohesive, imperative API +that can also be used standalone. -[crush]: https://github.com/charmbracelet/crush [bbt]: https://github.com/charmbracelet/bubbletea [lg]: https://github.com/charmbracelet/lipgloss -> [!CAUTION] -> This project currently exists to serve internal use cases. API stability is a goal, but expect no stability guarantees as of now. +## Install -## Features +```bash +go get github.com/charmbracelet/ultraviolet@latest +``` + +## Quick Start + +```go +package main + +import ( + "log" -Ultraviolet is built with several core features in mind to make terminal -application development easy and performant: + uv "github.com/charmbracelet/ultraviolet" + "github.com/charmbracelet/ultraviolet/screen" +) -### 👺 The Cursed Renderer +func main() { + t := uv.DefaultTerminal() + scr := t.Screen() -The cell-based rendering model—called _The Cursed Render_—was inspired by the infamous -[ncurses](https://invisible-island.net/ncurses/) library, which has been an -essential part of terminal applications for decades. Ultraviolet takes this -concept and modernizes it for the Go programming language, providing a more -ergonomic and efficient way to work with terminal cells without the need for -archaic technologies like `terminfo` or `termcap` databases. + scr.EnterAltScreen() -Unlike ncurses, it supports both full-window and inline use-cases as we see inline TUIs as important in maintaining user context and flow. + if err := t.Start(); err != nil { + log.Fatalf("failed to start terminal: %v", err) + } + defer t.Stop() -### 🏎️ High Speeds and Low Bandwidth + ctx := screen.NewContext(scr) + text := "Hello, World!" + textWidth := scr.StringWidth(text) -The built-in terminal renderer efficiently handles content updates by utilizing -a powerful cell-based diffing algorithm that minimizes the amount of data -written to the terminal using various ANSI escape sequences to accomplish this. -This allows applications to update only the parts of the terminal that have -changed, significantly improving performance and responsiveness. + display := func() { + screen.Clear(scr) + bounds := scr.Bounds() + x := (bounds.Dx() - textWidth) / 2 + y := bounds.Dy() / 2 + ctx.DrawString(text, x, y) + scr.Render() + scr.Flush() + } -In practical terms, Ultraviolet optimizes for fast redraws that use minimal data transfer. This is very important locally and critically important over the network (for example, via SSH). + for ev := range t.Events() { + switch ev := ev.(type) { + case uv.WindowSizeEvent: + scr.Resize(ev.Width, ev.Height) + display() + case uv.KeyPressEvent: + if ev.MatchString("q", "ctrl+c") { + return + } + } + } +} +``` -### 💬 Universal Input +## Architecture -Input handling in terminals can be complex, especially when dealing with -multiple input sources, different platforms, and ancient terminal baggage. -Ultraviolet simplifies this by providing a unified interface for handling user -input, allowing developers to focus on building their applications without -getting bogged down in the intricacies of terminal input handling. +Ultraviolet is organized as a set of layered primitives: -### 🎮 Cross-Platform Compatibility +- **Terminal** — manages the application lifecycle: raw mode, input event loop, + start/stop. Create one with `DefaultTerminal()` or `NewTerminal(console, opts)`. -Ultraviolet is designed to work seamlessly across different platforms and -terminal emulators. It abstracts away the differences in terminal capabilities -and provides a consistent API for developers to work with, ensuring that -applications built with Ultraviolet will run smoothly on various systems. +- **TerminalScreen** — the screen state manager. Handles rendering, alternate + screen buffer, cursor, mouse modes, keyboard enhancements, bracketed paste, + window title, and more. Access it via `terminal.Screen()`. -On Windows, it uses the [Windows Console API](https://learn.microsoft.com/en-us/windows/console/console-functions) to -provide a consistent experience, while on Unix-like systems, it relies on the -standard Termios API along with ANSI escape sequences to manipulate the -terminal. +- **Screen** — a minimal interface (`Bounds`, `CellAt`, `SetCell`, + `WidthMethod`) implemented by `TerminalScreen`, `Buffer`, `Window`, and + `ScreenBuffer`. Write code against `Screen` to stay decoupled from the + terminal. -In short: Ultraviolet provides first-class support for both Unix and Windows-based systems. +- **Buffer / Window** — off-screen cell buffers. `Buffer` is a flat grid of + cells. `Window` adds parent/child relationships and shared-buffer views. + Both implement `Screen` and `Drawable`. -### 🧩 Extensible Architecture +- **screen package** — drawing helpers that operate on any `Screen`: a + `Context` for styled text rendering (`Print`, `DrawString`, etc.) and + utility functions like `Clear`, `Fill`, `Clone`. + +- **layout package** — a constraint-based layout solver built on the + [Cassowary algorithm][casso]. Partition screen space with `Len`, `Min`, + `Max`, `Percent`, `Ratio`, and `Fill` constraints. + +[casso]: https://en.wikipedia.org/wiki/Cassowary_(software) + +## Features -Ultraviolet is built with extensibility in mind, providing a solid API that can -be embedded into other applications or used as a foundation for building custom -terminal user interfaces. It allows developers to create their own components, -styles, and behaviors, making it a versatile tool for building terminal -applications. +- **Cell-based diffing renderer** — only redraws what changed. Optimizes + cursor movement, uses ECH/REP/ICH/DCH when available, and supports scroll + optimizations. Minimal bandwidth, critical for SSH. -## FAQ +- **Universal input** — unified keyboard and mouse event handling across + platforms. Supports legacy encodings, Kitty keyboard protocol, SGR mouse, + and Windows Console input. -### 🐈 What about other Charm libraries? +- **Inline and fullscreen** — works in both alternate screen (fullscreen) and + inline mode. Inline TUIs preserve terminal context and scrollback. -Ultraviolet is not a replacement for existing libraries like [Bubble Tea](https://github.com/charmbracelet/bubbletea) or [Lip -Gloss](https://github.com/charmbracelet/lipgloss). Instead, it serves as a -foundation for the latest versions of both of these libraries and others like them, providing the -underlying primitives and abstractions needed to build terminal user interfaces -applications and frameworks. +- **Cross-platform** — first-class support for Unix (termios + ANSI) and + Windows (Console API). Consistent behavior across terminal emulators. -### 🛁 How is it different from Bubble Tea? +- **Suspend/resume** — `Stop()` and `Start()` can be called repeatedly for + suspend/resume cycles, shelling out to editors, or process suspension + via `uv.Suspend()`. -Ultraviolet is a lower-level library that focuses on the core primitives of -terminal manipulation, rendering, and input handling. It provides the building -blocks for creating terminal applications, while Bubble Tea is a higher-level -framework that builds on top of Ultraviolet to provide a more structured and -opinionated way to build terminal user interfaces. +## Examples -### 💋 Is it a replacement for Lip Gloss? +See the [`examples/`](./examples/) directory for core examples and +[`examples/advanced/`](./examples/advanced/) for more complex demos. -Simply put, no. Ultraviolet is not a replacement for Lip Gloss. Instead, it -provides the underlying rendering capabilities that Lip Gloss can use to create -styled terminal content. Lip Gloss is a higher-level library that builds on top -of Ultraviolet by utilizing the cell-based rendering model to provide a -simplified and ergonomic way to create styled terminal content and composition -of terminal user interfaces. +## Tutorial -## ✏️ Tutorial +See [TUTORIAL.md](./TUTORIAL.md) for a step-by-step guide to building your +first Ultraviolet application. -You can find a simple tutorial on how to create a UV application that displays -"Hello, World!" on the screen in the [TUTORIAL.md](./TUTORIAL.md) file. +> [!NOTE] +> Ultraviolet is in active development. The API may change. -## Whatcha think? +## Feedback -We’d love to hear your thoughts on this project. Feel free to drop us a note! +We'd love to hear your thoughts on this project. Feel free to drop us a note! - [Twitter](https://twitter.com/charmcli) - [Discord](https://charm.land/discord) diff --git a/vendor/github.com/charmbracelet/ultraviolet/TUTORIAL.md b/vendor/github.com/charmbracelet/ultraviolet/TUTORIAL.md index 6556172138..d456d5a490 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/TUTORIAL.md +++ b/vendor/github.com/charmbracelet/ultraviolet/TUTORIAL.md @@ -1,199 +1,167 @@ -# Hello World! +# Tutorial: Hello World -This is a simple tutorial on how to create a UV application that displays -"Hello, World!" on the screen. UV is a terminal UI toolkit that allows you to -create terminal applications with ease. It provides a simple API to handle -screen management, input handling, and rendering content. +This tutorial walks through building a simple Ultraviolet application that +displays centered "Hello, World!" text. You'll learn the core concepts: +creating a terminal, managing a screen, handling input events, and rendering. -## Tutorial +## Creating a Terminal -What does UV consist of? A UV application consists of a screen that displays -content, has some sort of input sources that can be used to interact with the -screen, and is meant to display content or programs on the screen. - -First, we need to create a screen that will display our program. UV comes with -a `Terminal` screen that is used to display content on a terminal. +A `Terminal` manages the console, input event loop, and screen state. ```go -t := uv.NewTerminal(os.Stdin, os.Stdout, os.Environ()) -// Or simply use... -// t := uv.DefaultTerminal() +t := uv.DefaultTerminal() ``` -A terminal screen has a few properties that are unique to it. For example, a -terminal screen can go into raw mode, which is important to disable echoing of -input characters, and to disable signal handling so that we can receive things -like ctrl+c without the terminal interfering with our program. +You can also create a terminal with a custom console and options: -Another important property of a terminal screen is the alternate screen buffer. -This property puts the terminal screen into a special mode that allows us to -display content without interfering with the normal screen buffer. +```go +con := uv.NewConsole(os.Stdin, os.Stdout, os.Environ()) +t := uv.NewTerminal(con, &uv.Options{ + Logger: myLogger, // optional, for debugging I/O +}) +``` -In this tutorial, we will use the alternate screen buffer to display our -program so that we don't affect the normal screen buffer. +## Getting the Screen -```go -// Set the terminal to raw mode. -if err := t.MakeRaw(); err != nil { - log.Fatal(err) -} +The terminal's screen is where you draw content, manage the alternate screen +buffer, and set cells. -// Enter the alternate screen buffer. This will -// only take affect once we flush or display -// our program on the terminal screen. -t.EnterAltScreen() +```go +scr := t.Screen() +``` -// My program -// ... +## Alternate Screen -// Make sure we leave the alternate screen buffer when we -// are done with our program. This will be called automatically -// when we use [t.Shutdown(ctx)] later. -t.LeaveAltScreen() +The alternate screen buffer lets your application display content without +affecting the user's scrollback. Most fullscreen TUIs use it. -// Make sure we restore the terminal to its original state -// before we exit. We don't care about errors here, but you -// can handle them if you want. This will be called automatically -// when we use [t.Shutdown(ctx)] later. -_ = t.Restore() //nolint:errcheck +```go +scr.EnterAltScreen() ``` -Now that we have our screen set to raw mode and in the alternate screen buffer, -we can create our program that will be displayed on the screen. A program is an -abstraction layer that handles different screen types and implementations. It -only cares about displaying content on the screen. +## Starting the Terminal -We need to start our program before we can display anything on the screen. This -will ensure that the program and screen are initialized and ready to display -content. Internally, this will also call `t.Start()` to start the terminal -screen. +Starting puts the console into raw mode (disabling echoing and line buffering +so we receive individual keypresses like ctrl+c), initializes the +input event loop, and prepares the screen for rendering. ```go if err := t.Start(); err != nil { - log.Fatalf("failed to start program: %v", err) + log.Fatalf("failed to start terminal: %v", err) } +defer t.Stop() ``` -Let's display a simple frame with some text in it. A frame is a container that -holds the buffer we're displaying. The final cursor position we want our cursor -to be at, and the viewport area we are working with to display our content. +`Stop()` restores the console, exits the alternate screen, and cleans up. It +is safe to call multiple times and supports suspend/resume cycles. + +## Drawing Text + +You can set individual cells directly: ```go for i, r := range "Hello, World!" { - // We iterate over the string to display each character - // in a separate cell. Ideally, we want each cell - // to have exactly one grapheme. In this case, since - // we're using a simple ASCII string, we know that - // each character is a single grapheme with a width of 1. - var c uv.Cell - c.Content = string(r) - c.Width = 1 - t.SetCell(i, 0, &c) + scr.SetCell(i, 0, &uv.Cell{Content: string(r), Width: 1}) } -// Now we simply render the changes and flush them -// to the terminal screen. -_ = p.Display() ``` -Different screen models have different ways to receive input. Some models have -a remote control, while others have a touch screen. A terminal can receive -input from various peripherals usually through control codes and escape -sequences. Our terminal has a `t.Events(ctx)` method that returns a channel -which will receive events from different terminal input sources. +Or use the `screen` helper package for convenience: ```go -// We want to be able to stop the terminal input loop -// whenever we call cancel(). -ctx, cancel := context.WithCancel(context.Background()) -defer cancel() - -for ev := range t.Events(ctx) { - switch ev := ev.(type) { - case uv.WindowSizeEvent: - // Our terminal screen is resizable. This is important - // as we want to inform our terminal screen with the - // size we'd like to display our program in. - // When we're using the full terminal window size, - // we can assume that the terminal screen will - // also have the same size as our program. - // However, with inline programs, usually we want - // the height to be the height of our program. - // So if our inline program takes 10 lines, we - // want to resize the terminal screen to 10 lines - // high. - width, height := ev.Width, ev.Height - if !altscreen { - height = 10 - } - t.Resize(width, height) - case uv.KeyPressEvent: - if ev.MatchStrings("q", "ctrl+c") { - // This will stop the input loop and cancel the context. - cancel() - } - } +ctx := screen.NewContext(scr) +ctx.DrawString("Hello, World!", 0, 0) +``` + +The `Context` supports styled text, links, and wrapping. It implements +`io.Writer`, so you can use `fmt.Fprint` and friends. + +## Rendering + +Drawing to the screen is a two-step process: + +1. **Render** — computes the minimal diff between the current and new screen + state, writing ANSI escape sequences to an internal buffer. +2. **Flush** — writes the buffer to the terminal. This is the only step that + performs real I/O and can return an error. + +```go +scr.Render() +if err := scr.Flush(); err != nil { + log.Fatalf("flush failed: %v", err) } ``` -Now that we've handled displaying our program and receiving input from the -terminal, we need to handle the program's lifecycle. We need to make sure that -we restore the terminal to its original state when we exit the program. A -terminal program can be stopped gracefully using the `t.Shutdown(ctx)` method. +## Handling Events + +The terminal provides a channel of input events. Range over it to process +keyboard, mouse, and resize events: ```go -// We need to make sure we stop the program gracefully -// after we exit the input loop. -if err := t.Shutdown(ctx); err != nil { - log.Fatal(err) +for ev := range t.Events() { + switch ev := ev.(type) { + case uv.WindowSizeEvent: + scr.Resize(ev.Width, ev.Height) + case uv.KeyPressEvent: + if ev.MatchString("q", "ctrl+c") { + return + } + } } ``` -Finally, let's put everything together and create a simple program that displays -a frame with "Hello, World!" in it. The program will exit when we press -ctrl+c or q. +`MatchString` accepts key names and modifier combinations like `"ctrl+a"`, +`"shift+enter"`, or `"alt+tab"`. + +## Putting It Together + +Here's the complete program—centered "Hello, World!" that redraws on resize +and exits on `q` or `ctrl+c`: ```go +package main + +import ( + "log" + + uv "github.com/charmbracelet/ultraviolet" + "github.com/charmbracelet/ultraviolet/screen" +) + func main() { - t := uv.NewTerminal(os.Stdin, os.Stdout, os.Environ()) + t := uv.DefaultTerminal() + scr := t.Screen() - if err := t.MakeRaw(); err != nil { - log.Fatalf("failed to make terminal raw: %v", err) - } + scr.EnterAltScreen() if err := t.Start(); err != nil { - log.Fatalf("failed to start program: %v", err) + log.Fatalf("failed to start terminal: %v", err) + } + defer t.Stop() + + ctx := screen.NewContext(scr) + text := "Hello, World!" + textWidth := scr.StringWidth(text) + + display := func() { + screen.Clear(scr) + bounds := scr.Bounds() + x := (bounds.Dx() - textWidth) / 2 + y := bounds.Dy() / 2 + ctx.DrawString(text, x, y) + scr.Render() + scr.Flush() } - t.EnterAltScreen() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - for ev := range t.Events(ctx) { + for ev := range t.Events() { switch ev := ev.(type) { case uv.WindowSizeEvent: - width, height := ev.Width, ev.Height - t.Erase() - t.Resize(width, height) + scr.Resize(ev.Width, ev.Height) + display() case uv.KeyPressEvent: - if ev.MatchStrings("q", "ctrl+c") { - cancel() + if ev.MatchString("q", "ctrl+c") { + return } } - - for i, r := range "Hello, World!" { - var c uv.Cell - c.Content = string(r) - c.Width = 1 - t.SetCell(i, 0, &c) - } - if err := t.Display(); err != nil { - log.Fatal(err) - } - } - - if err := t.Shutdown(ctx); err != nil { - log.Fatal(err) } } ``` diff --git a/vendor/github.com/charmbracelet/ultraviolet/buffer.go b/vendor/github.com/charmbracelet/ultraviolet/buffer.go index d13afbc970..db8f6b8885 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/buffer.go +++ b/vendor/github.com/charmbracelet/ultraviolet/buffer.go @@ -374,7 +374,12 @@ func (b *Buffer) FillArea(c *Cell, area Rectangle) { // Clear clears the buffer with space cells and rectangle. func (b *Buffer) Clear() { - b.ClearArea(b.Bounds()) + area := b.Bounds() + for y := area.Min.Y; y < area.Max.Y; y++ { + for x := area.Min.X; x < area.Max.X; x++ { + b.Lines[y][x] = EmptyCell + } + } } // ClearArea clears the buffer with space cells within the specified @@ -392,12 +397,14 @@ func (b *Buffer) CloneArea(area Rectangle) *Buffer { } n := NewBuffer(area.Dx(), area.Dy()) for y := area.Min.Y; y < area.Max.Y; y++ { - for x := area.Min.X; x < area.Max.X; x++ { + for x := area.Min.X; x < area.Max.X; { c := b.CellAt(x, y) if c == nil || c.IsZero() { + x++ continue } n.SetCell(x-area.Min.X, y-area.Min.Y, c) + x += max(c.Width, 1) } } return n @@ -697,11 +704,14 @@ func (b *RenderBuffer) TouchedLines() int { // SetCell sets the cell at the given x, y position and marks the line as // touched. func (b *RenderBuffer) SetCell(x, y int, c *Cell) { - if !cellEqual(b.CellAt(x, y), c) { + if p := b.CellAt(x, y); !cellEqual(p, c) { width := 1 if c != nil && c.Width > 0 { width = c.Width } + if p != nil && p.Width > 0 { + width = max(width, p.Width) + } b.TouchLine(x, y, width) } b.Buffer.SetCell(x, y, c) @@ -786,3 +796,37 @@ func (b *RenderBuffer) DeleteCellArea(x, y, n int, c *Cell, area Rectangle) { } b.TouchLine(x, y, n) } + +// Clear clears the buffer with space cells and marks all lines as touched. +func (b *RenderBuffer) Clear() { + b.Buffer.Clear() + w := b.Width() + for y := range b.Lines { + b.TouchLine(0, y, w) + } +} + +// ClearArea clears the buffer with space cells within the specified rectangle +// and marks the affected lines as touched. +func (b *RenderBuffer) ClearArea(area Rectangle) { + b.Buffer.ClearArea(area) + w := area.Max.X - area.Min.X + for y := area.Min.Y; y < area.Max.Y; y++ { + b.TouchLine(area.Min.X, y, w) + } +} + +// Fill fills the buffer with the given cell and marks all lines as touched. +func (b *RenderBuffer) Fill(c *Cell) { + b.FillArea(c, b.Bounds()) +} + +// FillArea fills the buffer with the given cell within the specified rectangle +// and marks the affected lines as touched. +func (b *RenderBuffer) FillArea(c *Cell, area Rectangle) { + b.Buffer.FillArea(c, area) + w := area.Max.X - area.Min.X + for y := area.Min.Y; y < area.Max.Y; y++ { + b.TouchLine(area.Min.X, y, w) + } +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/doc.go b/vendor/github.com/charmbracelet/ultraviolet/doc.go index af352053c0..27b8a25c35 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/doc.go +++ b/vendor/github.com/charmbracelet/ultraviolet/doc.go @@ -1,3 +1,46 @@ -// Package uv (Ultraviolet) is a library for creating terminal-based user +// Package uv (Ultraviolet) provides primitives for building terminal user // interfaces in Go. +// +// It offers cell-based screen buffers, a diffing terminal renderer, cross-platform +// input decoding (keyboard, mouse, paste, focus, resize), and a windowing model +// with off-screen buffers. Ultraviolet powers Bubble Tea v2 and Lip Gloss v2. +// +// # Getting Started +// +// Create a terminal, get its screen, and run an event loop: +// +// t := uv.DefaultTerminal() +// scr := t.Screen() +// scr.EnterAltScreen() +// +// if err := t.Start(); err != nil { +// log.Fatal(err) +// } +// defer t.Stop() +// +// for ev := range t.Events() { +// switch ev := ev.(type) { +// case uv.WindowSizeEvent: +// scr.Resize(ev.Width, ev.Height) +// case uv.KeyPressEvent: +// if ev.MatchString("q") { return } +// } +// } +// +// # Rendering +// +// Drawing is a two-step process. [TerminalScreen.Render] diffs the screen and +// writes escape sequences to an internal buffer. [TerminalScreen.Flush] sends +// the buffer to the terminal—this is the only method that performs real I/O. +// +// scr.Render() +// scr.Flush() +// +// Use the screen package for high-level drawing helpers like +// [screen.Context.DrawString] and [screen.Clear]. +// +// # Packages +// +// - screen — drawing context and screen manipulation helpers +// - layout — constraint-based layout solver (Cassowary algorithm) package uv diff --git a/vendor/github.com/charmbracelet/ultraviolet/event.go b/vendor/github.com/charmbracelet/ultraviolet/event.go index bf48d147c5..2d0d8cf202 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/event.go +++ b/vendor/github.com/charmbracelet/ultraviolet/event.go @@ -94,7 +94,7 @@ type MultiEvent []Event func (e MultiEvent) String() string { var sb strings.Builder for _, ev := range e { - sb.WriteString(fmt.Sprintf("%v\n", ev)) + fmt.Fprintf(&sb, "%v\n", ev) } return sb.String() } diff --git a/vendor/github.com/charmbracelet/ultraviolet/mouse.go b/vendor/github.com/charmbracelet/ultraviolet/mouse.go index e86d94b8d3..a74cdf992a 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/mouse.go +++ b/vendor/github.com/charmbracelet/ultraviolet/mouse.go @@ -4,16 +4,37 @@ import ( "github.com/charmbracelet/x/ansi" ) -// MouseMode represents the mouse mode for the terminal. It is used to enable -// or disable mouse support on the terminal. -type MouseMode byte +// MouseMode represents the mouse tracking mode for the terminal. +type MouseMode uint8 -// Mouse modes. +// Mouse tracking modes. +// +// These determine which mouse events the terminal reports. +const ( + MouseModeNone MouseMode = iota // Disable mouse tracking. + MouseModePress // Press only (DEC mode 9). Reports button press events. + MouseModeClick // Click tracking (DEC mode 1000). Reports button press and release. + MouseModeDrag // Drag tracking (DEC mode 1002). Reports press, release, and drag. + MouseModeMotion // Motion tracking (DEC mode 1003). Reports all mouse events including motion. +) + +// MouseEncoding represents the encoding used for mouse events. +type MouseEncoding uint8 + +// Mouse encodings. +// +// These determine how mouse coordinates and buttons are encoded in the +// terminal's escape sequences. The encoding is only meaningful when mouse +// tracking is enabled via [MouseMode]. const ( - MouseModeNone MouseMode = iota - MouseModeClick - MouseModeDrag - MouseModeMotion + MouseEncodingLegacy MouseEncoding = iota // Legacy X10-compatible encoding. Coordinates limited to 223. + MouseEncodingSGR // SGR encoding (DEC mode 1006). No coordinate limit, distinguishes press/release. + MouseEncodingSGRPixel // SGR-pixel encoding (DEC mode 1016). Reports pixel coordinates. + + // TODO: support these additional encodings in the future. + // MouseEncodingUTF8 // UTF-8 encoding (DEC mode 1005). Coordinates limited to 223. + // MouseEncodingUrxvt // urxvt encoding (DEC mode 1015). No coordinate limit. + // MouseEncodingSGRPixel // SGR-pixel encoding (DEC mode 1016). Reports pixel coordinates. ) // MouseButton represents the button that was pressed during a mouse message. @@ -55,7 +76,8 @@ const ( // messages. // // The X and Y coordinates are zero-based, with (0,0) being the upper left -// corner of the terminal. +// corner of the terminal. When using [MouseEncodingSGRPixel] (DEC mode 1016), +// X and Y are in pixel coordinates; otherwise they are in cell coordinates. // // // Catch all mouse events // switch Event := Event.(type) { @@ -96,3 +118,33 @@ func (m Mouse) String() (s string) { return s } + +// MousePixelToCell converts a mouse event with pixel coordinates to cell +// coordinates. +// +// This is only meaningful when using [MouseEncodingSGRPixel] encoding, which +// reports mouse coordinates in pixels rather than cell units. The conversion +// is based on the terminal's reported pixel dimensions and cell dimensions. +// +// On Windows, or other platforms where [Terminal.GetWinsize] doesn't report +// pixel dimensions, you can query for the terminal window pixel dimensions via +// [ansi.WindowOp](4) and get back a [PixelSizeEvent] with the pixel +// dimensions. You can then construct a [Winsize] struct with the pixel +// dimensions and the cell dimensions from [Terminal.GetWinsize] and pass it to +// this function. +func MousePixelToCell(m Mouse, ws *Winsize) Mouse { + var col, row int + if ws.Xpixel > 0 { + col = m.X * int(ws.Col) / int(ws.Xpixel) + } + if ws.Ypixel > 0 { + row = m.Y * int(ws.Row) / int(ws.Ypixel) + } + + return Mouse{ + X: col, + Y: row, + Button: m.Button, + Mod: m.Mod, + } +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/styled.go b/vendor/github.com/charmbracelet/ultraviolet/styled.go index baf346bd53..540f6463ad 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/styled.go +++ b/vendor/github.com/charmbracelet/ultraviolet/styled.go @@ -203,6 +203,12 @@ func printString[T []byte | string]( // Advance the state and data state = newState str = str[n:] + + if y >= bounds.Max.Y { + // We've reached the bottom of the bounds, stop processing further + // lines. + break + } } // Make sure to set the last cell if it's not empty. diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal.go b/vendor/github.com/charmbracelet/ultraviolet/terminal.go index a019516323..bf79dce9f0 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/terminal.go +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "os/signal" - "sync" "time" "golang.org/x/sync/errgroup" @@ -51,6 +50,10 @@ type Options struct { // // This is disabled by default. UseTerminfoKeys bool + + // Logger is an optional logger for tracing terminal I/O operations. + // If nil, no logging is performed. + Logger Logger } // DefaultOptions returns the default [Terminal] options. @@ -64,16 +67,15 @@ func DefaultOptions() *Options { // Terminal represents an interactive terminal application. type Terminal struct { - con Console - opts *Options - scr *TerminalScreen - pr pollReader - buf []byte - evc chan Event - errg errgroup.Group - winch chan os.Signal - donec chan struct{} - logger Logger + con Console + opts *Options + scr *TerminalScreen + pr pollReader + buf []byte + evc chan Event + errg errgroup.Group + winch chan os.Signal + donec chan struct{} } // DefaultTerminal creates a new [Terminal] instance using the default standard @@ -124,13 +126,34 @@ func NewTerminal(con Console, opts *Options) *Terminal { // These channels never close during the terminal's lifetime. t.evc = make(chan Event) t.winch = make(chan os.Signal, 1) // buffered to avoid missing signals + if opts.Logger != nil { + t.scr.rend.SetLogger(opts.Logger) + } return t } -// SetLogger sets the terminal's logger for tracing I/O operations. -func (t *Terminal) SetLogger(logger Logger) { - t.logger = logger - t.scr.rend.SetLogger(logger) +// GetSize returns the current size of the terminal in characters and pixels. +func (t *Terminal) GetSize() (width, height int, err error) { + w, h, err := t.con.GetSize() + if err != nil { + return 0, 0, fmt.Errorf("getting terminal size: %w", err) + } + return int(w), int(h), nil +} + +// GetWinsize returns the current size of the terminal as a [Winsize] struct. +// This includes both character dimensions (columns and rows) and pixel +// dimensions (xpixel and ypixel). +// +// Note that this only returns the pixel dimensions on Unix-like systems that +// support the TIOCGWINSZ ioctl. On other platforms, the pixel dimensions may +// be zero or not available. +func (t *Terminal) GetWinsize() (*Winsize, error) { + ws, err := t.con.GetWinsize() + if err != nil { + return nil, fmt.Errorf("getting terminal winsize: %w", err) + } + return ws, nil } // Screen returns the terminal's screen. @@ -156,8 +179,8 @@ func (t *Terminal) Start() error { if evs.lookup { evs.table = buildKeysTable(t.opts.LegacyKeyEncoding, t.con.Getenv("TERM"), t.opts.UseTerminfoKeys) } - if t.logger != nil { - evs.setLogger(t.logger) + if t.opts.Logger != nil { + evs.setLogger(t.opts.Logger) } bufc := make(chan []byte) t.donec = make(chan struct{}) @@ -266,9 +289,7 @@ func (t *Terminal) Start() error { }) // Restore any previous screen state. - if err := t.scr.Restore(); err != nil { - return fmt.Errorf("failed to restore terminal screen: %w", err) - } + t.scr.Restore() if err := t.scr.Flush(); err != nil { return fmt.Errorf("failed to flush terminal screen: %w", err) } @@ -285,22 +306,27 @@ func (t *Terminal) Wait() error { return nil } -// Stop stops the terminal event loop. This is a non-blocking call. Use -// [Terminal.Wait] to wait for the terminal to exit. +// Stop stops the terminal event loop. It is safe to call Stop without a +// prior [Terminal.Start], and safe to call Stop multiple times in a row. +// After Stop returns, [Terminal.Start] may be called again to resume. func (t *Terminal) Stop() error { - sync.OnceFunc(func() { - close(t.donec) - }) - signal.Stop(t.winch) + if t.donec != nil { + select { + case <-t.donec: + // Already closed. + default: + close(t.donec) + } + } + if t.winch != nil { + signal.Stop(t.winch) + } if t.pr != nil { t.pr.Cancel() _ = t.pr.Close() + t.pr = nil } - if err := t.scr.Reset(); err != nil { - _ = t.scr.Flush() - _ = t.con.Restore() - return fmt.Errorf("failed to reset terminal screen: %w", err) - } + t.scr.Reset() if err := t.scr.Flush(); err != nil { _ = t.con.Restore() return fmt.Errorf("failed to flush terminal screen: %w", err) diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go index f8dc288b96..b3b79cd008 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go @@ -8,6 +8,7 @@ import ( "io" "strconv" "strings" + "sync" "time" "unicode" "unicode/utf16" @@ -142,7 +143,11 @@ func (d *TerminalReader) StreamEvents(ctx context.Context, eventc chan<- Event) timeout := time.NewTimer(d.EscTimeout) ttimeout := time.Now().Add(d.EscTimeout) + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() if err := d.streamData(ctx, readc); err != nil { if errors.Is(err, io.EOF) || errors.Is(err, cancelreader.ErrCanceled) { errc <- nil @@ -157,9 +162,11 @@ func (d *TerminalReader) StreamEvents(ctx context.Context, eventc chan<- Event) select { case <-ctx.Done(): d.sendEvents(eventc, buf.Bytes(), true) + wg.Wait() return nil case err := <-errc: d.sendEvents(eventc, buf.Bytes(), true) + wg.Wait() return err // return the first error encountered case <-timeout.C: d.logf("timeout reached") diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go index 119dae1726..8a526b9567 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go @@ -295,13 +295,13 @@ func peekNConsoleInputs(console windows.Handle, maxEvents uint32) ([]xwindows.In func keyEventString(vkc, sc uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) string { var s strings.Builder s.WriteString("vkc: ") - s.WriteString(fmt.Sprintf("%d, 0x%02x", vkc, vkc)) + fmt.Fprintf(&s, "%d, 0x%02x", vkc, vkc) s.WriteString(", sc: ") - s.WriteString(fmt.Sprintf("%d, 0x%02x", sc, sc)) + fmt.Fprintf(&s, "%d, 0x%02x", sc, sc) s.WriteString(", r: ") - s.WriteString(fmt.Sprintf("%q 0x%x", r, r)) + fmt.Fprintf(&s, "%q 0x%x", r, r) s.WriteString(", down: ") - s.WriteString(fmt.Sprintf("%v", keyDown)) + fmt.Fprintf(&s, "%v", keyDown) s.WriteString(", cks: [") if cks&xwindows.LEFT_ALT_PRESSED != 0 { s.WriteString("left alt, ") @@ -331,6 +331,6 @@ func keyEventString(vkc, sc uint16, r rune, keyDown bool, cks uint32, repeatCoun s.WriteString("enhanced key, ") } s.WriteString("], repeat count: ") - s.WriteString(fmt.Sprintf("%d", repeatCount)) + fmt.Fprintf(&s, "%d", repeatCount) return s.String() } diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_renderer.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_renderer.go index af3d8ee9b3..aad5a0ef29 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/terminal_renderer.go +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_renderer.go @@ -137,7 +137,6 @@ type TerminalRenderer struct { cur, saved cursor // the current and saved cursors flags tFlag // terminal writer flags. term string // the terminal type - scrollHeight int // keeps track of how many lines we've scrolled down (inline mode) clear bool // whether to force clear the screen caps capabilities // terminal control sequence capabilities atPhantom bool // whether the cursor is out of bounds and at a phantom cell @@ -167,7 +166,6 @@ func NewTerminalRenderer(w io.Writer, env []string) (s *TerminalRenderer) { s.caps = xtermCaps(s.term) s.cur = cursor{Cell: EmptyCell, Position: Pos(-1, -1)} // start at -1 to force a move s.saved = s.cur - s.scrollHeight = 0 s.oldhash, s.newhash = nil, nil return } @@ -368,9 +366,7 @@ func (s *TerminalRenderer) moveCursor(newbuf *RenderBuffer, x, y int, overwrite _ = s.buf.WriteByte('\r') s.cur.X, s.cur.Y = 0, 0 } - seq, scrollHeight := moveCursor(s, newbuf, x, y, overwrite) - // If we scrolled the screen, we need to update the scroll height. - s.scrollHeight = max(s.scrollHeight, scrollHeight) + seq := moveCursor(s, newbuf, x, y, overwrite) _, _ = s.buf.WriteString(seq) s.cur.X, s.cur.Y = x, y } @@ -682,7 +678,7 @@ func (s *TerminalRenderer) putRange(newbuf *RenderBuffer, oldLine, newLine Line, var j, same int for j, same = start, 0; j <= end; j++ { oldCell, newCell := oldLine.At(j), newLine.At(j) - if same == 0 && oldCell != nil && oldCell.IsZero() { + if same == 0 && oldCell != nil && oldCell.IsZero() && newCell.IsZero() { continue } if cellEqual(oldCell, newCell) { @@ -941,6 +937,7 @@ func (s *TerminalRenderer) transformLine(newbuf *RenderBuffer, y int) { for next != nil && next.IsZero() { n++ oLastCell++ + next = newLine.At(n + 1) } } @@ -1218,7 +1215,7 @@ func (s *TerminalRenderer) Render(newbuf *RenderBuffer) { } } - if !s.flags.Contains(tFullscreen) && s.scrollHeight < newHeight-1 { + if !s.flags.Contains(tFullscreen) && (curWidth != newWidth || curHeight != newHeight) { s.move(newbuf, 0, newHeight-1) } @@ -1258,7 +1255,6 @@ func (s *TerminalRenderer) Resize(width, _ int) { if s.tabs != nil { s.tabs.Resize(width) } - s.scrollHeight = 0 } // Position returns the cursor position in the screen buffer after applying any @@ -1314,9 +1310,8 @@ func notLocal(cols, fx, fy, tx, ty int) bool { // // It is safe to call this function with a nil [Buffer]. In that case, it won't // use any optimizations that require the new buffer such as overwrite. -func relativeCursorMove(s *TerminalRenderer, newbuf *RenderBuffer, fx, fy, tx, ty int, overwrite, useTabs, useBackspace bool) (string, int) { +func relativeCursorMove(s *TerminalRenderer, newbuf *RenderBuffer, fx, fy, tx, ty int, overwrite, useTabs, useBackspace bool) string { var seq strings.Builder - var scrollHeight int if newbuf == nil { overwrite = false // We can't overwrite the current buffer. } @@ -1332,10 +1327,8 @@ func relativeCursorMove(s *TerminalRenderer, newbuf *RenderBuffer, fx, fy, tx, t if cud := ansi.CursorDown(n); yseq == "" || len(cud) < len(yseq) { yseq = cud } - shouldScroll := !s.flags.Contains(tFullscreen) && ty > s.scrollHeight - if shouldScroll || n < len(yseq) { // n is the cost of using newline characters + if !s.flags.Contains(tFullscreen) || n < len(yseq) { // n is the cost of using newline characters yseq = strings.Repeat("\n", n) - scrollHeight = ty if s.flags.Contains(tMapNewline) { fx = 0 } @@ -1419,8 +1412,6 @@ func relativeCursorMove(s *TerminalRenderer, newbuf *RenderBuffer, fx, fy, tx, t if cell != nil && cell.Width > 0 { ovw += cell.String() i += cell.Width - 1 - } else { - ovw += " " } } } @@ -1462,7 +1453,7 @@ func relativeCursorMove(s *TerminalRenderer, newbuf *RenderBuffer, fx, fy, tx, t seq.WriteString(xseq) } - return seq.String(), scrollHeight + return seq.String() } // moveCursor moves and returns the cursor movement sequence to move the cursor @@ -1472,7 +1463,7 @@ func relativeCursorMove(s *TerminalRenderer, newbuf *RenderBuffer, fx, fy, tx, t // // It is safe to call this function with a nil [Buffer]. In that case, it won't // use any optimizations that require the new buffer such as overwrite. -func moveCursor(s *TerminalRenderer, newbuf *RenderBuffer, x, y int, overwrite bool) (seq string, scrollHeight int) { +func moveCursor(s *TerminalRenderer, newbuf *RenderBuffer, x, y int, overwrite bool) (seq string) { fx, fy := s.cur.X, s.cur.Y if !s.flags.Contains(tRelativeCursor) { @@ -1490,7 +1481,7 @@ func moveCursor(s *TerminalRenderer, newbuf *RenderBuffer, x, y int, overwrite b // Method #0: Use [ansi.CUP] if the distance is long. seq = ansi.CursorPosition(x+1, y+1) if fx == -1 || fy == -1 || width == -1 || notLocal(width, fx, fy, x, y) { - return seq, 0 + return seq } } @@ -1514,32 +1505,29 @@ func moveCursor(s *TerminalRenderer, newbuf *RenderBuffer, x, y int, overwrite b useBackspace := i&1 != 0 // Method #1: Use local movement sequences. - nseq1, nscrollHeight1 := relativeCursorMove(s, newbuf, fx, fy, x, y, overwrite, useHardTabs, useBackspace) + nseq1 := relativeCursorMove(s, newbuf, fx, fy, x, y, overwrite, useHardTabs, useBackspace) if (i == 0 && len(seq) == 0) || len(nseq1) < len(seq) { seq = nseq1 - scrollHeight = max(scrollHeight, nscrollHeight1) } // Method #2: Use [ansi.CR] and local movement sequences. - nseq2, nscrollHeight2 := relativeCursorMove(s, newbuf, 0, fy, x, y, overwrite, useHardTabs, useBackspace) + nseq2 := relativeCursorMove(s, newbuf, 0, fy, x, y, overwrite, useHardTabs, useBackspace) nseq2 = "\r" + nseq2 if len(nseq2) < len(seq) { seq = nseq2 - scrollHeight = max(scrollHeight, nscrollHeight2) } if !s.flags.Contains(tRelativeCursor) { // Method #3: Use [ansi.CursorHomePosition] and local movement sequences. - nseq3, nscrollHeight3 := relativeCursorMove(s, newbuf, 0, 0, x, y, overwrite, useHardTabs, useBackspace) + nseq3 := relativeCursorMove(s, newbuf, 0, 0, x, y, overwrite, useHardTabs, useBackspace) nseq3 = ansi.CursorHomePosition + nseq3 if len(nseq3) < len(seq) { seq = nseq3 - scrollHeight = max(scrollHeight, nscrollHeight3) } } } - return seq, scrollHeight + return seq } // xtermCaps returns whether the terminal is xterm-like. This means that the diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_screen.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_screen.go index 99cd0c773d..9fdc32816e 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/terminal_screen.go +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_screen.go @@ -29,11 +29,14 @@ type TerminalScreen struct { keyboardEnhancements *KeyboardEnhancements bracketedPaste bool mouseMode MouseMode + mouseEncoding MouseEncoding cursor *Cursor // initial state is cursor hidden backgroundColor color.Color foregroundColor color.Color progressBar *ProgressBar windowTitle string + syncUpdates bool // mode 2026 + resetTabs bool // DECST8C - reset terminal tabs on start } var _ Screen = (*TerminalScreen)(nil) @@ -42,7 +45,7 @@ var _ Screen = (*TerminalScreen)(nil) func NewTerminalScreen(w io.Writer, env Environ) *TerminalScreen { s := &TerminalScreen{} s.buf = &bytes.Buffer{} - s.win = NewScreen(0, 0) + s.win = NewWindow(0, 0, nil) s.w = w s.profile = colorprofile.Detect(w, env) s.rend = NewTerminalRenderer(s.buf, env) @@ -52,21 +55,26 @@ func NewTerminalScreen(w io.Writer, env Environ) *TerminalScreen { s.rbuf = NewRenderBuffer(0, 0) s.env = env - if f, err := os.OpenFile("uv_debug.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644); err == nil { - log.SetOutput(f) - s.rend.SetLogger(log.Default()) + if debugFile := env.Getenv("UV_DEBUG"); debugFile != "" { + if f, err := os.OpenFile(debugFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644); err == nil { + log.SetOutput(f) + s.rend.SetLogger(log.Default()) + } } // Configure renderer optimizations based on console settings. f, ok := w.(term.File) if ok { state, err := term.GetState(f.Fd()) - if err == nil { + if err == nil || isWindows { // Windows supports tabs and backspace by default, so we can ignore errors here. useTabs, useBspace := optimizeMovements(state) if useTabs { s.rend.SetTabStops(0) // the width will be set after calling [TerminalScreen.Resize] + } else { + s.rend.SetTabStops(-1) } s.rend.SetBackspace(useBspace) + s.resetTabs = useTabs } } // XXX: Do we still need map nl to crlf handling in the renderer? @@ -89,6 +97,34 @@ func (s *TerminalScreen) Bounds() Rectangle { return s.win.Bounds() } +// Width returns the width of the terminal screen. +// +// Note that this is not the actual width of the terminal window, but rather +// the width of the screen we're managing. The actual width of the terminal +// window can be obtained using [Terminal.GetSize] or by reading the "COLUMNS" +// environment variable. +func (s *TerminalScreen) Width() int { + return s.win.Width() +} + +// Height returns the height of the terminal screen. +// +// Note that this is not the actual height of the terminal window, but rather +// the height of the screen we're managing. The actual height of the terminal +// window can be obtained using [Terminal.GetSize] or by reading the "LINES" +// environment variable. +func (s *TerminalScreen) Height() int { + return s.win.Height() +} + +// StringWidth returns the cell width of the given string using the terminal +// screen's width method. This accounts for the configured [WidthMethod] +// (e.g. wcwidth vs grapheme width) so callers don't need to import ansi +// directly. +func (s *TerminalScreen) StringWidth(str string) int { + return s.win.WidthMethod().StringWidth(str) +} + // WidthMethod returns the width method used by the terminal screen. func (s *TerminalScreen) WidthMethod() WidthMethod { return s.win.WidthMethod() @@ -109,13 +145,12 @@ func (s *TerminalScreen) SetColorProfile(profile colorprofile.Profile) { // Resize resizes the terminal screen to the specified width and height, // updating the render buffer and renderer accordingly. -func (s *TerminalScreen) Resize(width, height int) error { +func (s *TerminalScreen) Resize(width, height int) { s.win.Resize(width, height) s.rbuf.Resize(width, height) s.rend.Resize(width, height) s.rend.Erase() s.rbuf.Touched = nil - return nil } // Display clears the screen and draws the given [Drawable] onto the terminal @@ -128,9 +163,7 @@ func (s *TerminalScreen) Display(d Drawable) error { s.win.Clear() d.Draw(s, s.win.Bounds()) } - if err := s.Render(); err != nil { - return err - } + s.Render() return s.Flush() } @@ -139,7 +172,7 @@ func (s *TerminalScreen) Display(d Drawable) error { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) Render() error { +func (s *TerminalScreen) Render() { for y := 0; y < s.win.Height(); y++ { for x := 0; x < s.win.Width(); { cell := s.win.CellAt(x, y) @@ -156,7 +189,7 @@ func (s *TerminalScreen) Render() error { } } s.rend.Render(s.rbuf) - return s.rend.Flush() + _ = s.rend.Flush() } // Flush writes any pending output to the underlying writer. @@ -176,7 +209,38 @@ func (s *TerminalScreen) Flush() error { } } - _, err := s.w.Write(s.buf.Bytes()) + var buf bytes.Buffer + buf.Grow(s.buf.Len()) + + if s.buf.Len() > 0 { + if s.syncUpdates { + buf.Grow(len(ansi.SetModeSynchronizedOutput) + len(ansi.ResetModeSynchronizedOutput)) + + // If synchronized updates are enabled, we need to wrap the output in + // the appropriate control sequences to ensure that the terminal treats + // it as a single atomic update. This is necessary to prevent flickering + // and other visual artifacts that can occur when multiple updates are sent + // separately. + buf.WriteString(ansi.SetModeSynchronizedOutput) + } else if s.cursor != nil && !s.cursor.Hidden { + buf.Grow(len(ansi.HideCursor) + len(ansi.ShowCursor)) + + // If synchronized updates are not enabled, we need to ensure that + // the cursor is hidden before writing any output to prevent + // unwanted cursor visual artifacts. + buf.WriteString(ansi.HideCursor) + } + + buf.Write(s.buf.Bytes()) + + if s.syncUpdates { + buf.WriteString(ansi.ResetModeSynchronizedOutput) + } else if s.cursor != nil && !s.cursor.Hidden { + buf.WriteString(ansi.ShowCursor) + } + } + + _, err := s.w.Write(buf.Bytes()) if err != nil { return err } @@ -190,7 +254,7 @@ func (s *TerminalScreen) Flush() error { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) EnterAltScreen() error { +func (s *TerminalScreen) EnterAltScreen() { var sb strings.Builder sb.WriteString(ansi.SetModeAltScreenSaveCursor) if s.cursor == nil || s.cursor.Hidden { @@ -199,12 +263,9 @@ func (s *TerminalScreen) EnterAltScreen() error { sb.WriteString(ansi.ShowCursor) } if s.keyboardEnhancements != nil { - _ = EncodeKeyboardEnhancements(&sb, s.keyboardEnhancements) - } - _, err := s.buf.WriteString(sb.String()) - if err != nil { - return err + EncodeKeyboardEnhancements(&sb, s.keyboardEnhancements) } + s.buf.WriteString(sb.String()) if !s.altScreen { s.rend.SaveCursor() @@ -213,8 +274,6 @@ func (s *TerminalScreen) EnterAltScreen() error { s.rend.SetRelativeCursor(false) s.altScreen = true } - - return nil } // ExitAltScreen switches the terminal back to the main screen buffer, restoring @@ -222,7 +281,7 @@ func (s *TerminalScreen) EnterAltScreen() error { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) ExitAltScreen() error { +func (s *TerminalScreen) ExitAltScreen() { var sb strings.Builder sb.WriteString(ansi.ResetModeAltScreenSaveCursor) if s.cursor == nil || s.cursor.Hidden { @@ -231,12 +290,9 @@ func (s *TerminalScreen) ExitAltScreen() error { sb.WriteString(ansi.ShowCursor) } if s.keyboardEnhancements != nil { - _ = EncodeKeyboardEnhancements(&sb, s.keyboardEnhancements) - } - _, err := s.buf.WriteString(sb.String()) - if err != nil { - return err + EncodeKeyboardEnhancements(&sb, s.keyboardEnhancements) } + s.buf.WriteString(sb.String()) if s.altScreen { s.rend.RestoreCursor() @@ -245,8 +301,6 @@ func (s *TerminalScreen) ExitAltScreen() error { s.rend.SetRelativeCursor(true) s.altScreen = false } - - return nil } // AltScreen returns whether the terminal is currently in the alternate screen @@ -259,36 +313,24 @@ func (s *TerminalScreen) AltScreen() bool { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) HideCursor() error { - _, err := s.buf.WriteString(ansi.HideCursor) - if err != nil { - return err - } - +func (s *TerminalScreen) HideCursor() { + s.buf.WriteString(ansi.HideCursor) if s.cursor != nil { s.cursor.Hidden = true } - - return nil } // ShowCursor shows the terminal cursor. // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) ShowCursor() error { - _, err := s.buf.WriteString(ansi.ShowCursor) - if err != nil { - return err - } - +func (s *TerminalScreen) ShowCursor() { + s.buf.WriteString(ansi.ShowCursor) if s.cursor != nil { s.cursor.Hidden = false } else { s.cursor = NewCursor(-1, -1) } - - return nil } // CursorVisible returns whether the terminal cursor is currently visible. @@ -301,7 +343,7 @@ func (s *TerminalScreen) CursorVisible() bool { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) SetCursorPosition(x, y int) error { +func (s *TerminalScreen) SetCursorPosition(x, y int) { if s.cursor == nil { s.cursor = NewCursor(x, y) s.cursor.Hidden = true @@ -309,7 +351,6 @@ func (s *TerminalScreen) SetCursorPosition(x, y int) error { s.cursor.X = x s.cursor.Y = y } - return nil } // CursorPosition returns the last set cursor position of the terminal. If the @@ -328,18 +369,13 @@ func (s *TerminalScreen) CursorPosition() (x, y int) { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) SetCursorStyle(shape CursorShape, blink bool) error { - if err := EncodeCursorStyle(s.buf, shape, blink); err != nil { - return err - } - +func (s *TerminalScreen) SetCursorStyle(shape CursorShape, blink bool) { + EncodeCursorStyle(s.buf, shape, blink) if s.cursor == nil { s.cursor = NewCursor(-1, -1) } s.cursor.Shape = shape s.cursor.Blink = blink - - return nil } // CursorStyle returns the current style of the terminal cursor. @@ -354,17 +390,12 @@ func (s *TerminalScreen) CursorStyle() (shape CursorShape, blink bool) { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) SetCursorColor(c color.Color) error { - if err := EncodeCursorColor(s.buf, c); err != nil { - return err - } - +func (s *TerminalScreen) SetCursorColor(c color.Color) { + EncodeCursorColor(s.buf, c) if s.cursor == nil { s.cursor = NewCursor(-1, -1) } s.cursor.Color = c - - return nil } // CursorColor returns the current color of the terminal cursor. @@ -382,14 +413,9 @@ func (s *TerminalScreen) CursorColor() color.Color { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) SetBackgroundColor(c color.Color) error { - if err := EncodeBackgroundColor(s.buf, c); err != nil { - return err - } - +func (s *TerminalScreen) SetBackgroundColor(c color.Color) { + EncodeBackgroundColor(s.buf, c) s.backgroundColor = c - - return nil } // BackgroundColor returns the current background color of the terminal. @@ -404,14 +430,9 @@ func (s *TerminalScreen) BackgroundColor() color.Color { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) SetForegroundColor(c color.Color) error { - if err := EncodeForegroundColor(s.buf, c); err != nil { - return err - } - +func (s *TerminalScreen) SetForegroundColor(c color.Color) { + EncodeForegroundColor(s.buf, c) s.foregroundColor = c - - return nil } // ForegroundColor returns the current foreground color of the terminal. @@ -427,30 +448,18 @@ func (s *TerminalScreen) ForegroundColor() color.Color { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) EnableBracketedPaste() error { - _, err := s.buf.WriteString(ansi.SetModeBracketedPaste) - if err != nil { - return err - } - +func (s *TerminalScreen) EnableBracketedPaste() { + s.buf.WriteString(ansi.SetModeBracketedPaste) s.bracketedPaste = true - - return nil } // DisableBracketedPaste disables bracketed paste mode. // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) DisableBracketedPaste() error { - _, err := s.buf.WriteString(ansi.ResetModeBracketedPaste) - if err != nil { - return err - } - +func (s *TerminalScreen) DisableBracketedPaste() { + s.buf.WriteString(ansi.ResetModeBracketedPaste) s.bracketedPaste = false - - return nil } // BracketedPaste returns whether bracketed paste mode is currently enabled. @@ -458,38 +467,60 @@ func (s *TerminalScreen) BracketedPaste() bool { return s.bracketedPaste } -// SetMouseMode sets the mouse mode for the terminal, allowing applications to -// receive mouse events. +// SetSynchronizedUpdates sets whether to use synchronized updates (mode 2026), +// which allows applications to batch updates to the terminal screen and flush +// them all at once for improved performance. // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) SetMouseMode(mode MouseMode) error { - if err := EncodeMouseMode(s.buf, mode); err != nil { - return err - } +func (s *TerminalScreen) SetSynchronizedUpdates(enabled bool) { + s.syncUpdates = enabled +} - s.mouseMode = mode +// SynchronizedUpdates returns whether synchronized updates (mode 2026) are +// currently enabled. +func (s *TerminalScreen) SynchronizedUpdates() bool { + return s.syncUpdates +} - return nil +// SetMouseMode sets the mouse tracking mode for the terminal, allowing +// applications to receive mouse events. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) SetMouseMode(mode MouseMode) { + EncodeMouseMode(s.buf, mode) + s.mouseMode = mode } -// MouseMode returns the current mouse mode of the terminal. +// MouseMode returns the current mouse tracking mode of the terminal. func (s *TerminalScreen) MouseMode() MouseMode { return s.mouseMode } -// SetWindowTitle sets the title of the terminal window. +// SetMouseEncoding sets the mouse encoding for the terminal. +// The encoding determines how mouse coordinates and buttons are reported. +// This is only meaningful when mouse tracking is enabled via [TerminalScreen.SetMouseMode]. // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) SetWindowTitle(title string) error { - if err := EncodeWindowTitle(s.buf, title); err != nil { - return err - } +func (s *TerminalScreen) SetMouseEncoding(enc MouseEncoding) { + EncodeMouseEncoding(s.buf, enc) + s.mouseEncoding = enc +} - s.windowTitle = title +// MouseEncoding returns the current mouse encoding of the terminal. +func (s *TerminalScreen) MouseEncoding() MouseEncoding { + return s.mouseEncoding +} - return nil +// SetWindowTitle sets the title of the terminal window. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) SetWindowTitle(title string) { + EncodeWindowTitle(s.buf, title) + s.windowTitle = title } // WindowTitle returns the current title of the terminal window. @@ -502,14 +533,9 @@ func (s *TerminalScreen) WindowTitle() string { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) SetKeyboardEnhancements(enh *KeyboardEnhancements) error { - if err := EncodeKeyboardEnhancements(s.buf, enh); err != nil { - return err - } - +func (s *TerminalScreen) SetKeyboardEnhancements(enh *KeyboardEnhancements) { + EncodeKeyboardEnhancements(s.buf, enh) s.keyboardEnhancements = enh - - return nil } // KeyboardEnhancements returns the current keyboard enhancements of the terminal. @@ -524,14 +550,9 @@ func (s *TerminalScreen) KeyboardEnhancements() *KeyboardEnhancements { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) SetProgressBar(pb *ProgressBar) error { - if err := EncodeProgressBar(s.buf, pb); err != nil { - return err - } - +func (s *TerminalScreen) SetProgressBar(pb *ProgressBar) { + EncodeProgressBar(s.buf, pb) s.progressBar = pb - - return nil } // ProgressBar returns the current progress bar of the terminal. @@ -547,7 +568,7 @@ func (s *TerminalScreen) ProgressBar() *ProgressBar { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) Reset() error { +func (s *TerminalScreen) Reset() { var sb strings.Builder hasKeyboardEnhancements := s.keyboardEnhancements != nil @@ -562,7 +583,10 @@ func (s *TerminalScreen) Reset() error { sb.WriteString(ansi.KittyKeyboard(0, 1)) } if s.mouseMode != MouseModeNone { - _ = EncodeMouseMode(&sb, MouseModeNone) + EncodeMouseMode(&sb, MouseModeNone) + } + if s.mouseEncoding != MouseEncodingLegacy { + EncodeMouseEncoding(&sb, MouseEncodingLegacy) } if s.cursor == nil || !s.cursor.Hidden { @@ -592,10 +616,7 @@ func (s *TerminalScreen) Reset() error { sb.WriteString(ansi.ResetProgressBar) } - _, err := s.buf.WriteString(sb.String()) - if err != nil { - return err - } + s.buf.WriteString(sb.String()) // Go to the bottom of the screen. // We need to go to the bottom of the screen regardless of whether @@ -608,8 +629,6 @@ func (s *TerminalScreen) Reset() error { // // Note that both [TerminalScreen.rend] writes to [TerminalScreen.buf]. s.rend.MoveTo(0, s.win.Height()-1) - - return nil } // Restore restores the terminal screen to its previous state, applying any @@ -617,9 +636,12 @@ func (s *TerminalScreen) Reset() error { // // The changes can be committed to the underlying writer by calling the // [TerminalScreen.Flush] method. -func (s *TerminalScreen) Restore() error { +func (s *TerminalScreen) Restore() { var sb strings.Builder + if s.resetTabs { + sb.WriteString(ansi.SetTabEvery8Columns) + } if s.altScreen { sb.WriteString(ansi.SetModeAltScreenSaveCursor) } @@ -630,53 +652,39 @@ func (s *TerminalScreen) Restore() error { sb.WriteString(ansi.HideCursor) } if s.keyboardEnhancements != nil { - if err := EncodeKeyboardEnhancements(&sb, s.keyboardEnhancements); err != nil { - return err - } + EncodeKeyboardEnhancements(&sb, s.keyboardEnhancements) } if s.mouseMode != MouseModeNone { - if err := EncodeMouseMode(&sb, s.mouseMode); err != nil { - return err - } + EncodeMouseMode(&sb, s.mouseMode) + } + if s.mouseEncoding != MouseEncodingLegacy { + EncodeMouseEncoding(&sb, s.mouseEncoding) } if s.cursor != nil { if s.cursor.Shape != CursorBlock || !s.cursor.Blink { - _ = EncodeCursorStyle(&sb, s.cursor.Shape, s.cursor.Blink) + EncodeCursorStyle(&sb, s.cursor.Shape, s.cursor.Blink) } if s.cursor.Color != nil { - if err := EncodeCursorColor(&sb, s.cursor.Color); err != nil { - return err - } + EncodeCursorColor(&sb, s.cursor.Color) } } if s.backgroundColor != nil { - if err := EncodeBackgroundColor(&sb, s.backgroundColor); err != nil { - return err - } + EncodeBackgroundColor(&sb, s.backgroundColor) } if s.foregroundColor != nil { - if err := EncodeForegroundColor(&sb, s.foregroundColor); err != nil { - return err - } + EncodeForegroundColor(&sb, s.foregroundColor) } if s.bracketedPaste { sb.WriteString(ansi.SetModeBracketedPaste) } if s.windowTitle != "" { - if err := EncodeWindowTitle(&sb, s.windowTitle); err != nil { - return err - } + EncodeWindowTitle(&sb, s.windowTitle) } if s.progressBar != nil && s.progressBar.State != ProgressBarNone { - if err := EncodeProgressBar(&sb, s.progressBar); err != nil { - return err - } + EncodeProgressBar(&sb, s.progressBar) } - _, err := s.buf.WriteString(sb.String()) - if err != nil { - return err - } + s.buf.WriteString(sb.String()) // This needs to be called after restoring the screen state and writing to // the buffer. @@ -686,14 +694,10 @@ func (s *TerminalScreen) Restore() error { // that the restore commands are included in the render output. This // ensures that the screen is properly restored before rendering any // changes. - if err := s.Render(); err != nil { - return err - } + s.Render() // Cursor position will be restored by the caller after calling // [TerminalScreen.Flush]. - - return nil } // Write writes data to the underlying buffer queuing it for output. diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_unix.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_unix.go index 9caf0a74c4..04ed3e3f61 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/terminal_unix.go +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_unix.go @@ -4,10 +4,6 @@ package uv import ( - "os" - "os/signal" - "syscall" - "github.com/charmbracelet/x/term" "github.com/charmbracelet/x/termios" ) @@ -58,21 +54,3 @@ func getSize(inTty, outTty term.File) (w, h int, err error) { func optimizeMovements(state *term.State) (useTabs, useBspace bool) { return supportsHardTabs(uint64(state.Oflag)), supportsBackspace(uint64(state.Lflag)) //nolint:unconvert,nolintlint } - -func startWinch(inTty, outTty term.File) (c chan os.Signal, err error) { - for _, f := range []term.File{inTty, outTty} { - if f == nil { - continue - } - if term.IsTerminal(f.Fd()) { - c = make(chan os.Signal) - signal.Notify(c, syscall.SIGWINCH) - return c, nil - } - } - return nil, ErrNotTerminal -} - -func stopWinch(c chan os.Signal) { - signal.Stop(c) -} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_windows.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_windows.go index 73a39d7bd9..a899ba4868 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/terminal_windows.go +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_windows.go @@ -5,7 +5,6 @@ package uv import ( "fmt" - "os" "github.com/charmbracelet/x/term" "golang.org/x/sys/windows" @@ -76,10 +75,3 @@ func supportsBackspace(uint64) bool { func supportsHardTabs(uint64) bool { return true } - -func startWinch(_, _ term.File) (chan os.Signal, error) { - return nil, ErrPlatformNotSupported -} - -func stopWinch(chan os.Signal) { -} diff --git a/vendor/github.com/charmbracelet/ultraviolet/uv.go b/vendor/github.com/charmbracelet/ultraviolet/uv.go index 546d6f332a..dac088a9c8 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/uv.go +++ b/vendor/github.com/charmbracelet/ultraviolet/uv.go @@ -4,7 +4,6 @@ import ( "fmt" "image/color" "io" - "strings" "github.com/charmbracelet/x/ansi" "github.com/lucasb-eyer/go-colorful" @@ -153,6 +152,25 @@ type KeyboardEnhancements struct { // [KeyPressEvent] with the [Key.IsRepeat] field set indicating that this // is a it's part of a key repeat sequence. ReportEventTypes bool + + // ReportAlternateKeys requests the terminal to report alternate key values + // in addition to the main ones. + // Note that only key events represented as escape codes will affected by + // this enhancement. + ReportAlternateKeys bool + + // ReportAllKeysAsEscapeCodes requests the terminal to report all key + // events, including plain text keys, as escape codes. + // When this is enabled, text won't be sent as plain text but instead as + // escape codes that encode the key value and modifiers. + ReportAllKeysAsEscapeCodes bool + + // ReportAssociatedText requests the terminal to report the text associated + // with key events. + // Note that this is an enhancement to + // [KeyboardEnhancements.ReportAllKeysAsEscapeCodes] and only has an effect + // if that is enabled. + ReportAssociatedText bool } // NewKeyboardEnhancements returns a new [KeyboardEnhancements] with the given @@ -182,6 +200,15 @@ func (ke KeyboardEnhancements) Flags() int { if ke.ReportEventTypes { bits |= ansi.KittyReportEventTypes } + if ke.ReportAlternateKeys { + bits |= ansi.KittyReportAlternateKeys + } + if ke.ReportAllKeysAsEscapeCodes { + bits |= ansi.KittyReportAllKeysAsEscapeCodes + } + if ke.ReportAssociatedText { + bits |= ansi.KittyReportAssociatedKeys + } return bits } @@ -274,29 +301,26 @@ func EncodeBracketedPaste(w io.Writer, enable bool) error { return nil } -// EncodeMouseMode encodes the mouse mode to the given writer. +// EncodeMouseMode encodes the mouse tracking mode to the given writer. func EncodeMouseMode(w io.Writer, mode MouseMode) error { - var sb strings.Builder + var seq string switch mode { case MouseModeNone: - sb.WriteString(ansi.ResetModeMouseNormal) - sb.WriteString(ansi.ResetModeMouseButtonEvent) - sb.WriteString(ansi.ResetModeMouseAnyEvent) - sb.WriteString(ansi.ResetModeMouseExtSgr) + seq = ansi.ResetModeMouseX10 + + ansi.ResetModeMouseNormal + + ansi.ResetModeMouseButtonEvent + + ansi.ResetModeMouseAnyEvent + case MouseModePress: + seq = ansi.SetModeMouseX10 case MouseModeClick: - sb.WriteString(ansi.SetModeMouseNormal) - sb.WriteString(ansi.SetModeMouseExtSgr) + seq = ansi.SetModeMouseNormal case MouseModeDrag: - sb.WriteString(ansi.SetModeMouseButtonEvent) - sb.WriteString(ansi.SetModeMouseExtSgr) + seq = ansi.SetModeMouseButtonEvent case MouseModeMotion: - sb.WriteString(ansi.SetModeMouseAnyEvent) - sb.WriteString(ansi.SetModeMouseExtSgr) - default: - return fmt.Errorf("invalid mouse mode: %d", mode) + seq = ansi.SetModeMouseAnyEvent } - _, err := io.WriteString(w, sb.String()) + _, err := io.WriteString(w, seq) //nolint:errcheck if err != nil { return fmt.Errorf("failed to set mouse mode: %w", err) } @@ -304,6 +328,29 @@ func EncodeMouseMode(w io.Writer, mode MouseMode) error { return nil } +// EncodeMouseEncoding encodes the mouse encoding mode to the given writer. +// When enc is [MouseEncodingLegacy], all extended encodings are reset. +func EncodeMouseEncoding(w io.Writer, enc MouseEncoding) error { + var seq string + switch enc { + case MouseEncodingLegacy: + seq = ansi.ResetModeMouseExtSgr + + ansi.ResetModeMouseExtUrxvt + + ansi.ResetModeMouseExtSgrPixel + case MouseEncodingSGR: + seq = ansi.SetModeMouseExtSgr + case MouseEncodingSGRPixel: + seq = ansi.SetModeMouseExtSgrPixel + } + + _, err := io.WriteString(w, seq) //nolint:errcheck + if err != nil { + return fmt.Errorf("failed to set mouse encoding: %w", err) + } + + return nil +} + // EncodeProgressBar encodes the progress bar to the given writer. func EncodeProgressBar(w io.Writer, pb *ProgressBar) error { if pb == nil { diff --git a/vendor/github.com/charmbracelet/ultraviolet/window.go b/vendor/github.com/charmbracelet/ultraviolet/window.go index 21c707a84c..e90bef28b3 100644 --- a/vendor/github.com/charmbracelet/ultraviolet/window.go +++ b/vendor/github.com/charmbracelet/ultraviolet/window.go @@ -1,8 +1,6 @@ package uv -import ( - "github.com/charmbracelet/x/ansi" -) +import "github.com/charmbracelet/x/ansi" // Window represents a rectangular area on the screen. It can be a root window // with no parent, or a sub-window with a parent window. A window can have its @@ -105,11 +103,26 @@ func (w *Window) NewView(x, y, width, height int) *Window { return newWindow(w, x, y, width, height, w.method, true) } -// NewScreen creates a new root [Window] with the given size and width method. +// NewWindow creates a new root [Window] with the given size and width method. +// If the method is nil, it defaults to [ansi.WcWidth]. +// +// The [WidthMethod] is used to calculate the width of characters in the +// window, which is important for correctly rendering text, especially when +// dealing with wide characters, combining characters, emojis, and other +// Unicode characters that may have varying widths. +// +// Use [ansi.WcWidth] as the default width method, which is a common +// implementation that handles a wide range of Unicode characters according to +// the Unicode Standard. Use [ansi.GraphemeWidth] if you know that your +// terminal supports grapheme clusters and proper rendering of combining +// characters and emojis, and you want to ensure that the width calculations +// are based on grapheme clusters rather than individual code points. // // This will panic if width or height is negative. -func NewScreen(width, height int) *Window { - var method WidthMethod = ansi.WcWidth +func NewWindow(width, height int, method WidthMethod) *Window { + if method == nil { + method = ansi.WcWidth + } return newWindow(nil, 0, 0, width, height, &method, false) } diff --git a/vendor/github.com/charmbracelet/x/ansi/width.go b/vendor/github.com/charmbracelet/x/ansi/width.go index fd680cfdc0..a4735b8ac0 100644 --- a/vendor/github.com/charmbracelet/x/ansi/width.go +++ b/vendor/github.com/charmbracelet/x/ansi/width.go @@ -87,7 +87,7 @@ func stringWidth(m Method, s string) int { for i := 0; i < len(s); i++ { state, action := parser.Table.Transition(pstate, s[i]) - if state == parser.Utf8State { + if action == parser.PrintAction || state == parser.Utf8State { cluster, w := FirstGraphemeCluster(s[i:], m) width += w diff --git a/vendor/github.com/cilium/ebpf/CODEOWNERS b/vendor/github.com/cilium/ebpf/CODEOWNERS index bd0a61158e..be87f42251 100644 --- a/vendor/github.com/cilium/ebpf/CODEOWNERS +++ b/vendor/github.com/cilium/ebpf/CODEOWNERS @@ -1,11 +1,17 @@ * @cilium/ebpf-lib-maintainers -features/ @rgo3 -link/ @mmat11 +/features/ @rgo3 +/link/ @mmat11 @rgo3 -perf/ @florianl -ringbuf/ @florianl +/perf/ @florianl +/ringbuf/ @florianl -btf/ @dylandreimerink +/btf/ @dylandreimerink -docs/ @ti-mo +/docs/ @cilium/ebpf-go-reviewers @ti-mo + +# Windows specific code. +/docs/**/windows*.md @cilium/ebpf-go-windows-reviewers +/internal/efw @cilium/ebpf-go-windows-reviewers +windows/ @cilium/ebpf-go-windows-reviewers # Folders +*windows*.go @cilium/ebpf-go-windows-reviewers # Go code diff --git a/vendor/github.com/cilium/ebpf/Makefile b/vendor/github.com/cilium/ebpf/Makefile index 45462e8d5a..d697871e69 100644 --- a/vendor/github.com/cilium/ebpf/Makefile +++ b/vendor/github.com/cilium/ebpf/Makefile @@ -1,9 +1,9 @@ # The development version of clang is distributed as the 'clang' binary, # while stable/released versions have a version number attached. # Pin the default clang to a stable version. -CLANG ?= clang-20 -STRIP ?= llvm-strip-20 -OBJCOPY ?= llvm-objcopy-20 +CLANG ?= clang-22 +STRIP ?= llvm-strip-22 +OBJCOPY ?= llvm-objcopy-22 CFLAGS := -O2 -g -Wall -Werror -mcpu=v2 $(CFLAGS) CI_KERNEL_URL ?= https://github.com/cilium/ci-kernels/raw/master/ @@ -11,23 +11,41 @@ CI_KERNEL_URL ?= https://github.com/cilium/ci-kernels/raw/master/ # Obtain an absolute path to the directory of the Makefile. # Assume the Makefile is in the root of the repository. REPODIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) -UIDGID := $(shell stat -c '%u:%g' ${REPODIR}) # Prefer podman if installed, otherwise use docker. # Note: Setting the var at runtime will always override. -CONTAINER_ENGINE ?= $(if $(shell command -v podman), podman, docker) -CONTAINER_RUN_ARGS ?= $(if $(filter ${CONTAINER_ENGINE}, podman), \ - --log-driver=none \ - -v "$(shell go env GOCACHE)":/root/.cache/go-build \ - -v "$(shell go env GOMODCACHE)":/go/pkg/mod, --user "${UIDGID}") +CONTAINER_ENGINE ?= $(if $(shell command -v podman),podman,docker) + +# Configure container runtime arguments based on the container engine. +CONTAINER_RUN_ARGS := \ + --env MAKEFLAGS \ + --env BPF2GO_CC="$(CLANG)" \ + --env BPF2GO_CFLAGS="$(CFLAGS)" \ + --env HOME=/tmp \ + -v "${REPODIR}":/ebpf -w /ebpf \ + -v "$(shell go env GOCACHE)":/tmp/.cache/go-build \ + -v "$(shell go env GOPATH)":/go \ + -v "$(shell go env GOMODCACHE)":/go/pkg/mod + +ifeq ($(CONTAINER_ENGINE), podman) +CONTAINER_RUN_ARGS += --log-driver=none --security-opt label=disable +else +CONTAINER_RUN_ARGS += --user "$(shell stat -c '%u:%g' ${REPODIR})" +endif IMAGE := $(shell cat ${REPODIR}/testdata/docker/IMAGE) VERSION := $(shell cat ${REPODIR}/testdata/docker/VERSION) +TARGETS_EL := \ + testdata/linked1 \ + testdata/linked2 \ + testdata/linked + TARGETS := \ testdata/loader-clang-14 \ testdata/loader-clang-17 \ testdata/loader-$(CLANG) \ + testdata/loader_nobtf \ testdata/manyprogs \ testdata/btf_map_init \ testdata/invalid_map \ @@ -49,6 +67,8 @@ TARGETS := \ testdata/constants \ testdata/errors \ testdata/variables \ + testdata/arena \ + testdata/struct_ops \ btf/testdata/relocs \ btf/testdata/relocs_read \ btf/testdata/relocs_read_tgt \ @@ -56,27 +76,27 @@ TARGETS := \ btf/testdata/tags \ cmd/bpf2go/testdata/minimal -.PHONY: all clean container-all container-shell generate +HEADERS := $(wildcard testdata/*.h) + +.PHONY: all clean godirs container-all container-shell generate format -.DEFAULT_TARGET = container-all +.DEFAULT_GOAL = container-all + +# Make sure Go-related dirs exist before starting containerized builds, +# otherwise podman will fail to start. +godirs: + mkdir -p $(shell go env GOCACHE) $(shell go env GOPATH) $(shell go env GOMODCACHE) # Build all ELF binaries using a containerized LLVM toolchain. -container-all: +container-all: godirs +${CONTAINER_ENGINE} run --rm -ti ${CONTAINER_RUN_ARGS} \ - -v "${REPODIR}":/ebpf -w /ebpf --env MAKEFLAGS \ - --env HOME="/tmp" \ - --env BPF2GO_CC="$(CLANG)" \ - --env BPF2GO_CFLAGS="$(CFLAGS)" \ "${IMAGE}:${VERSION}" \ - make all + $(MAKE) all # (debug) Drop the user into a shell inside the container as root. # Set BPF2GO_ envs to make 'make generate' just work. -container-shell: +container-shell: godirs ${CONTAINER_ENGINE} run --rm -ti ${CONTAINER_RUN_ARGS} \ - -v "${REPODIR}":/ebpf -w /ebpf \ - --env BPF2GO_CC="$(CLANG)" \ - --env BPF2GO_CFLAGS="$(CFLAGS)" \ "${IMAGE}:${VERSION}" clean: @@ -86,32 +106,44 @@ clean: format: find . -type f -name "*.c" | xargs clang-format -i -all: format $(addsuffix -el.elf,$(TARGETS)) $(addsuffix -eb.elf,$(TARGETS)) generate +all: format testdata update-external-deps ln -srf testdata/loader-$(CLANG)-el.elf testdata/loader-el.elf ln -srf testdata/loader-$(CLANG)-eb.elf testdata/loader-eb.elf + $(MAKE) generate generate: - go generate -run "internal/cmd/gentypes" ./... - go generate -skip "internal/cmd/gentypes" ./... + go generate -run "gentypes" ./... + go generate -run "stringer" ./... + go generate -skip "(gentypes|stringer)" ./... + +testdata: $(addsuffix -el.elf,$(TARGETS)) $(addsuffix -eb.elf,$(TARGETS)) $(addsuffix -el.elf,$(TARGETS_EL)) -testdata/loader-%-el.elf: testdata/loader.c +testdata/loader-%-el.elf: testdata/loader.c $(HEADERS) $* $(CFLAGS) -target bpfel -c $< -o $@ $(STRIP) -g $@ -testdata/loader-%-eb.elf: testdata/loader.c +testdata/loader-%-eb.elf: testdata/loader.c $(HEADERS) $* $(CFLAGS) -target bpfeb -c $< -o $@ $(STRIP) -g $@ -%-el.elf: %.c +testdata/loader_nobtf-el.elf: testdata/loader.c $(HEADERS) + $(CLANG) $(CFLAGS) -g0 -D__NOBTF__ -target bpfel -c $< -o $@ + +testdata/loader_nobtf-eb.elf: testdata/loader.c $(HEADERS) + $(CLANG) $(CFLAGS) -g0 -D__NOBTF__ -target bpfeb -c $< -o $@ + +%-el.elf: %.c $(HEADERS) $(CLANG) $(CFLAGS) -target bpfel -c $< -o $@ $(STRIP) -g $@ -%-eb.elf : %.c +%-eb.elf: %.c $(HEADERS) $(CLANG) $(CFLAGS) -target bpfeb -c $< -o $@ $(STRIP) -g $@ -.PHONY: update-kernel-deps -update-kernel-deps: export KERNEL_VERSION?=6.8 -update-kernel-deps: - ./testdata/sh/update-kernel-deps.sh - $(MAKE) container-all +testdata/linked-el.elf: testdata/linked1-el.elf testdata/linked2-el.elf + bpftool gen object $@ $^ + +.PHONY: update-external-deps +update-external-deps: + ./scripts/update-kernel-deps.sh + ./scripts/update-efw-deps.sh diff --git a/vendor/github.com/cilium/ebpf/README.md b/vendor/github.com/cilium/ebpf/README.md index d471ae7693..01a154c615 100644 --- a/vendor/github.com/cilium/ebpf/README.md +++ b/vendor/github.com/cilium/ebpf/README.md @@ -59,10 +59,11 @@ This library includes the following packages: * A version of Go that is [supported by upstream](https://golang.org/doc/devel/release.html#policy) -* Linux: CI is run against kernel.org LTS releases. >= 4.4 should work but EOL'ed +* Linux (amd64, arm64): CI is run against kernel.org LTS releases. >= 4.4 should work but EOL'ed versions are not supported. -* Windows: CI is run against Windows Server 2022. Only the latest eBPF for Windows +* Windows (amd64): CI is run against Windows Server 2022. Only the latest eBPF for Windows release is supported. +* Other architectures are best effort. 32bit arches are not supported. ## License diff --git a/vendor/github.com/cilium/ebpf/asm/alu.go b/vendor/github.com/cilium/ebpf/asm/alu.go index 282233d327..a4ae722127 100644 --- a/vendor/github.com/cilium/ebpf/asm/alu.go +++ b/vendor/github.com/cilium/ebpf/asm/alu.go @@ -1,6 +1,6 @@ package asm -//go:generate go run golang.org/x/tools/cmd/stringer@latest -output alu_string.go -type=Source,Endianness,ALUOp +//go:generate go tool stringer -output alu_string.go -type=Source,Endianness,ALUOp // Source of ALU / ALU64 / Branch operations // diff --git a/vendor/github.com/cilium/ebpf/asm/func.go b/vendor/github.com/cilium/ebpf/asm/func.go index 5ee4e954f5..fe75c75784 100644 --- a/vendor/github.com/cilium/ebpf/asm/func.go +++ b/vendor/github.com/cilium/ebpf/asm/func.go @@ -2,7 +2,7 @@ package asm import "github.com/cilium/ebpf/internal/platform" -//go:generate go run golang.org/x/tools/cmd/stringer@latest -output func_string.go -type=BuiltinFunc +//go:generate go tool stringer -output func_string.go -type=BuiltinFunc // BuiltinFunc is a built-in eBPF function. type BuiltinFunc uint32 diff --git a/vendor/github.com/cilium/ebpf/asm/func_string.go b/vendor/github.com/cilium/ebpf/asm/func_string.go index d5d624f094..2598379745 100644 --- a/vendor/github.com/cilium/ebpf/asm/func_string.go +++ b/vendor/github.com/cilium/ebpf/asm/func_string.go @@ -241,33 +241,36 @@ func _() { _ = x[WindowsFnGetCurrentPidTgid-268435475] _ = x[WindowsFnGetCurrentLogonId-268435476] _ = x[WindowsFnIsCurrentAdmin-268435477] - _ = x[WindowsFnMemcpy-268435478] - _ = x[WindowsFnMemcmp-268435479] + _ = x[WindowsFnMemcpyS-268435478] + _ = x[WindowsFnMemcmpS-268435479] _ = x[WindowsFnMemset-268435480] - _ = x[WindowsFnMemmove-268435481] + _ = x[WindowsFnMemmoveS-268435481] _ = x[WindowsFnGetSocketCookie-268435482] _ = x[WindowsFnStrncpyS-268435483] _ = x[WindowsFnStrncatS-268435484] _ = x[WindowsFnStrnlenS-268435485] _ = x[WindowsFnKtimeGetBootMs-268435486] _ = x[WindowsFnKtimeGetMs-268435487] + _ = x[WindowsFnPerfEventOutput-268435488] + _ = x[WindowsFnGetCurrentProcessStartKey-268435489] + _ = x[WindowsFnGetCurrentThreadCreateTime-268435490] } const ( _BuiltinFunc_name_0 = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegsFnGetBranchSnapshotFnTraceVprintkFnSkcToUnixSockFnKallsymsLookupNameFnFindVmaFnLoopFnStrncmpFnGetFuncArgFnGetFuncRetFnGetFuncArgCntFnGetRetvalFnSetRetvalFnXdpGetBuffLenFnXdpLoadBytesFnXdpStoreBytesFnCopyFromUserTaskFnSkbSetTstampFnImaFileHashFnKptrXchgFnMapLookupPercpuElemFnSkcToMptcpSockFnDynptrFromMemFnRingbufReserveDynptrFnRingbufSubmitDynptrFnRingbufDiscardDynptrFnDynptrReadFnDynptrWriteFnDynptrDataFnTcpRawGenSyncookieIpv4FnTcpRawGenSyncookieIpv6FnTcpRawCheckSyncookieIpv4FnTcpRawCheckSyncookieIpv6FnKtimeGetTaiNsFnUserRingbufDrainFnCgrpStorageGetFnCgrpStorageDelete" - _BuiltinFunc_name_1 = "WindowsFnMapLookupElemWindowsFnMapUpdateElemWindowsFnMapDeleteElemWindowsFnMapLookupAndDeleteElemWindowsFnTailCallWindowsFnGetPrandomU32WindowsFnKtimeGetBootNsWindowsFnGetSmpProcessorIdWindowsFnKtimeGetNsWindowsFnCsumDiffWindowsFnRingbufOutputWindowsFnTracePrintk2WindowsFnTracePrintk3WindowsFnTracePrintk4WindowsFnTracePrintk5WindowsFnMapPushElemWindowsFnMapPopElemWindowsFnMapPeekElemWindowsFnGetCurrentPidTgidWindowsFnGetCurrentLogonIdWindowsFnIsCurrentAdminWindowsFnMemcpyWindowsFnMemcmpWindowsFnMemsetWindowsFnMemmoveWindowsFnGetSocketCookieWindowsFnStrncpySWindowsFnStrncatSWindowsFnStrnlenSWindowsFnKtimeGetBootMsWindowsFnKtimeGetMs" + _BuiltinFunc_name_1 = "WindowsFnMapLookupElemWindowsFnMapUpdateElemWindowsFnMapDeleteElemWindowsFnMapLookupAndDeleteElemWindowsFnTailCallWindowsFnGetPrandomU32WindowsFnKtimeGetBootNsWindowsFnGetSmpProcessorIdWindowsFnKtimeGetNsWindowsFnCsumDiffWindowsFnRingbufOutputWindowsFnTracePrintk2WindowsFnTracePrintk3WindowsFnTracePrintk4WindowsFnTracePrintk5WindowsFnMapPushElemWindowsFnMapPopElemWindowsFnMapPeekElemWindowsFnGetCurrentPidTgidWindowsFnGetCurrentLogonIdWindowsFnIsCurrentAdminWindowsFnMemcpySWindowsFnMemcmpSWindowsFnMemsetWindowsFnMemmoveSWindowsFnGetSocketCookieWindowsFnStrncpySWindowsFnStrncatSWindowsFnStrnlenSWindowsFnKtimeGetBootMsWindowsFnKtimeGetMsWindowsFnPerfEventOutputWindowsFnGetCurrentProcessStartKeyWindowsFnGetCurrentThreadCreateTime" ) var ( _BuiltinFunc_index_0 = [...]uint16{0, 8, 23, 38, 53, 64, 76, 89, 104, 123, 138, 153, 168, 178, 193, 212, 230, 246, 264, 277, 289, 306, 323, 338, 348, 363, 380, 394, 406, 416, 433, 450, 466, 481, 497, 512, 528, 544, 568, 583, 596, 608, 624, 639, 654, 669, 683, 700, 714, 723, 735, 750, 763, 778, 793, 808, 828, 847, 859, 875, 894, 910, 925, 939, 952, 958, 973, 990, 1000, 1022, 1033, 1049, 1066, 1082, 1096, 1115, 1133, 1148, 1158, 1169, 1182, 1202, 1219, 1238, 1259, 1272, 1285, 1296, 1309, 1321, 1334, 1347, 1359, 1373, 1383, 1395, 1407, 1416, 1429, 1446, 1460, 1479, 1494, 1517, 1536, 1555, 1563, 1572, 1586, 1603, 1615, 1632, 1643, 1658, 1675, 1693, 1713, 1725, 1743, 1754, 1773, 1794, 1805, 1821, 1849, 1859, 1875, 1886, 1896, 1908, 1928, 1943, 1959, 1974, 1990, 2004, 2015, 2030, 2044, 2066, 2087, 2102, 2116, 2128, 2141, 2156, 2173, 2193, 2200, 2214, 2227, 2241, 2259, 2274, 2285, 2297, 2311, 2327, 2346, 2365, 2378, 2396, 2410, 2424, 2434, 2450, 2460, 2468, 2487, 2497, 2508, 2526, 2538, 2551, 2562, 2579, 2591, 2610, 2624, 2639, 2659, 2668, 2674, 2683, 2695, 2707, 2722, 2733, 2744, 2759, 2773, 2788, 2806, 2820, 2833, 2843, 2864, 2880, 2895, 2917, 2938, 2960, 2972, 2985, 2997, 3021, 3045, 3071, 3097, 3112, 3130, 3146, 3165} - _BuiltinFunc_index_1 = [...]uint16{0, 22, 44, 66, 97, 114, 136, 159, 185, 204, 221, 243, 264, 285, 306, 327, 347, 366, 386, 412, 438, 461, 476, 491, 506, 522, 546, 563, 580, 597, 620, 639} + _BuiltinFunc_index_1 = [...]uint16{0, 22, 44, 66, 97, 114, 136, 159, 185, 204, 221, 243, 264, 285, 306, 327, 347, 366, 386, 412, 438, 461, 477, 493, 508, 525, 549, 566, 583, 600, 623, 642, 666, 700, 735} ) func (i BuiltinFunc) String() string { switch { case i <= 211: return _BuiltinFunc_name_0[_BuiltinFunc_index_0[i]:_BuiltinFunc_index_0[i+1]] - case 268435457 <= i && i <= 268435487: + case 268435457 <= i && i <= 268435490: i -= 268435457 return _BuiltinFunc_name_1[_BuiltinFunc_index_1[i]:_BuiltinFunc_index_1[i+1]] default: diff --git a/vendor/github.com/cilium/ebpf/asm/func_win.go b/vendor/github.com/cilium/ebpf/asm/func_win.go index b016f00863..609ed90560 100644 --- a/vendor/github.com/cilium/ebpf/asm/func_win.go +++ b/vendor/github.com/cilium/ebpf/asm/func_win.go @@ -1,44 +1,45 @@ -// Code generated by internal/cmd/genwinfunctions.awk; DO NOT EDIT. +// Code generated by internal/cmd/genwinfunctions.awk; DO NOT EDIT. package asm // Code in this file is derived from eBPF for Windows, available under the MIT License. -import ( - "github.com/cilium/ebpf/internal/platform" -) +import "github.com/cilium/ebpf/internal/platform" // Built-in functions (Windows). const ( - WindowsFnMapLookupElem = BuiltinFunc(platform.WindowsTag | 1) - WindowsFnMapUpdateElem = BuiltinFunc(platform.WindowsTag | 2) - WindowsFnMapDeleteElem = BuiltinFunc(platform.WindowsTag | 3) - WindowsFnMapLookupAndDeleteElem = BuiltinFunc(platform.WindowsTag | 4) - WindowsFnTailCall = BuiltinFunc(platform.WindowsTag | 5) - WindowsFnGetPrandomU32 = BuiltinFunc(platform.WindowsTag | 6) - WindowsFnKtimeGetBootNs = BuiltinFunc(platform.WindowsTag | 7) - WindowsFnGetSmpProcessorId = BuiltinFunc(platform.WindowsTag | 8) - WindowsFnKtimeGetNs = BuiltinFunc(platform.WindowsTag | 9) - WindowsFnCsumDiff = BuiltinFunc(platform.WindowsTag | 10) - WindowsFnRingbufOutput = BuiltinFunc(platform.WindowsTag | 11) - WindowsFnTracePrintk2 = BuiltinFunc(platform.WindowsTag | 12) - WindowsFnTracePrintk3 = BuiltinFunc(platform.WindowsTag | 13) - WindowsFnTracePrintk4 = BuiltinFunc(platform.WindowsTag | 14) - WindowsFnTracePrintk5 = BuiltinFunc(platform.WindowsTag | 15) - WindowsFnMapPushElem = BuiltinFunc(platform.WindowsTag | 16) - WindowsFnMapPopElem = BuiltinFunc(platform.WindowsTag | 17) - WindowsFnMapPeekElem = BuiltinFunc(platform.WindowsTag | 18) - WindowsFnGetCurrentPidTgid = BuiltinFunc(platform.WindowsTag | 19) - WindowsFnGetCurrentLogonId = BuiltinFunc(platform.WindowsTag | 20) - WindowsFnIsCurrentAdmin = BuiltinFunc(platform.WindowsTag | 21) - WindowsFnMemcpy = BuiltinFunc(platform.WindowsTag | 22) - WindowsFnMemcmp = BuiltinFunc(platform.WindowsTag | 23) - WindowsFnMemset = BuiltinFunc(platform.WindowsTag | 24) - WindowsFnMemmove = BuiltinFunc(platform.WindowsTag | 25) - WindowsFnGetSocketCookie = BuiltinFunc(platform.WindowsTag | 26) - WindowsFnStrncpyS = BuiltinFunc(platform.WindowsTag | 27) - WindowsFnStrncatS = BuiltinFunc(platform.WindowsTag | 28) - WindowsFnStrnlenS = BuiltinFunc(platform.WindowsTag | 29) - WindowsFnKtimeGetBootMs = BuiltinFunc(platform.WindowsTag | 30) - WindowsFnKtimeGetMs = BuiltinFunc(platform.WindowsTag | 31) + WindowsFnMapLookupElem = BuiltinFunc(platform.WindowsTag | 1) + WindowsFnMapUpdateElem = BuiltinFunc(platform.WindowsTag | 2) + WindowsFnMapDeleteElem = BuiltinFunc(platform.WindowsTag | 3) + WindowsFnMapLookupAndDeleteElem = BuiltinFunc(platform.WindowsTag | 4) + WindowsFnTailCall = BuiltinFunc(platform.WindowsTag | 5) + WindowsFnGetPrandomU32 = BuiltinFunc(platform.WindowsTag | 6) + WindowsFnKtimeGetBootNs = BuiltinFunc(platform.WindowsTag | 7) + WindowsFnGetSmpProcessorId = BuiltinFunc(platform.WindowsTag | 8) + WindowsFnKtimeGetNs = BuiltinFunc(platform.WindowsTag | 9) + WindowsFnCsumDiff = BuiltinFunc(platform.WindowsTag | 10) + WindowsFnRingbufOutput = BuiltinFunc(platform.WindowsTag | 11) + WindowsFnTracePrintk2 = BuiltinFunc(platform.WindowsTag | 12) + WindowsFnTracePrintk3 = BuiltinFunc(platform.WindowsTag | 13) + WindowsFnTracePrintk4 = BuiltinFunc(platform.WindowsTag | 14) + WindowsFnTracePrintk5 = BuiltinFunc(platform.WindowsTag | 15) + WindowsFnMapPushElem = BuiltinFunc(platform.WindowsTag | 16) + WindowsFnMapPopElem = BuiltinFunc(platform.WindowsTag | 17) + WindowsFnMapPeekElem = BuiltinFunc(platform.WindowsTag | 18) + WindowsFnGetCurrentPidTgid = BuiltinFunc(platform.WindowsTag | 19) + WindowsFnGetCurrentLogonId = BuiltinFunc(platform.WindowsTag | 20) + WindowsFnIsCurrentAdmin = BuiltinFunc(platform.WindowsTag | 21) + WindowsFnMemcpyS = BuiltinFunc(platform.WindowsTag | 22) + WindowsFnMemcmpS = BuiltinFunc(platform.WindowsTag | 23) + WindowsFnMemset = BuiltinFunc(platform.WindowsTag | 24) + WindowsFnMemmoveS = BuiltinFunc(platform.WindowsTag | 25) + WindowsFnGetSocketCookie = BuiltinFunc(platform.WindowsTag | 26) + WindowsFnStrncpyS = BuiltinFunc(platform.WindowsTag | 27) + WindowsFnStrncatS = BuiltinFunc(platform.WindowsTag | 28) + WindowsFnStrnlenS = BuiltinFunc(platform.WindowsTag | 29) + WindowsFnKtimeGetBootMs = BuiltinFunc(platform.WindowsTag | 30) + WindowsFnKtimeGetMs = BuiltinFunc(platform.WindowsTag | 31) + WindowsFnPerfEventOutput = BuiltinFunc(platform.WindowsTag | 32) + WindowsFnGetCurrentProcessStartKey = BuiltinFunc(platform.WindowsTag | 33) + WindowsFnGetCurrentThreadCreateTime = BuiltinFunc(platform.WindowsTag | 34) ) diff --git a/vendor/github.com/cilium/ebpf/asm/instruction.go b/vendor/github.com/cilium/ebpf/asm/instruction.go index b2ce72ca83..f966a234bd 100644 --- a/vendor/github.com/cilium/ebpf/asm/instruction.go +++ b/vendor/github.com/cilium/ebpf/asm/instruction.go @@ -2,10 +2,12 @@ package asm import ( "crypto/sha1" + "crypto/sha256" "encoding/binary" "encoding/hex" "errors" "fmt" + "hash" "io" "math" "sort" @@ -43,6 +45,14 @@ type Instruction struct { Metadata Metadata } +// Width returns how many raw BPF instructions the Instruction occupies within +// an instruction stream. For example, an Instruction encoding a 64-bit value +// will typically occupy 2 raw instructions, while a 32-bit constant can be +// encoded in a single raw instruction. +func (ins *Instruction) Width() RawInstructionOffset { + return RawInstructionOffset(ins.OpCode.rawInstructions()) +} + // Unmarshal decodes a BPF instruction. func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder, platform string) error { data := make([]byte, InstructionSize) @@ -233,22 +243,6 @@ func (ins *Instruction) AssociateMap(m FDer) error { return nil } -// RewriteMapPtr changes an instruction to use a new map fd. -// -// Returns an error if the instruction doesn't load a map. -// -// Deprecated: use AssociateMap instead. If you cannot provide a Map, -// wrap an fd in a type implementing FDer. -func (ins *Instruction) RewriteMapPtr(fd int) error { - if !ins.IsLoadFromMap() { - return errors.New("not a load from a map") - } - - ins.encodeMapFD(fd) - - return nil -} - func (ins *Instruction) encodeMapFD(fd int) { // Preserve the offset value for direct map loads. offset := uint64(ins.Constant) & (math.MaxUint32 << 32) @@ -256,22 +250,6 @@ func (ins *Instruction) encodeMapFD(fd int) { ins.Constant = int64(offset | rawFd) } -// MapPtr returns the map fd for this instruction. -// -// The result is undefined if the instruction is not a load from a map, -// see IsLoadFromMap. -// -// Deprecated: use Map() instead. -func (ins *Instruction) MapPtr() int { - // If there is a map associated with the instruction, return its FD. - if fd := ins.Metadata.Get(mapMeta{}); fd != nil { - return fd.(FDer).FD() - } - - // Fall back to the fd stored in the Constant field - return ins.mapFd() -} - // mapFd returns the map file descriptor stored in the 32 least significant // bits of ins' Constant field. func (ins *Instruction) mapFd() int { @@ -418,13 +396,19 @@ func (ins Instruction) Format(f fmt.State, c rune) { } case cls.IsJump(): - fmt.Fprintf(f, "%v ", op) switch jop := op.JumpOp(); jop { case Call: + fmt.Fprintf(f, "%v ", op) switch ins.Src { case PseudoCall: - // bpf-to-bpf call - fmt.Fprint(f, ins.Constant) + // bpf-to-bpf call. For JIT-compiled programs, the kernel stores + // the PC offset in the 'off' field (ins.Offset) rather than 'imm' + // (ins.Constant). See kernel bpf_insn_prepare_dump in verifier.c. + if ins.Offset != 0 { + fmt.Fprint(f, ins.Offset) + } else { + fmt.Fprint(f, ins.Constant) + } case PseudoKfuncCall: // kfunc call fmt.Fprintf(f, "Kfunc(%d)", ins.Constant) @@ -433,13 +417,23 @@ func (ins Instruction) Format(f fmt.State, c rune) { } case Ja: + fmt.Fprintf(f, "%v ", op) if ins.OpCode.Class() == Jump32Class { fmt.Fprintf(f, "imm: %d", ins.Constant) } else { fmt.Fprintf(f, "off: %d", ins.Offset) } + case JCOND: + switch ins.Src { + case PseudoMayGoto: + fmt.Fprintf(f, "JCond may_goto off: %d", ins.Offset) + default: + fmt.Fprintf(f, "%v", op) + } + default: + fmt.Fprintf(f, "%v ", op) fmt.Fprintf(f, "dst: %s off: %d ", ins.Dst, ins.Offset) if op.Source() == ImmSource { fmt.Fprintf(f, "imm: %d", ins.Constant) @@ -486,13 +480,6 @@ func (ins Instruction) WithSymbol(name string) Instruction { return ins } -// Sym creates a symbol. -// -// Deprecated: use WithSymbol instead. -func (ins Instruction) Sym(name string) Instruction { - return ins.WithSymbol(name) -} - // Symbol returns the value ins has been marked with using WithSymbol, // otherwise returns an empty string. A symbol is often an Instruction // at the start of a function body. @@ -632,39 +619,6 @@ func (insns Instructions) AssociateMap(symbol string, m FDer) error { return nil } -// RewriteMapPtr rewrites all loads of a specific map pointer to a new fd. -// -// Returns ErrUnreferencedSymbol if the symbol isn't used. -// -// Deprecated: use AssociateMap instead. -func (insns Instructions) RewriteMapPtr(symbol string, fd int) error { - if symbol == "" { - return errors.New("empty symbol") - } - - var found bool - for i := range insns { - ins := &insns[i] - if ins.Reference() != symbol { - continue - } - - if !ins.IsLoadFromMap() { - return errors.New("not a load from a map") - } - - ins.encodeMapFD(fd) - - found = true - } - - if !found { - return fmt.Errorf("symbol %s: %w", symbol, ErrUnreferencedSymbol) - } - - return nil -} - // SymbolOffsets returns the set of symbols and their offset in // the instructions. func (insns Instructions) SymbolOffsets() (map[string]int, error) { @@ -817,18 +771,72 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error { // It mirrors bpf_prog_calc_tag in the kernel and so can be compared // to ProgramInfo.Tag to figure out whether a loaded program matches // certain instructions. +// +// Deprecated: The value produced by this method no longer matches tags produced +// by the kernel since Linux 6.18. Use [Instructions.HasTag] instead. func (insns Instructions) Tag(bo binary.ByteOrder) (string, error) { + // We cannot determine which hashing function to use without probing the kernel. + // So use the legacy SHA-1 implementation and deprecate this method. + return insns.tagSha1(bo) +} + +// HasTag returns true if the given tag matches the kernel tag of insns. +func (insns Instructions) HasTag(tag string, bo binary.ByteOrder) (bool, error) { + sha256Tag, err := insns.tagSha256(bo) + if err != nil { + return false, fmt.Errorf("hashing sha256: %w", err) + } + if tag == sha256Tag { + return true, nil + } + + sha1Tag, err := insns.tagSha1(bo) + if err != nil { + return false, fmt.Errorf("hashing sha1: %w", err) + } + + return tag == sha1Tag, nil +} + +// tagSha1 calculates the kernel tag for a series of instructions. +// +// It mirrors bpf_prog_calc_tag in kernels up to v6.18 and can be compared to +// ProgramInfo.Tag to figure out whether a loaded Program matches insns. +func (insns Instructions) tagSha1(bo binary.ByteOrder) (string, error) { h := sha1.New() + if err := insns.hash(h, bo); err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)[:sys.BPF_TAG_SIZE]), nil +} + +// tagSha256 calculates the kernel tag for a series of instructions. +// +// It mirrors bpf_prog_calc_tag in the kernel and can be compared to +// ProgramInfo.Tag to figure out whether a loaded Program matches insns. +func (insns Instructions) tagSha256(bo binary.ByteOrder) (string, error) { + h := sha256.New() + if err := insns.hash(h, bo); err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)[:sys.BPF_TAG_SIZE]), nil +} + +// hash calculates the hash of the instruction stream. Map load instructions +// are zeroed out, since these contain map file descriptors or pointers to +// maps, which will be different from load to load and would make the hash +// non-deterministic. +func (insns Instructions) hash(h hash.Hash, bo binary.ByteOrder) error { for i, ins := range insns { if ins.IsLoadFromMap() { ins.Constant = 0 } _, err := ins.Marshal(h, bo) if err != nil { - return "", fmt.Errorf("instruction %d: %w", i, err) + return fmt.Errorf("instruction %d: %w", i, err) } } - return hex.EncodeToString(h.Sum(nil)[:sys.BPF_TAG_SIZE]), nil + return nil } // encodeFunctionReferences populates the Offset (or Constant, depending on @@ -968,11 +976,3 @@ func newBPFRegisters(dst, src Register, bo binary.ByteOrder) (bpfRegisters, erro return 0, fmt.Errorf("unrecognized ByteOrder %T", bo) } } - -// IsUnreferencedSymbol returns true if err was caused by -// an unreferenced symbol. -// -// Deprecated: use errors.Is(err, asm.ErrUnreferencedSymbol). -func IsUnreferencedSymbol(err error) bool { - return errors.Is(err, ErrUnreferencedSymbol) -} diff --git a/vendor/github.com/cilium/ebpf/asm/jump.go b/vendor/github.com/cilium/ebpf/asm/jump.go index 2738d736b2..e8a78093bd 100644 --- a/vendor/github.com/cilium/ebpf/asm/jump.go +++ b/vendor/github.com/cilium/ebpf/asm/jump.go @@ -1,6 +1,6 @@ package asm -//go:generate go run golang.org/x/tools/cmd/stringer@latest -output jump_string.go -type=JumpOp +//go:generate go tool stringer -output jump_string.go -type=JumpOp // JumpOp affect control flow. // @@ -44,6 +44,8 @@ const ( JSLT JumpOp = 0xc0 // JSLE jumps by offset if signed r <= signed imm JSLE JumpOp = 0xd0 + // JCOND is a conditional pseudo jump to encode the may_goto instruction + JCOND JumpOp = 0xe0 ) // Return emits an exit instruction. diff --git a/vendor/github.com/cilium/ebpf/asm/jump_string.go b/vendor/github.com/cilium/ebpf/asm/jump_string.go index 85a4aaffa5..240cb7c09a 100644 --- a/vendor/github.com/cilium/ebpf/asm/jump_string.go +++ b/vendor/github.com/cilium/ebpf/asm/jump_string.go @@ -23,9 +23,10 @@ func _() { _ = x[JLE-176] _ = x[JSLT-192] _ = x[JSLE-208] + _ = x[JCOND-224] } -const _JumpOp_name = "JaJEqJGTJGEJSetJNEJSGTJSGECallExitJLTJLEJSLTJSLEInvalidJumpOp" +const _JumpOp_name = "JaJEqJGTJGEJSetJNEJSGTJSGECallExitJLTJLEJSLTJSLEJCONDInvalidJumpOp" var _JumpOp_map = map[JumpOp]string{ 0: _JumpOp_name[0:2], @@ -42,7 +43,8 @@ var _JumpOp_map = map[JumpOp]string{ 176: _JumpOp_name[37:40], 192: _JumpOp_name[40:44], 208: _JumpOp_name[44:48], - 255: _JumpOp_name[48:61], + 224: _JumpOp_name[48:53], + 255: _JumpOp_name[53:66], } func (i JumpOp) String() string { diff --git a/vendor/github.com/cilium/ebpf/asm/load_store.go b/vendor/github.com/cilium/ebpf/asm/load_store.go index 29571a74ec..a32a9b3187 100644 --- a/vendor/github.com/cilium/ebpf/asm/load_store.go +++ b/vendor/github.com/cilium/ebpf/asm/load_store.go @@ -2,7 +2,7 @@ package asm import "fmt" -//go:generate go run golang.org/x/tools/cmd/stringer@latest -output load_store_string.go -type=Mode,Size +//go:generate go tool stringer -output load_store_string.go -type=Mode,Size // Mode for load and store operations // diff --git a/vendor/github.com/cilium/ebpf/asm/metadata.go b/vendor/github.com/cilium/ebpf/asm/metadata.go index dd368a9360..60b8fe31f3 100644 --- a/vendor/github.com/cilium/ebpf/asm/metadata.go +++ b/vendor/github.com/cilium/ebpf/asm/metadata.go @@ -7,13 +7,13 @@ type Metadata struct { type metaElement struct { next *metaElement - key, value interface{} + key, value any } // Find the element containing key. // // Returns nil if there is no such element. -func (m *Metadata) find(key interface{}) *metaElement { +func (m *Metadata) find(key any) *metaElement { for e := m.head; e != nil; e = e.next { if e.key == key { return e @@ -49,7 +49,7 @@ func (m *Metadata) remove(r *metaElement) { // // If value is nil, the key is removed. Avoids modifying old metadata by // copying if necessary. -func (m *Metadata) Set(key, value interface{}) { +func (m *Metadata) Set(key, value any) { if e := m.find(key); e != nil { if e.value == value { // Key is present and the value is the same. Nothing to do. @@ -72,7 +72,7 @@ func (m *Metadata) Set(key, value interface{}) { // Get the value of a key. // // Returns nil if no value with the given key is present. -func (m *Metadata) Get(key interface{}) interface{} { +func (m *Metadata) Get(key any) any { if e := m.find(key); e != nil { return e.value } diff --git a/vendor/github.com/cilium/ebpf/asm/opcode.go b/vendor/github.com/cilium/ebpf/asm/opcode.go index c82a1f8fb0..9b2f80f0ac 100644 --- a/vendor/github.com/cilium/ebpf/asm/opcode.go +++ b/vendor/github.com/cilium/ebpf/asm/opcode.go @@ -5,7 +5,7 @@ import ( "strings" ) -//go:generate go run golang.org/x/tools/cmd/stringer@latest -output opcode_string.go -type=Class +//go:generate go tool stringer -output opcode_string.go -type=Class // Class of operations // diff --git a/vendor/github.com/cilium/ebpf/asm/opcode_string.go b/vendor/github.com/cilium/ebpf/asm/opcode_string.go index 58bc3e7e7f..07825e0dd1 100644 --- a/vendor/github.com/cilium/ebpf/asm/opcode_string.go +++ b/vendor/github.com/cilium/ebpf/asm/opcode_string.go @@ -23,8 +23,9 @@ const _Class_name = "LdClassLdXClassStClassStXClassALUClassJumpClassJump32ClassA var _Class_index = [...]uint8{0, 7, 15, 22, 30, 38, 47, 58, 68} func (i Class) String() string { - if i >= Class(len(_Class_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_Class_index)-1 { return "Class(" + strconv.FormatInt(int64(i), 10) + ")" } - return _Class_name[_Class_index[i]:_Class_index[i+1]] + return _Class_name[_Class_index[idx]:_Class_index[idx+1]] } diff --git a/vendor/github.com/cilium/ebpf/asm/register.go b/vendor/github.com/cilium/ebpf/asm/register.go index 457a3b8a88..baf03aa42f 100644 --- a/vendor/github.com/cilium/ebpf/asm/register.go +++ b/vendor/github.com/cilium/ebpf/asm/register.go @@ -40,6 +40,7 @@ const ( PseudoCall = R1 // BPF_PSEUDO_CALL PseudoFunc = R4 // BPF_PSEUDO_FUNC PseudoKfuncCall = R2 // BPF_PSEUDO_KFUNC_CALL + PseudoMayGoto = R0 // BPF_MAY_GOTO ) func (r Register) String() string { diff --git a/vendor/github.com/cilium/ebpf/btf/btf.go b/vendor/github.com/cilium/ebpf/btf/btf.go index d26931a049..3411569295 100644 --- a/vendor/github.com/cilium/ebpf/btf/btf.go +++ b/vendor/github.com/cilium/ebpf/btf/btf.go @@ -1,9 +1,7 @@ package btf import ( - "bufio" "debug/elf" - "encoding/binary" "errors" "fmt" "io" @@ -69,11 +67,12 @@ func LoadSpec(file string) (*Spec, error) { func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) { file, err := internal.NewSafeELFFile(rd) if err != nil { - if bo := guessRawBTFByteOrder(rd); bo != nil { - return loadRawSpec(io.NewSectionReader(rd, 0, math.MaxInt64), bo, nil) + raw, err := io.ReadAll(io.NewSectionReader(rd, 0, math.MaxInt64)) + if err != nil { + return nil, fmt.Errorf("read raw BTF: %w", err) } - return nil, err + return loadRawSpec(raw, nil) } return loadSpecFromELF(file) @@ -170,15 +169,20 @@ func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) { return nil, err } - if btfSection.ReaderAt == nil { - return nil, fmt.Errorf("compressed BTF is not supported") + rawBTF, err := btfSection.Data() + if err != nil { + return nil, fmt.Errorf("reading .BTF section: %w", err) } - spec, err := loadRawSpec(btfSection.ReaderAt, file.ByteOrder, nil) + spec, err := loadRawSpec(rawBTF, nil) if err != nil { return nil, err } + if spec.decoder.byteOrder != file.ByteOrder { + return nil, fmt.Errorf("BTF byte order %s does not match ELF byte order %s", spec.decoder.byteOrder, file.ByteOrder) + } + spec.elf = &elfData{ sectionSizes, offsets, @@ -188,7 +192,7 @@ func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) { return spec, nil } -func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder, base *Spec) (*Spec, error) { +func loadRawSpec(btf []byte, base *Spec) (*Spec, error) { var ( baseDecoder *decoder baseStrings *stringTable @@ -200,25 +204,32 @@ func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder, base *Spec) (*Spec, error baseStrings = base.strings } - buf := internal.NewBufferedSectionReader(btf, 0, math.MaxInt64) - header, err := parseBTFHeader(buf, bo) + header, _, bo, err := parseBTFHeader(btf) if err != nil { return nil, fmt.Errorf("parsing .BTF header: %v", err) } - stringsSection := io.NewSectionReader(btf, header.stringStart(), int64(header.StringLen)) - rawStrings, err := readStringTable(stringsSection, baseStrings) + if header.HdrLen > uint32(len(btf)) { + return nil, fmt.Errorf("BTF header length is out of bounds") + } + btf = btf[header.HdrLen:] + + if uint64(header.StringOff)+uint64(header.StringLen) > uint64(len(btf)) { + return nil, fmt.Errorf("string table is out of bounds") + } + stringsSection := btf[header.StringOff : header.StringOff+header.StringLen] + + rawStrings, err := newStringTable(stringsSection, baseStrings) if err != nil { return nil, fmt.Errorf("read string section: %w", err) } - typesSection := io.NewSectionReader(btf, header.typeStart(), int64(header.TypeLen)) - rawTypes := make([]byte, header.TypeLen) - if _, err := io.ReadFull(typesSection, rawTypes); err != nil { - return nil, fmt.Errorf("read type section: %w", err) + if uint64(header.TypeOff)+uint64(header.TypeLen) > uint64(len(btf)) { + return nil, fmt.Errorf("types section is out of bounds") } + typesSection := btf[header.TypeOff : header.TypeOff+header.TypeLen] - decoder, err := newDecoder(rawTypes, bo, rawStrings, baseDecoder) + decoder, err := newDecoder(typesSection, bo, rawStrings, baseDecoder) if err != nil { return nil, err } @@ -226,21 +237,6 @@ func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder, base *Spec) (*Spec, error return &Spec{decoder, nil}, nil } -func guessRawBTFByteOrder(r io.ReaderAt) binary.ByteOrder { - buf := new(bufio.Reader) - for _, bo := range []binary.ByteOrder{ - binary.LittleEndian, - binary.BigEndian, - } { - buf.Reset(io.NewSectionReader(r, 0, math.MaxInt64)) - if _, err := parseBTFHeader(buf, bo); err == nil { - return bo - } - } - - return nil -} - // fixupDatasec attempts to patch up missing info in Datasecs and its members by // supplementing them with information from the ELF headers and symbol table. func (elf *elfData) fixupDatasec(typ Type) error { @@ -449,15 +445,18 @@ func (s *Spec) AnyTypeByName(name string) (Type, error) { // // Returns an error wrapping ErrNotFound if no matching Type exists in the Spec. // Returns an error wrapping ErrMultipleTypes if multiple candidates are found. -func (s *Spec) TypeByName(name string, typ interface{}) error { - typeInterface := reflect.TypeOf((*Type)(nil)).Elem() +func (s *Spec) TypeByName(name string, typ any) error { + if err := internal.IsNilPointer(typ); err != nil { + return fmt.Errorf("type argument: %w", err) + } + + typeInterface := reflect.TypeFor[Type]() // typ may be **T or *Type typValue := reflect.ValueOf(typ) - if typValue.Kind() != reflect.Ptr { + if typValue.Kind() != reflect.Pointer { return fmt.Errorf("%T is not a pointer", typ) } - typPtr := typValue.Elem() if !typPtr.CanSet() { return fmt.Errorf("%T cannot be set", typ) @@ -466,6 +465,9 @@ func (s *Spec) TypeByName(name string, typ interface{}) error { wanted := typPtr.Type() if wanted == typeInterface { // This is *Type. Unwrap the value's type. + if typPtr.IsNil() { + return fmt.Errorf("%T points to a nil Type", typ) + } wanted = typPtr.Elem().Type() } @@ -500,12 +502,31 @@ func (s *Spec) TypeByName(name string, typ interface{}) error { return nil } +// LoadSplitSpec loads split BTF from the given file. +// +// Types from base are used to resolve references in the split BTF. +// The returned Spec only contains types from the split BTF, not from the base. +func LoadSplitSpec(file string, base *Spec) (*Spec, error) { + fh, err := os.Open(file) + if err != nil { + return nil, err + } + defer fh.Close() + + return LoadSplitSpecFromReader(fh, base) +} + // LoadSplitSpecFromReader loads split BTF from a reader. // // Types from base are used to resolve references in the split BTF. // The returned Spec only contains types from the split BTF, not from the base. func LoadSplitSpecFromReader(r io.ReaderAt, base *Spec) (*Spec, error) { - return loadRawSpec(r, internal.NativeEndian, base) + raw, err := io.ReadAll(io.NewSectionReader(r, 0, math.MaxInt64)) + if err != nil { + return nil, fmt.Errorf("read raw BTF: %w", err) + } + + return loadRawSpec(raw, base) } // All iterates over all types. diff --git a/vendor/github.com/cilium/ebpf/btf/btf_types.go b/vendor/github.com/cilium/ebpf/btf/btf_types.go index d20a31969d..469baf60cd 100644 --- a/vendor/github.com/cilium/ebpf/btf/btf_types.go +++ b/vendor/github.com/cilium/ebpf/btf/btf_types.go @@ -4,13 +4,10 @@ import ( "encoding/binary" "errors" "fmt" - "io" "unsafe" - - "github.com/cilium/ebpf/internal" ) -//go:generate go run golang.org/x/tools/cmd/stringer@latest -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage,btfKind +//go:generate go tool stringer -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage,btfKind // btfKind describes a Type. type btfKind uint8 @@ -87,47 +84,68 @@ type btfHeader struct { StringLen uint32 } -// typeStart returns the offset from the beginning of the .BTF section -// to the start of its type entries. -func (h *btfHeader) typeStart() int64 { - return int64(h.HdrLen + h.TypeOff) -} - -// stringStart returns the offset from the beginning of the .BTF section -// to the start of its string table. -func (h *btfHeader) stringStart() int64 { - return int64(h.HdrLen + h.StringOff) +type btfLayout struct { + Off uint32 + Len uint32 } // parseBTFHeader parses the header of the .BTF section. -func parseBTFHeader(r io.Reader, bo binary.ByteOrder) (*btfHeader, error) { +func parseBTFHeader(buf []byte) (*btfHeader, *btfLayout, binary.ByteOrder, error) { var header btfHeader - if err := binary.Read(r, bo, &header); err != nil { - return nil, fmt.Errorf("can't read header: %v", err) + var bo binary.ByteOrder + for _, order := range []binary.ByteOrder{binary.LittleEndian, binary.BigEndian} { + n, err := binary.Decode(buf, order, &header) + if err != nil { + return nil, nil, nil, fmt.Errorf("read header: %v", err) + } + + if header.Magic != btfMagic { + continue + } + + buf = buf[n:] + bo = order + break } - if header.Magic != btfMagic { - return nil, fmt.Errorf("incorrect magic value %v", header.Magic) + if bo == nil { + return nil, nil, nil, fmt.Errorf("no valid BTF header") } if header.Version != 1 { - return nil, fmt.Errorf("unexpected version %v", header.Version) + return nil, nil, nil, fmt.Errorf("unexpected version %v", header.Version) } if header.Flags != 0 { - return nil, fmt.Errorf("unsupported flags %v", header.Flags) + return nil, nil, nil, fmt.Errorf("unsupported flags %v", header.Flags) } remainder := int64(header.HdrLen) - int64(binary.Size(&header)) if remainder < 0 { - return nil, errors.New("header length shorter than btfHeader size") + return nil, nil, nil, errors.New("header length shorter than minimum BTF header size") + } + + if len(buf) < int(remainder) { + return nil, nil, nil, errors.New("header length exceeds available data") + } + + var layout btfLayout + if remainder >= int64(binary.Size(&btfLayout{})) { + n, err := binary.Decode(buf, bo, &layout) + if err != nil { + return nil, nil, nil, fmt.Errorf("read layout offset and length: %v", err) + } + buf = buf[n:] + remainder -= int64(n) } - if _, err := io.CopyN(internal.DiscardZeroes{}, r, remainder); err != nil { - return nil, fmt.Errorf("header padding: %v", err) + for _, b := range buf[:remainder] { + if b != 0 { + return nil, nil, nil, errors.New("header contains non-zero trailer") + } } - return &header, nil + return &header, &layout, bo, nil } // btfType is equivalent to struct btf_type in Documentation/bpf/btf.rst. diff --git a/vendor/github.com/cilium/ebpf/btf/btf_types_string.go b/vendor/github.com/cilium/ebpf/btf/btf_types_string.go index b7a1b80d15..a9d2d82b66 100644 --- a/vendor/github.com/cilium/ebpf/btf/btf_types_string.go +++ b/vendor/github.com/cilium/ebpf/btf/btf_types_string.go @@ -18,10 +18,11 @@ const _FuncLinkage_name = "staticglobalextern" var _FuncLinkage_index = [...]uint8{0, 6, 12, 18} func (i FuncLinkage) String() string { - if i < 0 || i >= FuncLinkage(len(_FuncLinkage_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_FuncLinkage_index)-1 { return "FuncLinkage(" + strconv.FormatInt(int64(i), 10) + ")" } - return _FuncLinkage_name[_FuncLinkage_index[i]:_FuncLinkage_index[i+1]] + return _FuncLinkage_name[_FuncLinkage_index[idx]:_FuncLinkage_index[idx+1]] } func _() { // An "invalid array index" compiler error signifies that the constant values have changed. @@ -37,10 +38,11 @@ const _VarLinkage_name = "staticglobalextern" var _VarLinkage_index = [...]uint8{0, 6, 12, 18} func (i VarLinkage) String() string { - if i < 0 || i >= VarLinkage(len(_VarLinkage_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_VarLinkage_index)-1 { return "VarLinkage(" + strconv.FormatInt(int64(i), 10) + ")" } - return _VarLinkage_name[_VarLinkage_index[i]:_VarLinkage_index[i+1]] + return _VarLinkage_name[_VarLinkage_index[idx]:_VarLinkage_index[idx+1]] } func _() { // An "invalid array index" compiler error signifies that the constant values have changed. @@ -73,8 +75,9 @@ const _btfKind_name = "UnknownIntPointerArrayStructUnionEnumForwardTypedefVolati var _btfKind_index = [...]uint8{0, 7, 10, 17, 22, 28, 33, 37, 44, 51, 59, 64, 72, 76, 85, 88, 95, 100, 107, 114, 120} func (i btfKind) String() string { - if i >= btfKind(len(_btfKind_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_btfKind_index)-1 { return "btfKind(" + strconv.FormatInt(int64(i), 10) + ")" } - return _btfKind_name[_btfKind_index[i]:_btfKind_index[i+1]] + return _btfKind_name[_btfKind_index[idx]:_btfKind_index[idx+1]] } diff --git a/vendor/github.com/cilium/ebpf/btf/dedup.go b/vendor/github.com/cilium/ebpf/btf/dedup.go new file mode 100644 index 0000000000..783ea09e85 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/btf/dedup.go @@ -0,0 +1,633 @@ +package btf + +import ( + "errors" + "fmt" + "hash/maphash" + "slices" +) + +// deduper deduplicates BTF types by finding all types in a Type graph that are +// Equivalent and replaces them with a single instance. +// +// See doc comments in types.go to understand the various ways in which Types +// can relate to each other and how they are compared for equality. We separate +// Identity (same memory location), Equivalence (same shape/layout), and +// Compatibility (CO-RE compatible) to be explicit about intent. +// +// This deduper opportunistically uses a combination of Identity and Equivalence +// to find types that can be deduplicated. +type deduper struct { + visited map[Type]struct{} + hashCache map[hashCacheKey]uint64 + + // Set of types that have been deduplicated. + done map[Type]Type + + // Map of hash to types with that hash. + hashed map[uint64][]Type + eqCache map[typKey]bool + + seed maphash.Seed +} + +func newDeduper() *deduper { + return &deduper{ + make(map[Type]struct{}), + make(map[hashCacheKey]uint64), + make(map[Type]Type), + make(map[uint64][]Type), + make(map[typKey]bool), + maphash.MakeSeed(), + } +} + +func (d *deduper) deduplicate(t Type) (Type, error) { + // If we have already attempted to deduplicate this exact type, return the + // result. + if done, ok := d.done[t]; ok { + return done, nil + } + + // Visit the subtree, if a type has children, attempt to replace it with a + // deduplicated version of those children. + for t := range postorder(t, d.visited) { + for c := range children(t) { + var err error + *c, err = d.hashInsert(*c) + if err != nil { + return nil, err + } + } + } + + // Finally, deduplicate the root type itself. + return d.hashInsert(t) +} + +// hashInsert attempts to deduplicate t by hashing it and comparing against +// other types with the same hash. Returns the Type to be used as the common +// substitute at this position in the graph. +func (d *deduper) hashInsert(t Type) (Type, error) { + // If we have deduplicated this type before, return the result of that + // deduplication. + if done, ok := d.done[t]; ok { + return done, nil + } + + // Compute the hash of this type. Types with the same hash are candidates for + // deduplication. + hash, err := d.hash(t, -1) + if err != nil { + return nil, err + } + + // A hash collision is possible, so we need to compare against all candidates + // with the same hash. + for _, candidate := range d.hashed[hash] { + // Pre-size the visited slice, experimentation on VMLinux shows a capacity + // of 16 to give the best performance. + const visitedCapacity = 16 + err := d.typesEquivalent(candidate, t, make([]Type, 0, visitedCapacity)) + if errors.Is(err, errNotEquivalent) { + continue + } + if err != nil { + return nil, err + } + + // Found a Type that's both Equivalent and hashes to the same value, choose + // it as the deduplicated version. + d.done[t] = candidate + + return candidate, nil + } + + d.hashed[hash] = append(d.hashed[hash], t) + + return t, nil +} + +// The hash of a Type is the same given its pointer and depth budget. +type hashCacheKey struct { + t Type + depthBudget int +} + +// hash computes a hash for t. The produced hash is the same for Types which +// are similar. The hash can collide such that two different Types may produce +// the same hash, so equivalence must be checked explicitly. It will recurse +// into children. The initial call should use a depthBudget of -1. +func (d *deduper) hash(t Type, depthBudget int) (uint64, error) { + if depthBudget == 0 { + return 0, nil + } + + h := &maphash.Hash{} + h.SetSeed(d.seed) + + switch t := t.(type) { + case *Void: + maphash.WriteComparable(h, kindUnknown) + + case *Int: + maphash.WriteComparable(h, kindInt) + maphash.WriteComparable(h, *t) + + case *Pointer: + maphash.WriteComparable(h, kindPointer) + // If the depth budget is positive, decrement it every time we follow a + // pointer. + if depthBudget > 0 { + depthBudget-- + } + + // If this is the first time we are following a pointer, set the depth + // budget. This limits amount of recursion we do when hashing pointers that + // form cycles. This is cheaper than tracking visited types and works + // because hash collisions are allowed. + if depthBudget < 0 { + depthBudget = 1 + + // Double pointers are common in C. However, with a depth budget of 1, all + // double pointers would hash the same, causing a performance issue when + // checking equivalence. So we give double pointers a bit more budget. + if _, ok := t.Target.(*Pointer); ok { + depthBudget = 2 + } + } + sub, err := d.hash(t.Target, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Array: + maphash.WriteComparable(h, kindArray) + maphash.WriteComparable(h, t.Nelems) + sub, err := d.hash(t.Index, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + _, err = d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Struct, *Union: + // Check the cache to avoid recomputing the hash for this type and depth + // budget. + key := hashCacheKey{t, depthBudget} + if cached, ok := d.hashCache[key]; ok { + return cached, nil + } + + var members []Member + switch t := t.(type) { + case *Struct: + maphash.WriteComparable(h, kindStruct) + maphash.WriteComparable(h, t.Name) + maphash.WriteComparable(h, t.Size) + members = t.Members + + case *Union: + maphash.WriteComparable(h, kindUnion) + maphash.WriteComparable(h, t.Name) + maphash.WriteComparable(h, t.Size) + members = t.Members + } + + maphash.WriteComparable(h, len(members)) + for _, m := range members { + maphash.WriteComparable(h, m.Name) + maphash.WriteComparable(h, m.Offset) + sub, err := d.hash(m.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + } + + sum := h.Sum64() + d.hashCache[key] = sum + return sum, nil + + case *Enum: + maphash.WriteComparable(h, kindEnum) + maphash.WriteComparable(h, t.Name) + maphash.WriteComparable(h, t.Size) + maphash.WriteComparable(h, t.Signed) + for _, v := range t.Values { + maphash.WriteComparable(h, v) + } + + case *Fwd: + maphash.WriteComparable(h, kindForward) + maphash.WriteComparable(h, *t) + + case *Typedef: + maphash.WriteComparable(h, kindTypedef) + maphash.WriteComparable(h, t.Name) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Volatile: + maphash.WriteComparable(h, kindVolatile) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Const: + maphash.WriteComparable(h, kindConst) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Restrict: + maphash.WriteComparable(h, kindRestrict) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Func: + maphash.WriteComparable(h, kindFunc) + maphash.WriteComparable(h, t.Name) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *FuncProto: + // It turns out that pointers to function prototypes are common in C code, + // function pointers. Function prototypes frequently have similar patterns + // of [ptr, ptr] -> int, or [ptr, ptr, ptr] -> int. Causing frequent hash + // collisions, for the default depth budget of 1. So allow one additional + // level of pointers when we encounter a function prototype. + if depthBudget >= 0 { + depthBudget++ + } + + maphash.WriteComparable(h, kindFuncProto) + for _, p := range t.Params { + maphash.WriteComparable(h, p.Name) + sub, err := d.hash(p.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + } + sub, err := d.hash(t.Return, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Var: + maphash.WriteComparable(h, kindVar) + maphash.WriteComparable(h, t.Name) + maphash.WriteComparable(h, t.Linkage) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Datasec: + maphash.WriteComparable(h, kindDatasec) + maphash.WriteComparable(h, t.Name) + for _, v := range t.Vars { + maphash.WriteComparable(h, v.Offset) + maphash.WriteComparable(h, v.Size) + sub, err := d.hash(v.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + } + + case *declTag: + maphash.WriteComparable(h, kindDeclTag) + maphash.WriteComparable(h, t.Value) + maphash.WriteComparable(h, t.Index) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *TypeTag: + maphash.WriteComparable(h, kindTypeTag) + maphash.WriteComparable(h, t.Value) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Float: + maphash.WriteComparable(h, kindFloat) + maphash.WriteComparable(h, *t) + + default: + return 0, fmt.Errorf("unsupported type for hashing: %T", t) + } + + return h.Sum64(), nil +} + +type typKey struct { + a Type + b Type +} + +var errNotEquivalent = errors.New("types are not equivalent") + +// typesEquivalent checks if two types are Equivalent. +func (d *deduper) typesEquivalent(ta, tb Type, visited []Type) error { + // Fast path: if Types are Identical, they are also Equivalent. + if ta == tb { + return nil + } + + switch a := ta.(type) { + case *Void: + if _, ok := tb.(*Void); ok { + return nil + } + return errNotEquivalent + + case *Int: + b, ok := tb.(*Int) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name || a.Size != b.Size || a.Encoding != b.Encoding { + return errNotEquivalent + } + return nil + + case *Enum: + b, ok := tb.(*Enum) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name || len(a.Values) != len(b.Values) { + return errNotEquivalent + } + for i := range a.Values { + if a.Values[i].Name != b.Values[i].Name || a.Values[i].Value != b.Values[i].Value { + return errNotEquivalent + } + } + return nil + + case *Fwd: + b, ok := tb.(*Fwd) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name || a.Kind != b.Kind { + return errNotEquivalent + } + return nil + + case *Float: + b, ok := tb.(*Float) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name || a.Size != b.Size { + return errNotEquivalent + } + return nil + + case *Array: + b, ok := tb.(*Array) + if !ok { + return errNotEquivalent + } + + if a.Nelems != b.Nelems { + return errNotEquivalent + } + if err := d.typesEquivalent(a.Index, b.Index, visited); err != nil { + return err + } + if err := d.typesEquivalent(a.Type, b.Type, visited); err != nil { + return err + } + return nil + + case *Pointer: + b, ok := tb.(*Pointer) + if !ok { + return errNotEquivalent + } + + // Detect cycles by tracking visited types. Assume types are Equivalent if + // we have already visited this type in the current Equivalence check. + if slices.Contains(visited, ta) { + return nil + } + visited = append(visited, ta) + + return d.typesEquivalent(a.Target, b.Target, visited) + + case *Struct, *Union: + // Use a cache to avoid recomputation. We only do this for composite types + // since they are where types fan out the most. For other types, the + // overhead of the lookup and update outweighs performance benefits. + cacheKey := typKey{a: ta, b: tb} + if equal, ok := d.eqCache[cacheKey]; ok { + if equal { + return nil + } + return errNotEquivalent + } + + compErr := d.compositeEquivalent(ta, tb, visited) + d.eqCache[cacheKey] = compErr == nil + + return compErr + + case *Typedef: + b, ok := tb.(*Typedef) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name { + return errNotEquivalent + } + return d.typesEquivalent(a.Type, b.Type, visited) + + case *Volatile: + b, ok := tb.(*Volatile) + if !ok { + return errNotEquivalent + } + return d.typesEquivalent(a.Type, b.Type, visited) + + case *Const: + b, ok := tb.(*Const) + if !ok { + return errNotEquivalent + } + return d.typesEquivalent(a.Type, b.Type, visited) + + case *Restrict: + b, ok := tb.(*Restrict) + if !ok { + return errNotEquivalent + } + return d.typesEquivalent(a.Type, b.Type, visited) + + case *Func: + b, ok := tb.(*Func) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name { + return errNotEquivalent + } + return d.typesEquivalent(a.Type, b.Type, visited) + + case *FuncProto: + b, ok := tb.(*FuncProto) + if !ok { + return errNotEquivalent + } + + if err := d.typesEquivalent(a.Return, b.Return, visited); err != nil { + return err + } + if len(a.Params) != len(b.Params) { + return errNotEquivalent + } + for i := range a.Params { + if a.Params[i].Name != b.Params[i].Name { + return errNotEquivalent + } + if err := d.typesEquivalent(a.Params[i].Type, b.Params[i].Type, visited); err != nil { + return err + } + } + return nil + + case *Var: + b, ok := tb.(*Var) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name { + return errNotEquivalent + } + if err := d.typesEquivalent(a.Type, b.Type, visited); err != nil { + return err + } + if a.Linkage != b.Linkage { + return errNotEquivalent + } + return nil + + case *Datasec: + b, ok := tb.(*Datasec) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name || len(a.Vars) != len(b.Vars) { + return errNotEquivalent + } + for i := range a.Vars { + if a.Vars[i].Offset != b.Vars[i].Offset || + a.Vars[i].Size != b.Vars[i].Size { + return errNotEquivalent + } + + if err := d.typesEquivalent(a.Vars[i].Type, b.Vars[i].Type, visited); err != nil { + return err + } + } + return nil + + case *declTag: + b, ok := tb.(*declTag) + if !ok { + return errNotEquivalent + } + if a.Value != b.Value || a.Index != b.Index { + return errNotEquivalent + } + return d.typesEquivalent(a.Type, b.Type, visited) + + case *TypeTag: + b, ok := tb.(*TypeTag) + if !ok { + return errNotEquivalent + } + if a.Value != b.Value { + return errNotEquivalent + } + if err := d.typesEquivalent(a.Type, b.Type, visited); err != nil { + return err + } + return nil + + default: + return fmt.Errorf("unsupported type for equivalence: %T", a) + } +} + +// compositeEquivalent checks if two composite types (Struct or Union) are +// Equivalent. +func (d *deduper) compositeEquivalent(at, bt Type, visited []Type) error { + var ma, mb []Member + switch a := at.(type) { + case *Struct: + b, ok := bt.(*Struct) + if !ok { + return errNotEquivalent + } + + if a.Name != b.Name || a.Size != b.Size || len(a.Members) != len(b.Members) { + return errNotEquivalent + } + ma = a.Members + mb = b.Members + + case *Union: + b, ok := bt.(*Union) + if !ok { + return errNotEquivalent + } + + if a.Name != b.Name || a.Size != b.Size || len(a.Members) != len(b.Members) { + return errNotEquivalent + } + ma = a.Members + mb = b.Members + } + + for i := range ma { + if ma[i].Name != mb[i].Name || ma[i].Offset != mb[i].Offset { + return errNotEquivalent + } + + if err := d.typesEquivalent(ma[i].Type, mb[i].Type, visited); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/cilium/ebpf/btf/ext_info.go b/vendor/github.com/cilium/ebpf/btf/ext_info.go index 6ff5e2b90d..2e2aa629f1 100644 --- a/vendor/github.com/cilium/ebpf/btf/ext_info.go +++ b/vendor/github.com/cilium/ebpf/btf/ext_info.go @@ -13,12 +13,23 @@ import ( "github.com/cilium/ebpf/internal" ) -// ExtInfos contains ELF section metadata. +// ExtInfos contains raw, per-section extended BTF metadata from the .BTF.ext +// ELF section. type ExtInfos struct { - // The slices are sorted by offset in ascending order. - funcInfos map[string]FuncOffsets - lineInfos map[string]LineOffsets - relocationInfos map[string]CORERelocationInfos + Funcs map[string]FuncOffsets + Lines map[string]LineOffsets + CORERelos map[string]CORERelocationOffsets +} + +// Section returns the FuncOffsets, LineOffsets and CORERelocationOffsets for +// the given section name. Returns all nils if ExtInfos is nil, or individual +// nils if there is no metadata of that type for the section. +func (ei *ExtInfos) Section(name string) (FuncOffsets, LineOffsets, CORERelocationOffsets) { + if ei == nil { + return nil, nil, nil + } + + return ei.Funcs[name], ei.Lines[name], ei.CORERelos[name] } // loadExtInfosFromELF parses ext infos from the .BTF.ext section in an ELF. @@ -91,7 +102,7 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er return nil, fmt.Errorf("parsing CO-RE relocation info: %w", err) } - coreRelos := make(map[string]CORERelocationInfos, len(btfCORERelos)) + coreRelos := make(map[string]CORERelocationOffsets, len(btfCORERelos)) for section, brs := range btfCORERelos { coreRelos[section], err = newRelocationInfos(brs, spec, spec.strings) if err != nil { @@ -102,46 +113,6 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er return &ExtInfos{funcInfos, lineInfos, coreRelos}, nil } -type ( - funcInfoMeta struct{} - coreRelocationMeta struct{} -) - -// Assign per-section metadata from BTF to a section's instructions. -func (ei *ExtInfos) Assign(insns asm.Instructions, section string) { - funcInfos := ei.funcInfos[section] - lineInfos := ei.lineInfos[section] - reloInfos := ei.relocationInfos[section] - - AssignMetadataToInstructions(insns, funcInfos, lineInfos, reloInfos) -} - -// Assign per-instruction metadata to the instructions in insns. -func AssignMetadataToInstructions( - insns asm.Instructions, - funcInfos FuncOffsets, - lineInfos LineOffsets, - reloInfos CORERelocationInfos, -) { - iter := insns.Iterate() - for iter.Next() { - if len(funcInfos) > 0 && funcInfos[0].Offset == iter.Offset { - *iter.Ins = WithFuncMetadata(*iter.Ins, funcInfos[0].Func) - funcInfos = funcInfos[1:] - } - - if len(lineInfos) > 0 && lineInfos[0].Offset == iter.Offset { - *iter.Ins = iter.Ins.WithSource(lineInfos[0].Line) - lineInfos = lineInfos[1:] - } - - if len(reloInfos.infos) > 0 && reloInfos.infos[0].offset == iter.Offset { - iter.Ins.Metadata.Set(coreRelocationMeta{}, reloInfos.infos[0].relo) - reloInfos.infos = reloInfos.infos[1:] - } - } -} - // MarshalExtInfos encodes function and line info embedded in insns into kernel // wire format. // @@ -335,8 +306,8 @@ func parseExtInfoRecordSize(r io.Reader, bo binary.ByteOrder) (uint32, error) { return recordSize, nil } -// FuncOffsets is a sorted slice of FuncOffset. -type FuncOffsets []FuncOffset +// FuncOffsets is a slice of FuncOffsets sorted by offset. +type FuncOffsets = []FuncOffset // The size of a FuncInfo in BTF wire format. var FuncInfoSize = uint32(binary.Size(bpfFuncInfo{})) @@ -466,7 +437,7 @@ func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r return nil, fmt.Errorf("expected FuncInfo record size %d, but BTF blob contains %d", exp, got) } - for i := uint32(0); i < recordNum; i++ { + for range recordNum { if err := binary.Read(r, bo, &fi); err != nil { return nil, fmt.Errorf("can't read function info: %v", err) } @@ -518,8 +489,8 @@ func (li *Line) String() string { return li.line } -// LineOffsets contains a sorted list of line infos. -type LineOffsets []LineOffset +// LineOffsets is a slice of LineOffsets sorted by offset. +type LineOffsets = []LineOffset // LineOffset represents a line info and its raw instruction offset. type LineOffset struct { @@ -671,9 +642,15 @@ func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r return nil, fmt.Errorf("expected LineInfo record size %d, but BTF blob contains %d", exp, got) } - out := make([]bpfLineInfo, recordNum) - if err := binary.Read(r, bo, out); err != nil { - return nil, fmt.Errorf("can't read line info: %v", err) + out := make([]bpfLineInfo, 0) + chunk := make([]bpfLineInfo, min(1024, recordNum)) + for remaining := recordNum; remaining > 0; { + n := min(uint32(1024), remaining) + if err := binary.Read(r, bo, chunk[:n]); err != nil { + return nil, fmt.Errorf("can't read line info: %v", err) + } + out = append(out, chunk[:n]...) + remaining -= n } if offsetInBytes { @@ -713,22 +690,32 @@ func (cr *CORERelocation) String() string { return fmt.Sprintf("CORERelocation(%s, %s[%s], local_id=%d)", cr.kind, cr.typ, cr.accessor, cr.id) } +type coreRelocationMeta struct{} + +// CORERelocationMetadata returns the CORERelocation associated with ins. func CORERelocationMetadata(ins *asm.Instruction) *CORERelocation { relo, _ := ins.Metadata.Get(coreRelocationMeta{}).(*CORERelocation) return relo } -// CORERelocationInfos contains a sorted list of co:re relocation infos. -type CORERelocationInfos struct { - infos []coreRelocationInfo +// WithCORERelocationMetadata associates a CORERelocation with ins and returns +// the modified Instruction. +func WithCORERelocationMetadata(ins asm.Instruction, relo *CORERelocation) asm.Instruction { + ins.Metadata.Set(coreRelocationMeta{}, relo) + return ins } -type coreRelocationInfo struct { - relo *CORERelocation - offset asm.RawInstructionOffset +// CORERelocationOffsets is a slice of CORERelocationOffsets sorted by offset. +type CORERelocationOffsets = []CORERelocationOffset + +// CORERelocationOffset represents a CO-RE relocation and an offset at which it +// should be applied. +type CORERelocationOffset struct { + Relo *CORERelocation + Offset asm.RawInstructionOffset } -func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*coreRelocationInfo, error) { +func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*CORERelocationOffset, error) { typ, err := spec.TypeByID(relo.TypeID) if err != nil { return nil, err @@ -744,7 +731,7 @@ func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*cor return nil, fmt.Errorf("accessor %q: %s", accessorStr, err) } - return &coreRelocationInfo{ + return &CORERelocationOffset{ &CORERelocation{ typ, accessor, @@ -755,20 +742,21 @@ func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*cor }, nil } -func newRelocationInfos(brs []bpfCORERelo, spec *Spec, strings *stringTable) (CORERelocationInfos, error) { - rs := CORERelocationInfos{ - infos: make([]coreRelocationInfo, 0, len(brs)), - } +func newRelocationInfos(brs []bpfCORERelo, spec *Spec, strings *stringTable) (CORERelocationOffsets, error) { + rs := make(CORERelocationOffsets, 0, len(brs)) + for _, br := range brs { relo, err := newRelocationInfo(br, spec, strings) if err != nil { - return CORERelocationInfos{}, fmt.Errorf("offset %d: %w", br.InsnOff, err) + return nil, fmt.Errorf("offset %d: %w", br.InsnOff, err) } - rs.infos = append(rs.infos, *relo) + rs = append(rs, *relo) } - sort.Slice(rs.infos, func(i, j int) bool { - return rs.infos[i].offset < rs.infos[j].offset + + sort.Slice(rs, func(i, j int) bool { + return rs[i].Offset < rs[j].Offset }) + return rs, nil } @@ -812,7 +800,7 @@ func parseCOREReloRecords(r io.Reader, bo binary.ByteOrder, recordNum uint32) ([ var out []bpfCORERelo var relo bpfCORERelo - for i := uint32(0); i < recordNum; i++ { + for range recordNum { if err := binary.Read(r, bo, &relo); err != nil { return nil, fmt.Errorf("can't read CO-RE relocation: %v", err) } diff --git a/vendor/github.com/cilium/ebpf/btf/feature.go b/vendor/github.com/cilium/ebpf/btf/feature.go index 5b427f5d35..df365d42f5 100644 --- a/vendor/github.com/cilium/ebpf/btf/feature.go +++ b/vendor/github.com/cilium/ebpf/btf/feature.go @@ -135,7 +135,7 @@ var haveEnum64 = internal.NewFeatureTest("ENUM64", func() error { }, "6.0") func probeBTF(typ Type) error { - b, err := NewBuilder([]Type{typ}) + b, err := NewBuilder([]Type{typ}, nil) if err != nil { return err } diff --git a/vendor/github.com/cilium/ebpf/btf/handle.go b/vendor/github.com/cilium/ebpf/btf/handle.go index 2e483e9d90..6b432f5aec 100644 --- a/vendor/github.com/cilium/ebpf/btf/handle.go +++ b/vendor/github.com/cilium/ebpf/btf/handle.go @@ -1,7 +1,6 @@ package btf import ( - "bytes" "errors" "fmt" "math" @@ -100,6 +99,10 @@ func NewHandleFromRawBTF(btf []byte) (*Handle, error) { attr.BtfLogLevel = 1 } + if errors.Is(err, sys.ErrTokenCapabilities) { + return nil, fmt.Errorf("load btf: %w", err) + } + if err := haveBTF(); err != nil { return nil, err } @@ -153,7 +156,7 @@ func (h *Handle) Spec(base *Spec) (*Spec, error) { return nil, fmt.Errorf("missing base types") } - return loadRawSpec(bytes.NewReader(btfBuffer), internal.NativeEndian, base) + return loadRawSpec(btfBuffer, base) } // Close destroys the handle. @@ -172,6 +175,24 @@ func (h *Handle) FD() int { return h.fd.Int() } +// Clone creates a duplicate of the handle. +// +// Closing the duplicate does not affect the original, and vice versa. +// +// Cloning a nil Handle returns nil. +func (h *Handle) Clone() (*Handle, error) { + if h == nil { + return nil, nil + } + + dup, err := h.fd.Dup() + if err != nil { + return nil, fmt.Errorf("can't clone handle: %w", err) + } + + return &Handle{dup, h.size, h.needsKernelBase}, nil +} + // Info returns metadata about the handle. func (h *Handle) Info() (*HandleInfo, error) { return newHandleInfoFromFD(h.fd) diff --git a/vendor/github.com/cilium/ebpf/btf/kernel.go b/vendor/github.com/cilium/ebpf/btf/kernel.go index fcbe650ce5..8e9084b51e 100644 --- a/vendor/github.com/cilium/ebpf/btf/kernel.go +++ b/vendor/github.com/cilium/ebpf/btf/kernel.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "slices" "sort" "sync" @@ -12,59 +13,60 @@ import ( "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/linux" "github.com/cilium/ebpf/internal/platform" + "github.com/cilium/ebpf/internal/unix" ) -// globalCache amortises decoding BTF across all users of the library. -var globalCache = struct { - sync.RWMutex - kernel *Spec - modules map[string]*Spec -}{ - modules: make(map[string]*Spec), -} - -// FlushKernelSpec removes any cached kernel type information. -func FlushKernelSpec() { - globalCache.Lock() - defer globalCache.Unlock() - - globalCache.kernel = nil - globalCache.modules = make(map[string]*Spec) -} - // LoadKernelSpec returns the current kernel's BTF information. // -// Defaults to /sys/kernel/btf/vmlinux and falls back to scanning the file system -// for vmlinux ELFs. Returns an error wrapping ErrNotSupported if BTF is not enabled. +// Defaults to /sys/kernel/btf/vmlinux and falls back to scanning the file +// system for vmlinux ELFs. Returns an error wrapping [ErrNotSupported] if BTF +// is not enabled. // // Consider using [Cache] instead. func LoadKernelSpec() (*Spec, error) { - spec, err := loadCachedKernelSpec() - return spec.Copy(), err -} + if platform.IsWindows { + return nil, internal.ErrNotSupportedOnOS + } -// load (and cache) the kernel spec. -// -// Does not copy Spec. -func loadCachedKernelSpec() (*Spec, error) { - globalCache.RLock() - spec := globalCache.kernel - globalCache.RUnlock() + fh, err := os.Open("/sys/kernel/btf/vmlinux") + if err == nil { + defer fh.Close() + + info, err := fh.Stat() + if err != nil { + return nil, fmt.Errorf("stat vmlinux: %w", err) + } + + // NB: It's not safe to mmap arbitrary files because mmap(2) doesn't + // guarantee that changes made after mmap are not visible in the mapping. + // + // This is not a problem for vmlinux, since it is always a read-only file. + raw, err := unix.Mmap(int(fh.Fd()), 0, int(info.Size()), unix.PROT_READ, unix.MAP_PRIVATE) + if err != nil { + return LoadSplitSpecFromReader(fh, nil) + } + + spec, err := loadRawSpec(raw, nil) + if err != nil { + _ = unix.Munmap(raw) + return nil, fmt.Errorf("load vmlinux: %w", err) + } + + runtime.AddCleanup(spec.decoder.sharedBuf, func(b []byte) { + _ = unix.Munmap(b) + }, raw) - if spec != nil { return spec, nil } - globalCache.Lock() - defer globalCache.Unlock() - - spec, err := loadKernelSpec() + file, err := findVMLinux() if err != nil { return nil, err } + defer file.Close() - globalCache.kernel = spec - return spec, nil + spec, err := LoadSpecFromReader(file) + return spec, err } // LoadKernelModuleSpec returns the BTF information for the named kernel module. @@ -75,62 +77,11 @@ func loadCachedKernelSpec() (*Spec, error) { // Returns an error wrapping ErrNotSupported if BTF is not enabled. // Returns an error wrapping fs.ErrNotExist if BTF for the specific module doesn't exist. func LoadKernelModuleSpec(module string) (*Spec, error) { - spec, err := loadCachedKernelModuleSpec(module) - return spec.Copy(), err -} - -// load (and cache) a module spec. -// -// Does not copy Spec. -func loadCachedKernelModuleSpec(module string) (*Spec, error) { - globalCache.RLock() - spec := globalCache.modules[module] - globalCache.RUnlock() - - if spec != nil { - return spec, nil - } - - base, err := loadCachedKernelSpec() - if err != nil { - return nil, err - } - - // NB: This only allows a single module to be parsed at a time. Not sure - // it makes a difference. - globalCache.Lock() - defer globalCache.Unlock() - - spec, err = loadKernelModuleSpec(module, base) + base, err := LoadKernelSpec() if err != nil { return nil, err } - - globalCache.modules[module] = spec - return spec, nil -} - -func loadKernelSpec() (_ *Spec, _ error) { - if platform.IsWindows { - return nil, internal.ErrNotSupportedOnOS - } - - fh, err := os.Open("/sys/kernel/btf/vmlinux") - if err == nil { - defer fh.Close() - - spec, err := loadRawSpec(fh, internal.NativeEndian, nil) - return spec, err - } - - file, err := findVMLinux() - if err != nil { - return nil, err - } - defer file.Close() - - spec, err := LoadSpecFromReader(file) - return spec, err + return loadKernelModuleSpec(module, base) } func loadKernelModuleSpec(module string, base *Spec) (*Spec, error) { @@ -149,7 +100,7 @@ func loadKernelModuleSpec(module string, base *Spec) (*Spec, error) { } defer fh.Close() - return loadRawSpec(fh, internal.NativeEndian, base) + return LoadSplitSpecFromReader(fh, base) } // findVMLinux scans multiple well-known paths for vmlinux kernel images. @@ -188,52 +139,48 @@ func findVMLinux() (*os.File, error) { // Cache allows to amortise the cost of decoding BTF across multiple call-sites. // -// It is not safe for concurrent use. +// It is safe for concurrent use. type Cache struct { - KernelTypes *Spec - ModuleTypes map[string]*Spec - LoadedModules []string + mu sync.RWMutex + kernelTypes *Spec + moduleTypes map[string]*Spec + loadedModules []string } -// NewCache creates a new Cache. +// NewCache returns an empty Cache. // -// Opportunistically reuses a global cache if possible. +// Pass it to [CollectionOptions] to share kernel BTF across multiple +// Collection loads. func NewCache() *Cache { - globalCache.RLock() - defer globalCache.RUnlock() - - // This copy is either a no-op or very cheap, since the spec won't contain - // any inflated types. - kernel := globalCache.kernel.Copy() - if kernel == nil { - return &Cache{} - } - - modules := make(map[string]*Spec, len(globalCache.modules)) - for name, spec := range globalCache.modules { - decoder, _ := rebaseDecoder(spec.decoder, kernel.decoder) - // NB: Kernel module BTF can't contain ELF fixups because it is always - // read from sysfs. - modules[name] = &Spec{decoder: decoder} - } - - if len(modules) == 0 { - return &Cache{kernel, nil, nil} - } - - return &Cache{kernel, modules, nil} + return &Cache{} } // Kernel is equivalent to [LoadKernelSpec], except that repeated calls do // not copy the Spec. +// +// Returns an error wrapping [ErrNotSupported] if BTF is not enabled. func (c *Cache) Kernel() (*Spec, error) { - if c.KernelTypes != nil { - return c.KernelTypes, nil + c.mu.RLock() + if c.kernelTypes != nil { + spec := c.kernelTypes + c.mu.RUnlock() + return spec, nil + } + c.mu.RUnlock() + + c.mu.Lock() + defer c.mu.Unlock() + return c.kernel() +} + +func (c *Cache) kernel() (*Spec, error) { + if c.kernelTypes != nil { + return c.kernelTypes, nil } var err error - c.KernelTypes, err = LoadKernelSpec() - return c.KernelTypes, err + c.kernelTypes, err = LoadKernelSpec() + return c.kernelTypes, err } // Module is equivalent to [LoadKernelModuleSpec], except that repeated calls do @@ -241,40 +188,53 @@ func (c *Cache) Kernel() (*Spec, error) { // // All modules also share the return value of [Kernel] as their base. func (c *Cache) Module(name string) (*Spec, error) { - if spec := c.ModuleTypes[name]; spec != nil { + c.mu.RLock() + if spec := c.moduleTypes[name]; spec != nil { + c.mu.RUnlock() return spec, nil } + c.mu.RUnlock() + + c.mu.Lock() + defer c.mu.Unlock() - if c.ModuleTypes == nil { - c.ModuleTypes = make(map[string]*Spec) + if spec := c.moduleTypes[name]; spec != nil { + return spec, nil } - base, err := c.Kernel() - if err != nil { - return nil, err + if c.moduleTypes == nil { + c.moduleTypes = make(map[string]*Spec) } - spec, err := loadCachedKernelModuleSpec(name) + base, err := c.kernel() if err != nil { return nil, err } - // Important: base is shared between modules. This allows inflating common - // types only once. - decoder, err := rebaseDecoder(spec.decoder, base.decoder) + spec, err := loadKernelModuleSpec(name, base) if err != nil { return nil, err } - spec = &Spec{decoder: decoder} - c.ModuleTypes[name] = spec - return spec, err + c.moduleTypes[name] = spec + return spec, nil } // Modules returns a sorted list of all loaded modules. func (c *Cache) Modules() ([]string, error) { - if c.LoadedModules != nil { - return c.LoadedModules, nil + c.mu.RLock() + if c.loadedModules != nil { + modules := slices.Clone(c.loadedModules) + c.mu.RUnlock() + return modules, nil + } + c.mu.RUnlock() + + c.mu.Lock() + defer c.mu.Unlock() + + if c.loadedModules != nil { + return slices.Clone(c.loadedModules), nil } btfDir, err := os.Open("/sys/kernel/btf") @@ -293,6 +253,6 @@ func (c *Cache) Modules() ([]string, error) { }) sort.Strings(entries) - c.LoadedModules = entries - return entries, nil + c.loadedModules = entries + return slices.Clone(entries), nil } diff --git a/vendor/github.com/cilium/ebpf/btf/marshal.go b/vendor/github.com/cilium/ebpf/btf/marshal.go index 308ce8d347..1086605030 100644 --- a/vendor/github.com/cilium/ebpf/btf/marshal.go +++ b/vendor/github.com/cilium/ebpf/btf/marshal.go @@ -78,6 +78,13 @@ type Builder struct { stableIDs map[Type]TypeID // Explicitly added strings. strings *stringTableBuilder + // Deduplication data structure. + deduper *deduper +} + +type BuilderOptions struct { + // Deduplicate enables type deduplication. + Deduplicate bool } // NewBuilder creates a Builder from a list of types. @@ -85,11 +92,20 @@ type Builder struct { // It is more efficient than calling [Add] individually. // // Returns an error if adding any of the types fails. -func NewBuilder(types []Type) (*Builder, error) { +func NewBuilder(types []Type, opts *BuilderOptions) (*Builder, error) { + if opts == nil { + opts = &BuilderOptions{} + } + b := &Builder{ make([]Type, 0, len(types)), make(map[Type]TypeID, len(types)), nil, + nil, + } + + if opts.Deduplicate { + b.deduper = newDeduper() } for _, typ := range types { @@ -113,13 +129,25 @@ func (b *Builder) Empty() bool { // // See [Type] for details on identity. func (b *Builder) Add(typ Type) (TypeID, error) { + if _, ok := typ.(*Void); ok { + // Equality is weird for void, since it is a zero sized type. + return 0, nil + } + + if err := internal.IsNilPointer(typ); err != nil { + return 0, fmt.Errorf("invalid type: %w", err) + } + if b.stableIDs == nil { b.stableIDs = make(map[Type]TypeID) } - if _, ok := typ.(*Void); ok { - // Equality is weird for void, since it is a zero sized type. - return 0, nil + if b.deduper != nil { + var err error + typ, err = b.deduper.deduplicate(typ) + if err != nil { + return 0, err + } } if ds, ok := typ.(*Datasec); ok { @@ -144,6 +172,19 @@ func (b *Builder) Add(typ Type) (TypeID, error) { return id, nil } +// Spec marshals the Builder's types and returns a new Spec to query them. +// +// The resulting Spec does not share any state with the Builder, subsequent +// additions to the Builder will not affect the Spec. +func (b *Builder) Spec() (*Spec, error) { + buf, err := b.Marshal(make([]byte, 0), nil) + if err != nil { + return nil, err + } + + return loadRawSpec(buf, nil) +} + // Marshal encodes all types in the Marshaler into BTF wire format. // // opts may be nil. diff --git a/vendor/github.com/cilium/ebpf/btf/strings.go b/vendor/github.com/cilium/ebpf/btf/strings.go index d09151c0ee..3e3bd688e0 100644 --- a/vendor/github.com/cilium/ebpf/btf/strings.go +++ b/vendor/github.com/cilium/ebpf/btf/strings.go @@ -10,7 +10,7 @@ import ( "sync" ) -// stringTable is contains a sequence of null-terminated strings. +// stringTable contains a sequence of null-terminated strings. // // It is safe for concurrent use. type stringTable struct { @@ -28,28 +28,30 @@ type sizedReader interface { } func readStringTable(r sizedReader, base *stringTable) (*stringTable, error) { - // When parsing split BTF's string table, the first entry offset is derived - // from the last entry offset of the base BTF. - firstStringOffset := uint32(0) - if base != nil { - firstStringOffset = uint32(len(base.bytes)) - } - bytes := make([]byte, r.Size()) if _, err := io.ReadFull(r, bytes); err != nil { return nil, err } - if len(bytes) == 0 { - return nil, errors.New("string table is empty") - } + return newStringTable(bytes, base) +} - if bytes[len(bytes)-1] != 0 { - return nil, errors.New("string table isn't null terminated") +func newStringTable(bytes []byte, base *stringTable) (*stringTable, error) { + // When parsing split BTF's string table, the first entry offset is derived + // from the last entry offset of the base BTF. + firstStringOffset := uint32(0) + if base != nil { + firstStringOffset = uint32(len(base.bytes)) } - if firstStringOffset == 0 && bytes[0] != 0 { - return nil, errors.New("first item in string table is non-empty") + if len(bytes) > 0 { + if bytes[len(bytes)-1] != 0 { + return nil, errors.New("string table isn't null terminated") + } + + if firstStringOffset == 0 && bytes[0] != 0 { + return nil, errors.New("first item in string table is non-empty") + } } return &stringTable{base: base, bytes: bytes}, nil @@ -92,6 +94,9 @@ func (st *stringTable) lookupSlow(offset uint32) ([]byte, error) { } i := bytes.IndexByte(st.bytes[offset:], 0) + if i == -1 { + return nil, fmt.Errorf("string at offset %d is not null terminated", offset) + } return st.bytes[offset : offset+uint32(i)], nil } diff --git a/vendor/github.com/cilium/ebpf/btf/types.go b/vendor/github.com/cilium/ebpf/btf/types.go index fc0a597447..f24beeb983 100644 --- a/vendor/github.com/cilium/ebpf/btf/types.go +++ b/vendor/github.com/cilium/ebpf/btf/types.go @@ -21,15 +21,25 @@ type TypeID = sys.TypeID // Type represents a type described by BTF. // -// Identity of Type follows the [Go specification]: two Types are considered -// equal if they have the same concrete type and the same dynamic value, aka -// they point at the same location in memory. This means that the following -// Types are considered distinct even though they have the same "shape". +// A Type has three properties where compared to other Types. +// +// Identity: follows the [Go specification], two Types are considered identical +// if they have the same concrete type and the same dynamic value, aka they point +// at the same location in memory. This means that the following Types are +// considered distinct even though they have the same "shape". // // a := &Int{Size: 1} // b := &Int{Size: 1} // a != b // +// Equivalence: two Types are considered equivalent if they have the same shape +// and thus are functionally interchangeable, even if they are located at different +// memory addresses. The above two Int types are equivalent. +// +// Compatibility: two Types are considered compatible according to the rules of CO-RE +// see [coreAreTypesCompatible] for details. This is a non-commutative property, +// so A may be compatible with B, but B not compatible with A. +// // [Go specification]: https://go.dev/ref/spec#Comparison_operators type Type interface { // Type can be formatted using the %s and %v verbs. %s outputs only the @@ -50,7 +60,7 @@ type Type interface { // Make a copy of the type, without copying Type members. copy() Type - // New implementations must update walkType. + // New implementations must update children, deduper.typeHash, and typesEquivalent. } var ( @@ -170,8 +180,15 @@ type Struct struct { Tags []string } +// Format supports the width option to %v to limit or extend the number of +// struct member names printed (default 5). +// +// For example, %1v will print only the first member name followed by '...': +// +// Struct:"struct"[fields=3 fieldNames=[a ...]] func (s *Struct) Format(fs fmt.State, verb rune) { - formatType(fs, verb, s, "fields=", len(s.Members)) + max, _ := fs.Width() + formatType(fs, verb, s, "fields=", len(s.Members), "fieldNames=", memberNames(s.Members, max)) } func (s *Struct) TypeName() string { return s.Name } @@ -198,8 +215,15 @@ type Union struct { Tags []string } +// Format supports the width option to %v to limit or extend the number of +// union member names printed (default 5). +// +// For example, %1v will print only the first member name followed by '...': +// +// Union:"union"[fields=3 fieldNames=[a ...]] func (u *Union) Format(fs fmt.State, verb rune) { - formatType(fs, verb, u, "fields=", len(u.Members)) + max, _ := fs.Width() + formatType(fs, verb, u, "fields=", len(u.Members), "fieldNames=", memberNames(u.Members, max)) } func (u *Union) TypeName() string { return u.Name } @@ -217,6 +241,29 @@ func (u *Union) members() []Member { return u.Members } +// memberNames returns the names of members, or its index in the list of members +// if the name is empty. +// +// Returns up to max entries (default 5), followed by '...'. +func memberNames(members []Member, max int) []string { + if max <= 0 { + max = 5 + } + names := make([]string, min(len(members), max+1)) + for i, m := range members { + if i >= max { + names[i] = "..." + break + } + if m.Name == "" { + names[i] = fmt.Sprintf("<%d>", i) + continue + } + names[i] = m.Name + } + return names +} + func copyMembers(orig []Member) []Member { cpy := make([]Member, len(orig)) copy(cpy, orig) @@ -428,6 +475,8 @@ type Func struct { ParamTags [][]string } +type funcInfoMeta struct{} + func FuncMetadata(ins *asm.Instruction) *Func { fn, _ := ins.Metadata.Get(funcInfoMeta{}).(*Func) return fn @@ -644,7 +693,7 @@ func Sizeof(typ Type) (int, error) { elem int64 ) - for i := 0; i < maxResolveDepth; i++ { + for range maxResolveDepth { switch v := typ.(type) { case *Array: if n > 0 && int64(v.Nelems) > math.MaxInt64/n { @@ -840,7 +889,7 @@ type formattableType interface { // Handles cyclical types by only printing cycles up to a certain depth. Elements // in extra are separated by spaces unless the preceding element is a string // ending in '='. -func formatType(f fmt.State, verb rune, t formattableType, extra ...interface{}) { +func formatType(f fmt.State, verb rune, t formattableType, extra ...any) { if verb != 'v' && verb != 's' { fmt.Fprintf(f, "{UNRECOGNIZED: %c}", verb) return diff --git a/vendor/github.com/cilium/ebpf/btf/unmarshal.go b/vendor/github.com/cilium/ebpf/btf/unmarshal.go index fdcf583da6..b56b5a0681 100644 --- a/vendor/github.com/cilium/ebpf/btf/unmarshal.go +++ b/vendor/github.com/cilium/ebpf/btf/unmarshal.go @@ -13,13 +13,22 @@ import ( "sync" ) +// sharedBuf is a buffer which may be shared between multiple decoders. +// +// It must not be modified. Some sharedBuf may be backed by an mmap-ed file, in +// which case the sharedBuf has a finalizer. sharedBuf must therefore always be +// passed as a pointer. +type sharedBuf struct { + raw []byte +} + type decoder struct { // Immutable fields, may be shared. base *decoder byteOrder binary.ByteOrder - raw []byte - strings *stringTable + *sharedBuf + strings *stringTable // The ID for offsets[0]. firstTypeID TypeID // Map from TypeID to offset of the marshaled data in raw. Contains an entry @@ -121,7 +130,7 @@ func newDecoder(raw []byte, bo binary.ByteOrder, strings *stringTable, base *dec return &decoder{ base, bo, - raw, + &sharedBuf{raw}, strings, firstTypeID, offsets, @@ -165,31 +174,6 @@ func allBtfTypeOffsets(buf []byte, bo binary.ByteOrder, header *btfType) iter.Se } } -func rebaseDecoder(d *decoder, base *decoder) (*decoder, error) { - if d.base == nil { - return nil, fmt.Errorf("rebase split spec: not a split spec") - } - - if len(d.base.raw) != len(base.raw) || (len(d.base.raw) > 0 && &d.base.raw[0] != &base.raw[0]) { - return nil, fmt.Errorf("rebase split spec: raw BTF differs") - } - - return &decoder{ - base, - d.byteOrder, - d.raw, - d.strings, - d.firstTypeID, - d.offsets, - d.declTags, - d.namedTypes, - sync.Mutex{}, - make(map[TypeID]Type), - make(map[Type]TypeID), - make(map[TypeID][2]Bits), - }, nil -} - // Copy performs a deep copy of a decoder and its base. func (d *decoder) Copy() *decoder { if d == nil { @@ -220,7 +204,7 @@ func (d *decoder) copy(copiedTypes map[Type]Type) *decoder { return &decoder{ d.base.copy(copiedTypes), d.byteOrder, - d.raw, + d.sharedBuf, d.strings, d.firstTypeID, d.offsets, @@ -564,7 +548,7 @@ func (d *decoder) inflateType(id TypeID) (typ Type, err error) { vlen := header.Vlen() vars := make([]VarSecinfo, 0, vlen) var bSecInfo btfVarSecinfo - for i := 0; i < vlen; i++ { + for i := range vlen { n, err := unmarshalBtfVarSecInfo(&bSecInfo, pos, d.byteOrder) if err != nil { return nil, fmt.Errorf("can't unmarshal btfVarSecinfo %d, id: %d: %w", i, id, err) diff --git a/vendor/github.com/cilium/ebpf/collection.go b/vendor/github.com/cilium/ebpf/collection.go index 77476bd673..42621e74c3 100644 --- a/vendor/github.com/cilium/ebpf/collection.go +++ b/vendor/github.com/cilium/ebpf/collection.go @@ -6,9 +6,10 @@ import ( "fmt" "path/filepath" "reflect" + "runtime" + "slices" "strings" - "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/kallsyms" @@ -35,6 +36,12 @@ type CollectionOptions struct { // The given Maps are Clone()d before being used in the Collection, so the // caller can Close() them freely when they are no longer needed. MapReplacements map[string]*Map + + // Cache amortises the cost of decoding kernel BTF across multiple + // Collection loads. Callers loading more than one Collection should + // allocate a Cache via [btf.NewCache] and share it across loads. + // If nil, a fresh cache is allocated per load and discarded. + Cache *btf.Cache } // CollectionSpec describes a collection. @@ -71,7 +78,7 @@ func (cs *CollectionSpec) Copy() *CollectionSpec { } for name, spec := range cs.Variables { - cpy.Variables[name] = spec.copy(&cpy) + cpy.Variables[name] = spec.Copy() } if cs.Variables == nil { cpy.Variables = nil @@ -93,97 +100,6 @@ func copyMapOfSpecs[T interface{ Copy() T }](m map[string]T) map[string]T { return cpy } -// RewriteMaps replaces all references to specific maps. -// -// Use this function to use pre-existing maps instead of creating new ones -// when calling NewCollection. Any named maps are removed from CollectionSpec.Maps. -// -// Returns an error if a named map isn't used in at least one program. -// -// Deprecated: Pass CollectionOptions.MapReplacements when loading the Collection -// instead. -func (cs *CollectionSpec) RewriteMaps(maps map[string]*Map) error { - for symbol, m := range maps { - // have we seen a program that uses this symbol / map - seen := false - for progName, progSpec := range cs.Programs { - err := progSpec.Instructions.AssociateMap(symbol, m) - - switch { - case err == nil: - seen = true - - case errors.Is(err, asm.ErrUnreferencedSymbol): - // Not all programs need to use the map - - default: - return fmt.Errorf("program %s: %w", progName, err) - } - } - - if !seen { - return fmt.Errorf("map %s not referenced by any programs", symbol) - } - - // Prevent NewCollection from creating rewritten maps - delete(cs.Maps, symbol) - } - - return nil -} - -// MissingConstantsError is returned by [CollectionSpec.RewriteConstants]. -type MissingConstantsError struct { - // The constants missing from .rodata. - Constants []string -} - -func (m *MissingConstantsError) Error() string { - return fmt.Sprintf("some constants are missing from .rodata: %s", strings.Join(m.Constants, ", ")) -} - -// RewriteConstants replaces the value of multiple constants. -// -// The constant must be defined like so in the C program: -// -// volatile const type foobar; -// volatile const type foobar = default; -// -// Replacement values must be of the same length as the C sizeof(type). -// If necessary, they are marshalled according to the same rules as -// map values. -// -// From Linux 5.5 the verifier will use constants to eliminate dead code. -// -// Returns an error wrapping [MissingConstantsError] if a constant doesn't exist. -// -// Deprecated: Use [CollectionSpec.Variables] to interact with constants instead. -// RewriteConstants is now a wrapper around the VariableSpec API. -func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error { - var missing []string - for n, c := range consts { - v, ok := cs.Variables[n] - if !ok { - missing = append(missing, n) - continue - } - - if !v.Constant() { - return fmt.Errorf("variable %s is not a constant", n) - } - - if err := v.Set(c); err != nil { - return fmt.Errorf("rewriting constant %s: %w", n, err) - } - } - - if len(missing) != 0 { - return fmt.Errorf("rewrite constants: %w", &MissingConstantsError{Constants: missing}) - } - - return nil -} - // Assign the contents of a CollectionSpec to a struct. // // This function is a shortcut to manually checking the presence @@ -205,22 +121,22 @@ func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error // // Returns an error if any of the eBPF objects can't be found, or // if the same Spec is assigned multiple times. -func (cs *CollectionSpec) Assign(to interface{}) error { - getValue := func(typ reflect.Type, name string) (interface{}, error) { +func (cs *CollectionSpec) Assign(to any) error { + getValue := func(typ reflect.Type, name string) (any, error) { switch typ { - case reflect.TypeOf((*ProgramSpec)(nil)): + case reflect.TypeFor[*ProgramSpec](): if p := cs.Programs[name]; p != nil { return p, nil } return nil, fmt.Errorf("missing program %q", name) - case reflect.TypeOf((*MapSpec)(nil)): + case reflect.TypeFor[*MapSpec](): if m := cs.Maps[name]; m != nil { return m, nil } return nil, fmt.Errorf("missing map %q", name) - case reflect.TypeOf((*VariableSpec)(nil)): + case reflect.TypeFor[*VariableSpec](): if v := cs.Variables[name]; v != nil { return v, nil } @@ -261,7 +177,7 @@ func (cs *CollectionSpec) Assign(to interface{}) error { // // Returns an error if any of the fields can't be found, or // if the same Map or Program is assigned multiple times. -func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) error { +func (cs *CollectionSpec) LoadAndAssign(to any, opts *CollectionOptions) error { loader, err := newCollectionLoader(cs, opts) if err != nil { return err @@ -273,18 +189,18 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) assignedProgs := make(map[string]bool) assignedVars := make(map[string]bool) - getValue := func(typ reflect.Type, name string) (interface{}, error) { + getValue := func(typ reflect.Type, name string) (any, error) { switch typ { - case reflect.TypeOf((*Program)(nil)): + case reflect.TypeFor[*Program](): assignedProgs[name] = true return loader.loadProgram(name) - case reflect.TypeOf((*Map)(nil)): + case reflect.TypeFor[*Map](): assignedMaps[name] = true return loader.loadMap(name) - case reflect.TypeOf((*Variable)(nil)): + case reflect.TypeFor[*Variable](): assignedVars[name] = true return loader.loadVariable(name) @@ -295,12 +211,12 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) // Load the Maps and Programs requested by the annotated struct. if err := assignValues(to, getValue); err != nil { - return err + return fmt.Errorf("assign values: %w", err) } // Populate the requested maps. Has a chance of lazy-loading other dependent maps. if err := loader.populateDeferredMaps(); err != nil { - return err + return fmt.Errorf("populate deferred maps: %w", err) } // Evaluate the loader's objects after all (lazy)loading has taken place. @@ -431,13 +347,18 @@ func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collec return nil, fmt.Errorf("populating kallsyms caches: %w", err) } + cache := opts.Cache + if cache == nil { + cache = btf.NewCache() + } + return &collectionLoader{ coll, opts, make(map[string]*Map), make(map[string]*Program), make(map[string]*Variable), - newBTFCache(&opts.Programs), + cache, }, nil } @@ -445,20 +366,6 @@ func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collec // during individual program loading. Since we have less context available // at those stages, we batch the lookups here instead to avoid redundant work. func populateKallsyms(progs map[string]*ProgramSpec) error { - // Look up associated kernel modules for all symbols referenced by - // ProgramSpec.AttachTo for program types that support attaching to kmods. - mods := make(map[string]string) - for _, p := range progs { - if p.AttachTo != "" && p.targetsKernelModule() { - mods[p.AttachTo] = "" - } - } - if len(mods) != 0 { - if err := kallsyms.AssignModules(mods); err != nil { - return fmt.Errorf("getting modules from kallsyms: %w", err) - } - } - // Look up addresses of all kernel symbols referenced by all programs. addrs := make(map[string]uint64) for _, p := range progs { @@ -526,7 +433,11 @@ func (cl *collectionLoader) loadMap(mapName string) (*Map, error) { return m, nil } - m, err := newMapWithOptions(mapSpec, cl.opts.Maps) + if err := mapSpec.updateDataSection(cl.coll.Variables, mapName); err != nil { + return nil, fmt.Errorf("assembling contents of map %s: %w", mapName, err) + } + + m, err := newMapWithOptions(mapSpec, cl.opts.Maps, cl.types) if err != nil { return nil, fmt.Errorf("map %s: %w", mapName, err) } @@ -595,6 +506,7 @@ func (cl *collectionLoader) loadProgram(progName string) (*Program, error) { } cl.programs[progName] = prog + return prog, nil } @@ -608,19 +520,7 @@ func (cl *collectionLoader) loadVariable(varName string) (*Variable, error) { return nil, fmt.Errorf("unknown variable %s", varName) } - // Get the key of the VariableSpec's MapSpec in the CollectionSpec. - var mapName string - for n, ms := range cl.coll.Maps { - if ms == varSpec.m { - mapName = n - break - } - } - if mapName == "" { - return nil, fmt.Errorf("variable %s: underlying MapSpec %s was removed from CollectionSpec", varName, varSpec.m.Name) - } - - m, err := cl.loadMap(mapName) + m, err := cl.loadMap(varSpec.SectionName) if err != nil { return nil, fmt.Errorf("variable %s: %w", varName, err) } @@ -637,14 +537,14 @@ func (cl *collectionLoader) loadVariable(varName string) (*Variable, error) { mm, err = m.Memory() } if err != nil && !errors.Is(err, ErrNotSupported) { - return nil, fmt.Errorf("variable %s: getting memory for map %s: %w", varName, mapName, err) + return nil, fmt.Errorf("variable %s: getting memory for map %s: %w", varName, varSpec.SectionName, err) } v, err := newVariable( - varSpec.name, - varSpec.offset, - varSpec.size, - varSpec.t, + varSpec.Name, + varSpec.Offset, + varSpec.Size(), + varSpec.Type, mm, ) if err != nil { @@ -702,6 +602,13 @@ func (cl *collectionLoader) populateDeferredMaps() error { } } + if mapSpec.Type == StructOpsMap { + // populate StructOps data into `kernVData` + if err := cl.populateStructOps(m, mapSpec); err != nil { + return err + } + } + // Populate and freeze the map if specified. if err := m.finalize(mapSpec); err != nil { return fmt.Errorf("populating map %s: %w", mapName, err) @@ -711,6 +618,97 @@ func (cl *collectionLoader) populateDeferredMaps() error { return nil } +// populateStructOps translates the user struct bytes into the kernel value struct +// layout for a struct_ops map and writes the result back to mapSpec.Contents[0]. +func (cl *collectionLoader) populateStructOps(m *Map, mapSpec *MapSpec) error { + userType, ok := btf.As[*btf.Struct](mapSpec.Value) + if !ok { + return fmt.Errorf("value should be a *Struct") + } + + userData, err := mapSpec.dataSection() + if err != nil { + return fmt.Errorf("getting data section: %w", err) + } + if len(userData) < int(userType.Size) { + return fmt.Errorf("user data too short: have %d, need at least %d", len(userData), userType.Size) + } + + vType, _, module, err := structOpsFindTarget(userType, cl.types) + if err != nil { + return fmt.Errorf("struct_ops value type %q: %w", userType.Name, err) + } + defer module.Close() + + // Find the inner ops struct embedded in the value struct. + kType, kTypeOff, err := structOpsFindInnerType(vType) + if err != nil { + return err + } + + kernVData := make([]byte, int(vType.Size)) + for _, m := range userType.Members { + i := slices.IndexFunc(kType.Members, func(km btf.Member) bool { + return km.Name == m.Name + }) + + // Allow field to not exist in target as long as the source is zero. + if i == -1 { + mSize, err := btf.Sizeof(m.Type) + if err != nil { + return fmt.Errorf("sizeof(user.%s): %w", m.Name, err) + } + srcOff := int(m.Offset.Bytes()) + if srcOff < 0 || srcOff+mSize > len(userData) { + return fmt.Errorf("member %q: userdata is too small", m.Name) + } + + // let fail if the field in type user type is missing in type kern type + if !structOpsIsMemZeroed(userData[srcOff : srcOff+mSize]) { + return fmt.Errorf("%s doesn't exist in %s, but it has non-zero value", m.Name, kType.Name) + } + + continue + } + + km := kType.Members[i] + + switch btf.UnderlyingType(m.Type).(type) { + case *btf.Pointer: + // If this is a pointer → resolve struct_ops program. + psKey := kType.Name + ":" + m.Name + for k, ps := range cl.coll.Programs { + if ps.AttachTo == psKey { + p, ok := cl.programs[k] + if !ok || p == nil { + return nil + } + if err := structOpsPopulateValue(km, kernVData[kTypeOff:], p); err != nil { + return err + } + } + } + + default: + // Otherwise → memcpy the field contents. + if err := structOpsCopyMember(m, km, userData, kernVData[kTypeOff:]); err != nil { + return fmt.Errorf("field %s: %w", kType.Name, err) + } + } + } + + // Populate the map explicitly and keep a reference on cl.programs. + // This is necessary because we may inline fds into kernVData which + // may become invalid if the GC frees them. + if err := m.Put(uint32(0), kernVData); err != nil { + return err + } + mapSpec.Contents = nil + runtime.KeepAlive(cl.programs) + + return nil +} + // resolveKconfig resolves all variables declared in .kconfig and populates // m.Contents. Does nothing if the given m.Contents is non-empty. func resolveKconfig(m *MapSpec) error { @@ -851,31 +849,31 @@ func LoadCollection(file string) (*Collection, error) { // // Ownership and Close()ing responsibility is transferred to `to` // for any successful assigns. On error `to` is left in an undefined state. -func (coll *Collection) Assign(to interface{}) error { +func (coll *Collection) Assign(to any) error { assignedMaps := make(map[string]bool) assignedProgs := make(map[string]bool) assignedVars := make(map[string]bool) // Assign() only transfers already-loaded Maps and Programs. No extra // loading is done. - getValue := func(typ reflect.Type, name string) (interface{}, error) { + getValue := func(typ reflect.Type, name string) (any, error) { switch typ { - case reflect.TypeOf((*Program)(nil)): + case reflect.TypeFor[*Program](): if p := coll.Programs[name]; p != nil { assignedProgs[name] = true return p, nil } return nil, fmt.Errorf("missing program %q", name) - case reflect.TypeOf((*Map)(nil)): + case reflect.TypeFor[*Map](): if m := coll.Maps[name]; m != nil { assignedMaps[name] = true return m, nil } return nil, fmt.Errorf("missing map %q", name) - case reflect.TypeOf((*Variable)(nil)): + case reflect.TypeFor[*Variable](): if v := coll.Variables[name]; v != nil { assignedVars[name] = true return v, nil @@ -888,7 +886,7 @@ func (coll *Collection) Assign(to interface{}) error { } if err := assignValues(to, getValue); err != nil { - return err + return fmt.Errorf("assign values: %w", err) } // Finalize ownership transfer @@ -976,7 +974,7 @@ func ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) ([]struc // to a struct, attempt to gather its fields as well. var v reflect.Value switch field.Type.Kind() { - case reflect.Ptr: + case reflect.Pointer: if field.Type.Elem().Kind() != reflect.Struct { continue } @@ -1011,18 +1009,16 @@ func ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) ([]struc // // getValue is called for every tagged field of 'to' and must return the value // to be assigned to the field with the given typ and name. -func assignValues(to interface{}, - getValue func(typ reflect.Type, name string) (interface{}, error)) error { +func assignValues(to any, getValue func(typ reflect.Type, name string) (any, error)) error { + if err := internal.IsNilPointer(to); err != nil { + return err + } toValue := reflect.ValueOf(to) - if toValue.Type().Kind() != reflect.Ptr { + if toValue.Type().Kind() != reflect.Pointer { return fmt.Errorf("%T is not a pointer to struct", to) } - if toValue.IsNil() { - return fmt.Errorf("nil pointer to %T", to) - } - fields, err := ebpfFields(toValue.Elem(), nil) if err != nil { return err diff --git a/vendor/github.com/cilium/ebpf/collection_windows.go b/vendor/github.com/cilium/ebpf/collection_windows.go index c1bbaa21d1..38ce73e8b8 100644 --- a/vendor/github.com/cilium/ebpf/collection_windows.go +++ b/vendor/github.com/cilium/ebpf/collection_windows.go @@ -11,8 +11,8 @@ import ( ) func loadCollectionFromNativeImage(file string) (_ *Collection, err error) { - mapFds := make([]efw.FD, 16) - programFds := make([]efw.FD, 16) + mapFds := make([]efw.FD, 32) + programFds := make([]efw.FD, 32) var maps map[string]*Map var programs map[string]*Program diff --git a/vendor/github.com/cilium/ebpf/elf_reader.go b/vendor/github.com/cilium/ebpf/elf_reader.go index e2b21fa57c..e2f264912c 100644 --- a/vendor/github.com/cilium/ebpf/elf_reader.go +++ b/vendor/github.com/cilium/ebpf/elf_reader.go @@ -8,6 +8,8 @@ import ( "errors" "fmt" "io" + "iter" + "maps" "math" "os" "slices" @@ -116,8 +118,17 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { case sec.Type == elf.SHT_REL: // Store relocations under the section index of the target relSections[elf.SectionIndex(sec.Info)] = sec - case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0: - sections[idx] = newElfSection(sec, programSection) + case sec.Type == elf.SHT_PROGBITS && sec.Size > 0: + if (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0 { + sections[idx] = newElfSection(sec, programSection) + } else if sec.Name == structOpsLinkSec { + // classification based on sec names so that struct_ops-specific + // sections (.struct_ops.link) is correctly recognized + // as non-executable PROGBITS, allowing value placement and link metadata to be loaded. + sections[idx] = newElfSection(sec, structOpsSection) + } else if sec.Name == structOpsSec { + return nil, fmt.Errorf("section %q: got '.struct_ops' section: %w", sec.Name, ErrNotSupported) + } } } @@ -186,6 +197,11 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { return nil, fmt.Errorf("load programs: %w", err) } + // assiociate members in structs with ProgramSpecs using relo + if err := ec.associateStructOpsRelocs(progs); err != nil { + return nil, fmt.Errorf("load struct_ops: %w", err) + } + return &CollectionSpec{ ec.maps, progs, @@ -239,6 +255,7 @@ const ( btfMapSection programSection dataSection + structOpsSection ) type elfSection struct { @@ -263,6 +280,17 @@ func newElfSection(section *elf.Section, kind elfSectionKind) *elfSection { } } +// symbolsSorted returns the section's symbols sorted by offset. +func (es *elfSection) symbolsSorted() iter.Seq2[uint64, elf.Symbol] { + return func(yield func(uint64, elf.Symbol) bool) { + for _, off := range slices.Sorted(maps.Keys(es.symbols)) { + if !yield(off, es.symbols[off]) { + return + } + } + } +} + // assignSymbols takes a list of symbols and assigns them to their // respective sections, indexed by name. func (ec *elfCode) assignSymbols(symbols []elf.Symbol) { @@ -290,10 +318,11 @@ func (ec *elfCode) assignSymbols(symbols []elf.Symbol) { if symType != elf.STT_NOTYPE && symType != elf.STT_FUNC { continue } - // LLVM emits LBB_ (Local Basic Block) symbols that seem to be jump - // targets within sections, but BPF has no use for them. - if symType == elf.STT_NOTYPE && elf.ST_BIND(symbol.Info) == elf.STB_LOCAL && - strings.HasPrefix(symbol.Name, "LBB") { + + // Program sections may contain NOTYPE symbols with local scope, these are + // usually labels for jumps. We do not care for these for the purposes of + // linking and they may overlap with function symbols. + if symType == elf.STT_NOTYPE && elf.ST_BIND(symbol.Info) == elf.STB_LOCAL { continue } // Only collect symbols that occur in program/maps/data sections. @@ -353,6 +382,10 @@ func (ec *elfCode) loadProgramSections() (map[string]*ProgramSpec, error) { return nil, fmt.Errorf("section %v: missing symbols", sec.Name) } + if sec.ReaderAt == nil { + return nil, fmt.Errorf("compressed program section is not supported") + } + funcs, err := ec.loadFunctions(sec) if err != nil { return nil, fmt.Errorf("section %v: %w", sec.Name, err) @@ -405,47 +438,106 @@ func (ec *elfCode) loadProgramSections() (map[string]*ProgramSpec, error) { // be narrowed down to STT_NOTYPE (emitted by clang <8) or STT_FUNC. // // The resulting map is indexed by function name. -func (ec *elfCode) loadFunctions(section *elfSection) (map[string]asm.Instructions, error) { - r := bufio.NewReader(section.Open()) - - // Decode the section's instruction stream. - insns := make(asm.Instructions, 0, section.Size/asm.InstructionSize) - insns, err := asm.AppendInstructions(insns, r, ec.ByteOrder, platform.Linux) - if err != nil { - return nil, fmt.Errorf("decoding instructions for section %s: %w", section.Name, err) - } - if len(insns) == 0 { - return nil, fmt.Errorf("no instructions found in section %s", section.Name) - } +func (ec *elfCode) loadFunctions(sec *elfSection) (map[string]asm.Instructions, error) { + progs := make(map[string]asm.Instructions) + + // Pull out ExtInfos once per section to avoid map lookups on every + // instruction. + fo, lo, ro := ec.extInfo.Section(sec.Name) + + // Raw instruction count since start of the section. ExtInfos point at raw + // insn offsets and ignore the gaps between symbols in case of linked objects. + // We need to count them, we can't obtain this info by any other means. + var raw asm.RawInstructionOffset + + // Sort symbols by offset so we can track instructions by their raw offsets. + for _, sym := range sec.symbolsSorted() { + if progs[sym.Name] != nil { + return nil, fmt.Errorf("duplicate symbol %s in section %s", sym.Name, sec.Name) + } - iter := insns.Iterate() - for iter.Next() { - ins := iter.Ins - offset := iter.Offset.Bytes() + if sym.Value > math.MaxUint32 || sym.Size > math.MaxUint32 { + return nil, fmt.Errorf("symbol %s: offset or size exceeds 32 bits in section %s", sym.Name, sec.Name) + } + if sym.Value+sym.Size > sec.Size { + return nil, fmt.Errorf("symbol %s: size goes out of bounds of section %s", sym.Name, sec.Name) + } - // Tag Symbol Instructions. - if sym, ok := section.symbols[offset]; ok { - *ins = ins.WithSymbol(sym.Name) + // Decode the symbol's instruction stream, limited to its size. + sr := internal.NewBufferedSectionReader(sec, int64(sym.Value), int64(sym.Size)) + insns := make(asm.Instructions, 0, sym.Size/asm.InstructionSize) + insns, err := asm.AppendInstructions(insns, sr, ec.ByteOrder, platform.Linux) + if err != nil { + return nil, fmt.Errorf("decoding instructions for symbol %s in section %s: %w", sym.Name, sec.Name, err) + } + if len(insns) == 0 { + return nil, fmt.Errorf("no instructions found for symbol %s in section %s", sym.Name, sec.Name) } - // Apply any relocations for the current instruction. - // If no relocation is present, resolve any section-relative function calls. - if rel, ok := section.relocations[offset]; ok { - if err := ec.relocateInstruction(ins, rel); err != nil { - return nil, fmt.Errorf("offset %d: relocating instruction: %w", offset, err) - } - } else { - if err := referenceRelativeJump(ins, offset, section.symbols); err != nil { - return nil, fmt.Errorf("offset %d: resolving relative jump: %w", offset, err) + // Mark the first instruction as the start of a function. + insns[0] = insns[0].WithSymbol(sym.Name) + + iter := insns.Iterate() + for iter.Next() { + // Global byte offset of the instruction within the ELF section. + offset := sym.Value + iter.Offset.Bytes() + + // Apply any relocations for the current instruction. If no relocation is + // present, resolve any section-relative function calls. + if rel, ok := sec.relocations[offset]; ok { + if err := ec.relocateInstruction(iter.Ins, rel); err != nil { + return nil, fmt.Errorf("offset %d in section %s: relocating instruction: %w", offset, sec.Name, err) + } + } else { + if err := referenceRelativeJump(iter.Ins, offset, sec.symbols); err != nil { + return nil, fmt.Errorf("offset %d in section %s: resolving relative jump: %w", offset, sec.Name, err) + } } + + assignMetadata(iter.Ins, raw, &fo, &lo, &ro) + + raw += iter.Ins.Width() } + + // Emit the program's instructions. + progs[sym.Name] = insns + } + + return progs, nil +} + +// take pops and returns the first item in q if it matches the given predicate +// f. Otherwise, it returns nil. +func take[T any](q *[]T, f func(T) bool) *T { + if q == nil || len(*q) == 0 { + return nil } - if ec.extInfo != nil { - ec.extInfo.Assign(insns, section.Name) + out := (*q)[0] + if f(out) { + *q = (*q)[1:] + return &out } - return splitSymbols(insns) + return nil +} + +// Tag the instruction with any ExtInfo metadata that's pointing at the given +// raw instruction. +func assignMetadata(ins *asm.Instruction, raw asm.RawInstructionOffset, + fo *btf.FuncOffsets, lo *btf.LineOffsets, ro *btf.CORERelocationOffsets) { + + if f := take(fo, func(f btf.FuncOffset) bool { return f.Offset == raw }); f != nil { + *ins = btf.WithFuncMetadata(*ins, f.Func) + } + + if l := take(lo, func(l btf.LineOffset) bool { return l.Offset == raw }); l != nil { + *ins = ins.WithSource(l.Line) + } + + if r := take(ro, func(r btf.CORERelocationOffset) bool { return r.Offset == raw }); r != nil { + *ins = btf.WithCORERelocationMetadata(*ins, r.Relo) + } } // referenceRelativeJump turns a relative jump to another bpf subprogram within @@ -573,7 +665,7 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err switch typ { case elf.STT_NOTYPE, elf.STT_FUNC: - if bind != elf.STB_GLOBAL { + if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK { return fmt.Errorf("call: %s: %w: %s", name, errUnsupportedBinding, bind) } @@ -710,89 +802,102 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err return nil } +// loadMaps iterates over all ELF sections marked as map sections (like .maps) +// and parses each symbol into a MapSpec. func (ec *elfCode) loadMaps() error { for _, sec := range ec.sections { if sec.kind != mapSection { continue } - nSym := len(sec.symbols) - if nSym == 0 { + if len(sec.symbols) == 0 { return fmt.Errorf("section %v: no symbols", sec.Name) } - if sec.Size%uint64(nSym) != 0 { - return fmt.Errorf("section %v: map descriptors are not of equal size", sec.Name) + if sec.ReaderAt == nil { + return fmt.Errorf("compressed map section is not supported") } - // If the ELF has BTF, pull out the btf.Var for each map definition to - // extract decl tags from. - varsByName := make(map[string]*btf.Var) - if ec.btf != nil { - var ds *btf.Datasec - if err := ec.btf.TypeByName(sec.Name, &ds); err == nil { - for _, vsi := range ds.Vars { - v, ok := btf.As[*btf.Var](vsi.Type) - if !ok { - return fmt.Errorf("section %v: btf.VarSecInfo doesn't point to a *btf.Var: %T", sec.Name, vsi.Type) - } - varsByName[string(v.Name)] = v - } - } + vars, err := ec.sectionVars(ec.btf, sec.Name) + if err != nil { + return fmt.Errorf("section %v: loading map variable BTF: %w", sec.Name, err) } - var ( - r = bufio.NewReader(sec.Open()) - size = sec.Size / uint64(nSym) - ) - for i, offset := 0, uint64(0); i < nSym; i, offset = i+1, offset+size { - mapSym, ok := sec.symbols[offset] - if !ok { - return fmt.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset) + for _, sym := range sec.symbols { + name := sym.Name + if ec.maps[name] != nil { + return fmt.Errorf("duplicate symbol %s in section %s", name, sec.Name) } - mapName := mapSym.Name - if ec.maps[mapName] != nil { - return fmt.Errorf("section %v: map %v already exists", sec.Name, mapSym) + if sym.Value > math.MaxUint32 || sym.Size > math.MaxUint32 { + return fmt.Errorf("symbol %s: offset or size exceeds 32 bits in section %s", sym.Name, sec.Name) + } + if sym.Value+sym.Size > sec.Size { + return fmt.Errorf("symbol %s: size goes out of bounds of section %s", name, sec.Name) } - lr := io.LimitReader(r, int64(size)) + sr := internal.NewBufferedSectionReader(sec, int64(sym.Value), int64(sym.Size)) spec := MapSpec{ - Name: sanitizeName(mapName, -1), + Name: sanitizeName(name, -1), } switch { - case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil: - return fmt.Errorf("map %s: missing type", mapName) - case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil: - return fmt.Errorf("map %s: missing key size", mapName) - case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil: - return fmt.Errorf("map %s: missing value size", mapName) - case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil: - return fmt.Errorf("map %s: missing max entries", mapName) - case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil: - return fmt.Errorf("map %s: missing flags", mapName) - } - - extra, err := io.ReadAll(lr) + case binary.Read(sr, ec.ByteOrder, &spec.Type) != nil: + return fmt.Errorf("map %s: missing type", name) + case binary.Read(sr, ec.ByteOrder, &spec.KeySize) != nil: + return fmt.Errorf("map %s: missing key size", name) + case binary.Read(sr, ec.ByteOrder, &spec.ValueSize) != nil: + return fmt.Errorf("map %s: missing value size", name) + case binary.Read(sr, ec.ByteOrder, &spec.MaxEntries) != nil: + return fmt.Errorf("map %s: missing max entries", name) + case binary.Read(sr, ec.ByteOrder, &spec.Flags) != nil: + return fmt.Errorf("map %s: missing flags", name) + } + + extra, err := io.ReadAll(sr) if err != nil { - return fmt.Errorf("map %s: reading map tail: %w", mapName, err) + return fmt.Errorf("map %s: reading map tail: %w", name, err) } if len(extra) > 0 { spec.Extra = bytes.NewReader(extra) } - if v, ok := varsByName[mapName]; ok { + if v, ok := vars[name]; ok { spec.Tags = slices.Clone(v.Tags) } - ec.maps[mapName] = &spec + ec.maps[name] = &spec } } return nil } +// sectionVars looks up the BTF Datasec for the given section name and returns a +// map of variable names to their btf.Var definitions. +func (ec *elfCode) sectionVars(spec *btf.Spec, sec string) (map[string]*btf.Var, error) { + vars := make(map[string]*btf.Var) + + if spec == nil { + return vars, nil + } + + var ds *btf.Datasec + if err := ec.btf.TypeByName(sec, &ds); err != nil { + return vars, nil + } + + for _, vsi := range ds.Vars { + v, ok := btf.As[*btf.Var](vsi.Type) + if !ok { + return nil, fmt.Errorf("btf.VarSecInfo doesn't point to a *btf.Var: %T", vsi.Type) + } + vars[string(v.Name)] = v + } + + return vars, nil +} + // loadBTFMaps iterates over all ELF sections marked as BTF map sections // (like .maps) and parses them into MapSpecs. Dump the .maps section and // any relocations with `readelf -x .maps -r `. @@ -806,32 +911,47 @@ func (ec *elfCode) loadBTFMaps() error { return fmt.Errorf("missing BTF") } - // Each section must appear as a DataSec in the ELF's BTF blob. - var ds *btf.Datasec - if err := ec.btf.TypeByName(sec.Name, &ds); err != nil { - return fmt.Errorf("cannot find section '%s' in BTF: %w", sec.Name, err) + if sec.ReaderAt == nil { + return fmt.Errorf("compressed BTF map section is not supported") + } + + vars, err := ec.sectionVars(ec.btf, sec.Name) + if err != nil { + return fmt.Errorf("section %v: loading map variable BTF: %w", sec.Name, err) + } + + if len(vars) != len(sec.symbols) { + return fmt.Errorf("section %v: contains %d symbols but %d btf.Vars", sec.Name, len(sec.symbols), len(vars)) + } + + syms := make(map[string]elf.Symbol) + for _, sym := range sec.symbols { + syms[sym.Name] = sym } - // Open a Reader to the ELF's raw section bytes so we can assert that all - // of them are zero on a per-map (per-Var) basis. For now, the section's - // sole purpose is to receive relocations, so all must be zero. - rs := sec.Open() + for _, v := range vars { + name := v.Name - for _, vs := range ds.Vars { - // BPF maps are declared as and assigned to global variables, - // so iterate over each Var in the DataSec and validate their types. - v, ok := vs.Type.(*btf.Var) + // Find the ELF symbol corresponding to this Var. + sym, ok := syms[name] if !ok { - return fmt.Errorf("section %v: unexpected type %s", sec.Name, vs.Type) + return fmt.Errorf("section %v: missing symbol for map %s", sec.Name, name) } - name := string(v.Name) + + if sym.Value > math.MaxUint32 || sym.Size > math.MaxUint32 { + return fmt.Errorf("symbol %s: offset or size exceeds 32 bits in section %s", sym.Name, sec.Name) + } + if sym.Value+sym.Size > sec.Size { + return fmt.Errorf("section %v: symbol %s: size goes out of bounds of section", sec.Name, name) + } + + sr := internal.NewBufferedSectionReader(sec, int64(sym.Value), int64(sym.Size)) // The BTF metadata for each Var contains the full length of the map // declaration, so read the corresponding amount of bytes from the ELF. // This way, we can pinpoint which map declaration contains unexpected // (and therefore unsupported) data. - _, err := io.Copy(internal.DiscardZeroes{}, io.LimitReader(rs, int64(vs.Size))) - if err != nil { + if _, err = io.Copy(internal.DiscardZeroes{}, sr); err != nil { return fmt.Errorf("section %v: map %s: initializing BTF map definitions: %w", sec.Name, name, internal.ErrNotSupported) } @@ -845,22 +965,12 @@ func (ec *elfCode) loadBTFMaps() error { return fmt.Errorf("expected struct, got %s", v.Type) } - mapSpec, err := mapSpecFromBTF(sec, &vs, mapStruct, ec.btf, name, false) + spec, err := mapSpecFromBTF(sec, sym, v, mapStruct, ec.btf, name, false) if err != nil { return fmt.Errorf("map %v: %w", name, err) } - ec.maps[name] = mapSpec - } - - // Drain the ELF section reader to make sure all bytes are accounted for - // with BTF metadata. - i, err := io.Copy(io.Discard, rs) - if err != nil { - return fmt.Errorf("section %v: unexpected error reading remainder of ELF section: %w", sec.Name, err) - } - if i > 0 { - return fmt.Errorf("section %v: %d unexpected remaining bytes in ELF section, invalid BTF?", sec.Name, i) + ec.maps[name] = spec } } @@ -870,13 +980,14 @@ func (ec *elfCode) loadBTFMaps() error { // mapSpecFromBTF produces a MapSpec based on a btf.Struct def representing // a BTF map definition. The name and spec arguments will be copied to the // resulting MapSpec, and inner must be true on any recursive invocations. -func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *btf.Spec, name string, inner bool) (*MapSpec, error) { +func mapSpecFromBTF(es *elfSection, sym elf.Symbol, v *btf.Var, def *btf.Struct, spec *btf.Spec, name string, inner bool) (*MapSpec, error) { var ( key, value btf.Type - keySize, valueSize uint32 + keySize, valueSize uint64 mapType MapType - flags, maxEntries uint32 + flags, maxEntries uint64 pinType PinType + mapExtra uint64 innerMapSpec *MapSpec contents []MapKV err error @@ -920,7 +1031,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b return nil, fmt.Errorf("can't get size of BTF key: %w", err) } - keySize = uint32(size) + keySize = uint64(size) case "value": if valueSize != 0 { @@ -939,7 +1050,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b return nil, fmt.Errorf("can't get size of BTF value: %w", err) } - valueSize = uint32(size) + valueSize = uint64(size) case "key_size": // Key needs to be nil and keySize needs to be 0 for key_size to be @@ -1013,7 +1124,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b // on kernels 5.2 and up) // Pass the BTF spec from the parent object, since both parent and // child must be created from the same BTF blob (on kernels that support BTF). - innerMapSpec, err = mapSpecFromBTF(es, vs, t, spec, name+"_inner", true) + innerMapSpec, err = mapSpecFromBTF(es, sym, v, t, spec, name+"_inner", true) if err != nil { return nil, fmt.Errorf("can't parse BTF map definition of inner map: %w", err) } @@ -1029,13 +1140,16 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b return nil, fmt.Errorf("unsupported value type %q in 'values' field", t) } - contents, err = resolveBTFValuesContents(es, vs, member) + contents, err = resolveBTFValuesContents(es, sym, member) if err != nil { return nil, fmt.Errorf("resolving values contents: %w", err) } case "map_extra": - return nil, fmt.Errorf("BTF map definition: field %s: %w", member.Name, ErrNotSupported) + mapExtra, err = uintFromBTF(member.Type) + if err != nil { + return nil, fmt.Errorf("resolving map_extra: %w", err) + } default: return nil, fmt.Errorf("unrecognized field %s in BTF map definition", member.Name) @@ -1049,41 +1163,48 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b valueSize = 0 } - v, ok := btf.As[*btf.Var](vs.Type) - if !ok { - return nil, fmt.Errorf("BTF map definition: btf.VarSecInfo doesn't point to a *btf.Var: %T", vs.Type) - } - return &MapSpec{ Name: sanitizeName(name, -1), Type: MapType(mapType), - KeySize: keySize, - ValueSize: valueSize, - MaxEntries: maxEntries, - Flags: flags, + KeySize: uint32(keySize), + ValueSize: uint32(valueSize), + MaxEntries: uint32(maxEntries), + Flags: uint32(flags), Key: key, Value: value, Pinning: pinType, InnerMap: innerMapSpec, Contents: contents, Tags: slices.Clone(v.Tags), + MapExtra: mapExtra, }, nil } -// uintFromBTF resolves the __uint macro, which is a pointer to a sized -// array, e.g. for int (*foo)[10], this function will return 10. -func uintFromBTF(typ btf.Type) (uint32, error) { - ptr, ok := typ.(*btf.Pointer) - if !ok { - return 0, fmt.Errorf("not a pointer: %v", typ) - } +// uintFromBTF resolves the __uint and __ulong macros. +// +// __uint emits a pointer to a sized array. For int (*foo)[10], this function +// will return 10. +// +// __ulong emits an enum with a single value that can represent a 64-bit +// integer. The first (and only) enum value is returned. +func uintFromBTF(typ btf.Type) (uint64, error) { + switch t := typ.(type) { + case *btf.Pointer: + arr, ok := t.Target.(*btf.Array) + if !ok { + return 0, fmt.Errorf("not a pointer to array: %v", typ) + } + return uint64(arr.Nelems), nil - arr, ok := ptr.Target.(*btf.Array) - if !ok { - return 0, fmt.Errorf("not a pointer to array: %v", typ) - } + case *btf.Enum: + if len(t.Values) == 0 { + return 0, errors.New("enum has no values") + } + return t.Values[0].Value, nil - return arr.Nelems, nil + default: + return 0, fmt.Errorf("not a pointer or enum: %v", typ) + } } // resolveBTFArrayMacro resolves the __array macro, which declares an array @@ -1103,58 +1224,74 @@ func resolveBTFArrayMacro(typ btf.Type) (btf.Type, error) { return ptr.Target, nil } -// resolveBTFValuesContents resolves relocations into ELF sections belonging -// to btf.VarSecinfo's. This can be used on the 'values' member in BTF map -// definitions to extract static declarations of map contents. -func resolveBTFValuesContents(es *elfSection, vs *btf.VarSecinfo, member btf.Member) ([]MapKV, error) { - // The elements of a .values pointer array are not encoded in BTF. - // Instead, relocations are generated into each array index. - // However, it's possible to leave certain array indices empty, so all - // indices' offsets need to be checked for emitted relocations. - - // The offset of the 'values' member within the _struct_ (in bits) - // is the starting point of the array. Convert to bytes. Add VarSecinfo - // offset to get the absolute position in the ELF blob. - start := member.Offset.Bytes() + vs.Offset - // 'values' is encoded in BTF as a zero (variable) length struct - // member, and its contents run until the end of the VarSecinfo. - // Add VarSecinfo offset to get the absolute position in the ELF blob. - end := vs.Size + vs.Offset - // The size of an address in this section. This determines the width of - // an index in the array. - align := uint32(es.Addralign) - - // Check if variable-length section is aligned. - if (end-start)%align != 0 { - return nil, errors.New("unaligned static values section") - } +// valuesRelocations returns an iterator over the relocations in the ELF section +// corresponding to the elements of a .values array in a BTF map definition. Each +// iteration yields the array index and the symbol referenced by the relocation +// at that index. Empty indices are skipped. +func valuesRelocations(es *elfSection, sym elf.Symbol, member btf.Member) iter.Seq2[uint32, elf.Symbol] { + // The elements of a .values pointer array are not encoded in BTF itself. + // Instead, each array index receives a relocation pointing at a symbol + // (map/prog) in another section. However, it's possible to leave certain + // array indices empty, so all indices' offsets need to be checked for emitted + // relocations. + + // Absolute offset of the .values member within the section. + start := sym.Value + uint64(member.Offset.Bytes()) + + // .values is a variable-length struct member, so its contents run until the + // end of the symbol. The symbol offset + size is the absolute offset of the + // end of the array in the section. + end := sym.Value + sym.Size + + // The size of an address in this section. This determines the width of an + // index in the array. + align := es.Addralign + + // Amount of elements in the .values array. elems := (end - start) / align - if elems == 0 { - return nil, nil - } + return func(yield func(uint32, elf.Symbol) bool) { + for i := range uint32(elems) { + // off increases by align on each iteration, starting at .values. + off := start + (uint64(i) * align) - contents := make([]MapKV, 0, elems) + r, ok := es.relocations[off] + if !ok { + continue + } - // k is the array index, off is its corresponding ELF section offset. - for k, off := uint32(0), start; k < elems; k, off = k+1, off+align { - r, ok := es.relocations[uint64(off)] - if !ok { - continue + if !yield(i, r) { + return + } } + } + +} + +// resolveBTFValuesContents looks up the symbols referenced by the relocations +// in a .values array and returns them as MapKV pairs, where the key is the +// array index and the value is the symbol name. Empty indices are skipped. +func resolveBTFValuesContents(es *elfSection, sym elf.Symbol, member btf.Member) ([]MapKV, error) { + var contents []MapKV + + if member.Offset.Bytes() > uint32(sym.Size) { + return nil, fmt.Errorf("member offset %d exceeds symbol size %d", member.Offset.Bytes(), sym.Size) + } - // Relocation exists for the current offset in the ELF section. - // Emit a value stub based on the type of relocation to be replaced by - // a real fd later in the pipeline before populating the map. - // Map keys are encoded in MapKV entries, so empty array indices are - // skipped here. - switch t := elf.ST_TYPE(r.Info); t { + if es.Addralign == 0 { + return nil, fmt.Errorf("section has no address alignment, can't resolve .values contents") + } + + for i, sym := range valuesRelocations(es, sym, member) { + // Emit a value stub based on the type of relocation to be replaced by a + // real fd later in the pipeline before populating the Map. + switch t := elf.ST_TYPE(sym.Info); t { case elf.STT_FUNC: - contents = append(contents, MapKV{uint32(k), r.Name}) + contents = append(contents, MapKV{i, sym.Name}) case elf.STT_OBJECT: - contents = append(contents, MapKV{uint32(k), r.Name}) + contents = append(contents, MapKV{i, sym.Name}) default: - return nil, fmt.Errorf("unknown relocation type %v for symbol %s", t, r.Name) + return nil, fmt.Errorf("unknown relocation type %v for symbol %s", t, sym.Name) } } @@ -1194,14 +1331,15 @@ func (ec *elfCode) loadDataSections() error { mapSpec.Flags = sys.BPF_F_RDONLY_PROG } + var data []byte switch sec.Type { // Only open the section if we know there's actual data to be read. case elf.SHT_PROGBITS: - data, err := sec.Data() + var err error + data, err = sec.Data() if err != nil { return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err) } - mapSpec.Contents = []MapKV{{uint32(0), data}} case elf.SHT_NOBITS: // NOBITS sections like .bss contain only zeroes and are not allocated in @@ -1209,12 +1347,14 @@ func (ec *elfCode) loadDataSections() error { // them. Don't attempt reading zeroes from the ELF, instead allocate the // zeroed memory to support getting and setting VariableSpecs for sections // like .bss. - mapSpec.Contents = []MapKV{{uint32(0), make([]byte, sec.Size)}} + data = make([]byte, sec.Size) default: return fmt.Errorf("data section %s: unknown section type %s", sec.Name, sec.Type) } + mapSpec.Contents = []MapKV{{uint32(0), data}} + for off, sym := range sec.symbols { // Skip symbols marked with the 'hidden' attribute. if elf.ST_VISIBILITY(sym.Other) == elf.STV_HIDDEN || @@ -1243,11 +1383,23 @@ func (ec *elfCode) loadDataSections() error { continue } + if off+sym.Size > uint64(len(data)) { + return fmt.Errorf("data section %s: variable %s exceeds section bounds", sec.Name, sym.Name) + } + + if off > math.MaxUint32 { + return fmt.Errorf("data section %s: variable %s offset %d exceeds maximum", sec.Name, sym.Name, off) + } + + if sym.Size > math.MaxUint32 { + return fmt.Errorf("data section %s: variable %s size %d exceeds maximum", sec.Name, sym.Name, sym.Size) + } + ec.vars[sym.Name] = &VariableSpec{ - name: sym.Name, - offset: off, - size: sym.Size, - m: mapSpec, + SectionName: sec.Name, + Name: sym.Name, + Offset: uint32(off), + Value: slices.Clone(data[off : off+sym.Size]), } } @@ -1278,17 +1430,17 @@ func (ec *elfCode) loadDataSections() error { continue } - if uint64(v.Offset) != ev.offset { - return fmt.Errorf("data section %s: variable %s datasec offset (%d) doesn't match ELF symbol offset (%d)", sec.Name, name, v.Offset, ev.offset) + if v.Offset != ev.Offset { + return fmt.Errorf("data section %s: variable %s datasec offset (%d) doesn't match ELF symbol offset (%d)", sec.Name, name, v.Offset, ev.Offset) } - if uint64(v.Size) != ev.size { - return fmt.Errorf("data section %s: variable %s size in datasec (%d) doesn't match ELF symbol size (%d)", sec.Name, name, v.Size, ev.size) + if v.Size != ev.Size() { + return fmt.Errorf("data section %s: variable %s size in datasec (%d) doesn't match ELF symbol size (%d)", sec.Name, name, v.Size, ev.Size()) } // Decouple the Var in the VariableSpec from the underlying DataSec in // the MapSpec to avoid modifications from affecting map loads later on. - ev.t = btf.Copy(vt).(*btf.Var) + ev.Type = btf.Copy(vt).(*btf.Var) } } } @@ -1363,6 +1515,95 @@ func (ec *elfCode) loadKsymsSection() error { return nil } +// associateStructOpsRelocs handles `.struct_ops.link` +// and associates the target function with the correct struct member in the map. +func (ec *elfCode) associateStructOpsRelocs(progs map[string]*ProgramSpec) error { + for _, sec := range ec.sections { + if sec.kind != structOpsSection { + continue + } + + userData, err := sec.Data() + if err != nil { + return fmt.Errorf("failed to read section data: %w", err) + } + + if ec.btf == nil { + return fmt.Errorf("struct_ops section %s: missing BTF", sec.Name) + } + + // Resolve the BTF datasec describing variables in this section. + var ds *btf.Datasec + if err := ec.btf.TypeByName(sec.Name, &ds); err != nil { + return fmt.Errorf("datasec %s: %w", sec.Name, err) + } + + // Set flags for .struct_ops.link (BPF_F_LINK). + flags := uint32(0) + if sec.Name == structOpsLinkSec { + flags = sys.BPF_F_LINK + } + + for _, vsi := range ds.Vars { + userSt, baseOff, err := ec.createStructOpsMap(vsi, userData, flags) + if err != nil { + return err + } + + if err := structOpsSetAttachTo(sec, baseOff, userSt, progs); err != nil { + return err + } + } + } + + return nil +} + +// createStructOpsMap() creates and registers a MapSpec for a struct_ops +func (ec *elfCode) createStructOpsMap(vsi btf.VarSecinfo, userData []byte, flags uint32) (*btf.Struct, uint32, error) { + varType, ok := btf.As[*btf.Var](vsi.Type) + if !ok { + return nil, 0, fmt.Errorf("vsi: expect var, got %T", vsi.Type) + } + + mapName := varType.Name + + userSt, ok := btf.As[*btf.Struct](varType.Type) + if !ok { + return nil, 0, fmt.Errorf("var %s: expect struct, got %T", varType.Name, varType.Type) + } + + userSize := userSt.Size + baseOff := vsi.Offset + if uint64(baseOff)+uint64(userSize) > uint64(len(userData)) { + return nil, 0, fmt.Errorf("%s exceeds section", mapName) + } + + // Register the MapSpec for this struct_ops instance if doesn't exist + if _, exists := ec.maps[mapName]; exists { + return nil, 0, fmt.Errorf("struct_ops map %s: already exists", mapName) + } + + ec.maps[mapName] = &MapSpec{ + Name: mapName, + Type: StructOpsMap, + Key: &btf.Int{Size: 4}, + KeySize: structOpsKeySize, + ValueSize: userSize, // length of the user-struct type + Value: userSt, + Flags: flags, + MaxEntries: 1, + Contents: []MapKV{ + { + Key: uint32(0), + Value: append([]byte(nil), userData[baseOff:baseOff+userSize]...), + }, + }, + } + + return userSt, baseOff, nil +} + type libbpfElfSectionDef struct { pattern string programType sys.ProgType @@ -1383,29 +1624,9 @@ const ( _SEC_XDP_FRAGS _SEC_USDT - // Ignore any present extra in order to preserve backwards compatibility - // with earlier versions of the library. - ignoreExtra - _SEC_ATTACHABLE_OPT = _SEC_ATTACHABLE | _SEC_EXP_ATTACH_OPT ) -func init() { - // Compatibility with older versions of the library. - // We prepend libbpf definitions since they contain a prefix match - // for "xdp". - elfSectionDefs = append([]libbpfElfSectionDef{ - {"xdp.frags/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP, _SEC_XDP_FRAGS | ignoreExtra}, - {"xdp.frags_devmap/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP_DEVMAP, _SEC_XDP_FRAGS}, - {"xdp_devmap/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP_DEVMAP, 0}, - {"xdp.frags_cpumap/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP_CPUMAP, _SEC_XDP_FRAGS}, - {"xdp_cpumap/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP_CPUMAP, 0}, - // This has been in the library since the beginning of time. Not sure - // where it came from. - {"seccomp", sys.BPF_PROG_TYPE_SOCKET_FILTER, 0, _SEC_NONE}, - }, elfSectionDefs...) -} - func getProgType(sectionName string) (ProgramType, AttachType, uint32, string) { // Skip optional program marking for now. sectionName = strings.TrimPrefix(sectionName, "?") @@ -1426,15 +1647,13 @@ func getProgType(sectionName string) (ProgramType, AttachType, uint32, string) { if t.flags&_SEC_XDP_FRAGS > 0 { flags |= sys.BPF_F_XDP_HAS_FRAGS } - if t.flags&_SEC_EXP_ATTACH_OPT > 0 { - if programType == XDP { - // The library doesn't yet have code to fallback to not specifying - // attach type. Only do this for XDP since we've enforced correct - // attach type for all other program types. - attachType = AttachNone - } - } - if t.flags&ignoreExtra > 0 { + + // The libbpf documentation on program types states: 'The struct_ops attach + // format supports struct_ops[.s]/ convention, but name is ignored and + // it is recommended to just use plain SEC("struct_ops[.s]").' + // + // Ignore any extra for struct_ops to match libbpf behaviour. + if programType == StructOps { extra = "" } diff --git a/vendor/github.com/cilium/ebpf/elf_sections.go b/vendor/github.com/cilium/ebpf/elf_sections.go index 43dcfb103e..1ded4c4d54 100644 --- a/vendor/github.com/cilium/ebpf/elf_sections.go +++ b/vendor/github.com/cilium/ebpf/elf_sections.go @@ -21,8 +21,10 @@ var elfSectionDefs = []libbpfElfSectionDef{ {"kprobe.session+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_KPROBE_SESSION, _SEC_NONE}, {"uprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_NONE}, {"uretprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_NONE}, + {"uprobe.session+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_SESSION, _SEC_NONE}, {"uprobe.multi.s+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_SLEEPABLE}, {"uretprobe.multi.s+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_SLEEPABLE}, + {"uprobe.session.s+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_SESSION, _SEC_SLEEPABLE}, {"ksyscall+", sys.BPF_PROG_TYPE_KPROBE, 0, _SEC_NONE}, {"kretsyscall+", sys.BPF_PROG_TYPE_KPROBE, 0, _SEC_NONE}, {"usdt+", sys.BPF_PROG_TYPE_KPROBE, 0, _SEC_USDT}, @@ -49,6 +51,8 @@ var elfSectionDefs = []libbpfElfSectionDef{ {"fentry.s+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_TRACE_FENTRY, _SEC_ATTACH_BTF | _SEC_SLEEPABLE}, {"fmod_ret.s+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_MODIFY_RETURN, _SEC_ATTACH_BTF | _SEC_SLEEPABLE}, {"fexit.s+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_TRACE_FEXIT, _SEC_ATTACH_BTF | _SEC_SLEEPABLE}, + {"fsession+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_TRACE_FSESSION, _SEC_ATTACH_BTF}, + {"fsession.s+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_TRACE_FSESSION, _SEC_ATTACH_BTF | _SEC_SLEEPABLE}, {"freplace+", sys.BPF_PROG_TYPE_EXT, 0, _SEC_ATTACH_BTF}, {"lsm+", sys.BPF_PROG_TYPE_LSM, sys.BPF_LSM_MAC, _SEC_ATTACH_BTF}, {"lsm.s+", sys.BPF_PROG_TYPE_LSM, sys.BPF_LSM_MAC, _SEC_ATTACH_BTF | _SEC_SLEEPABLE}, diff --git a/vendor/github.com/cilium/ebpf/features/doc.go b/vendor/github.com/cilium/ebpf/features/doc.go new file mode 100644 index 0000000000..acc57e3b1e --- /dev/null +++ b/vendor/github.com/cilium/ebpf/features/doc.go @@ -0,0 +1,19 @@ +// Package features allows probing for BPF features available to the calling process. +// +// In general, the error return values from feature probes in this package +// all have the following semantics unless otherwise specified: +// +// err == nil: The feature is available. +// errors.Is(err, ebpf.ErrNotSupported): The feature is not available. +// err != nil: Any errors encountered during probe execution, wrapped. +// +// Note that the latter case may include false negatives, and that resource +// creation may succeed despite an error being returned. For example, some +// map and program types cannot reliably be probed and will return an +// inconclusive error. +// +// As a rule, only `nil` and `ebpf.ErrNotSupported` are conclusive. +// +// Probe results are cached by the library and persist throughout any changes +// to the process' environment, like capability changes. +package features diff --git a/vendor/github.com/cilium/ebpf/features/link.go b/vendor/github.com/cilium/ebpf/features/link.go new file mode 100644 index 0000000000..4f440e7bcb --- /dev/null +++ b/vendor/github.com/cilium/ebpf/features/link.go @@ -0,0 +1,157 @@ +package features + +import ( + "errors" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/sys" + "github.com/cilium/ebpf/internal/unix" +) + +// HaveBPFLinkUprobeMulti probes the running kernel if uprobe_multi link is supported. +// +// See the package documentation for the meaning of the error return value. +func HaveBPFLinkUprobeMulti() error { + return haveBPFLinkUprobeMulti() +} + +var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", func() error { + prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Name: "probe_upm_link", + Type: ebpf.Kprobe, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, + AttachType: ebpf.AttachTraceUprobeMulti, + License: "MIT", + }) + if errors.Is(err, unix.E2BIG) { + // Kernel doesn't support AttachType field. + return ebpf.ErrNotSupported + } + if err != nil { + return err + } + defer prog.Close() + + // We try to create uprobe multi link on '/' path which results in + // error with -EBADF in case uprobe multi link is supported. + fd, err := sys.LinkCreateUprobeMulti(&sys.LinkCreateUprobeMultiAttr{ + ProgFd: uint32(prog.FD()), + AttachType: sys.BPF_TRACE_UPROBE_MULTI, + Path: sys.NewStringPointer("/"), + Offsets: sys.SlicePointer([]uint64{0}), + Count: 1, + }) + switch { + case errors.Is(err, unix.EBADF): + return nil + case errors.Is(err, unix.EINVAL): + return ebpf.ErrNotSupported + case err != nil: + return err + } + + // should not happen + fd.Close() + return errors.New("successfully attached uprobe_multi to /, kernel bug?") +}, "6.6") + +// HaveBPFLinkKprobeMulti probes the running kernel if kprobe_multi link is supported. +// +// See the package documentation for the meaning of the error return value. +func HaveBPFLinkKprobeMulti() error { + return haveBPFLinkKprobeMulti() +} + +var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", func() error { + prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Name: "probe_kpm_link", + Type: ebpf.Kprobe, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, + AttachType: ebpf.AttachTraceKprobeMulti, + License: "MIT", + }) + if errors.Is(err, unix.E2BIG) { + // Kernel doesn't support AttachType field. + return ebpf.ErrNotSupported + } + if err != nil { + return err + } + defer prog.Close() + + fd, err := sys.LinkCreateKprobeMulti(&sys.LinkCreateKprobeMultiAttr{ + ProgFd: uint32(prog.FD()), + AttachType: sys.BPF_TRACE_KPROBE_MULTI, + Count: 1, + Syms: sys.NewStringSlicePointer([]string{"vprintk"}), + }) + switch { + case errors.Is(err, unix.EINVAL): + return ebpf.ErrNotSupported + // If CONFIG_FPROBE isn't set. + case errors.Is(err, unix.EOPNOTSUPP): + return ebpf.ErrNotSupported + case err != nil: + return err + } + + fd.Close() + + return nil +}, "5.18") + +// HaveBPFLinkKprobeSession probes the running kernel if kprobe_session link is supported. +// +// See the package documentation for the meaning of the error return value. +func HaveBPFLinkKprobeSession() error { + return haveBPFLinkKprobeSession() +} + +var haveBPFLinkKprobeSession = internal.NewFeatureTest("bpf_link_kprobe_session", func() error { + prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Name: "probe_kps_link", + Type: ebpf.Kprobe, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, + AttachType: ebpf.AttachTraceKprobeSession, + License: "MIT", + }) + if errors.Is(err, unix.E2BIG) { + // Kernel doesn't support AttachType field. + return ebpf.ErrNotSupported + } + if err != nil { + return err + } + defer prog.Close() + + fd, err := sys.LinkCreateKprobeMulti(&sys.LinkCreateKprobeMultiAttr{ + ProgFd: uint32(prog.FD()), + AttachType: sys.BPF_TRACE_KPROBE_SESSION, + Count: 1, + Syms: sys.NewStringSlicePointer([]string{"vprintk"}), + }) + switch { + case errors.Is(err, unix.EINVAL): + return ebpf.ErrNotSupported + // If CONFIG_FPROBE isn't set. + case errors.Is(err, unix.EOPNOTSUPP): + return ebpf.ErrNotSupported + case err != nil: + return err + } + + fd.Close() + + return nil +}, "6.10") diff --git a/vendor/github.com/cilium/ebpf/features/map.go b/vendor/github.com/cilium/ebpf/features/map.go new file mode 100644 index 0000000000..75212552c0 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/features/map.go @@ -0,0 +1,362 @@ +package features + +import ( + "errors" + "fmt" + "os" + "unsafe" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/sys" + "github.com/cilium/ebpf/internal/unix" +) + +// HaveMapType probes the running kernel for the availability of the specified map type. +// +// See the package documentation for the meaning of the error return value. +func HaveMapType(mt ebpf.MapType) error { + return haveMapTypeMatrix.Result(mt) +} + +func probeCgroupStorageMap(mt sys.MapType) error { + // keySize needs to be sizeof(struct{u32 + u64}) = 12 (+ padding = 16) + // by using unsafe.Sizeof(int) we are making sure that this works on 32bit and 64bit archs + return createMap(&sys.MapCreateAttr{ + MapType: mt, + ValueSize: 4, + KeySize: uint32(8 + unsafe.Sizeof(int(0))), + MaxEntries: 0, + }) +} + +func probeStorageMap(mt sys.MapType) error { + // maxEntries needs to be 0 + // BPF_F_NO_PREALLOC needs to be set + // btf* fields need to be set + // see alloc_check for local_storage map types + err := createMap(&sys.MapCreateAttr{ + MapType: mt, + KeySize: 4, + ValueSize: 4, + MaxEntries: 0, + MapFlags: sys.BPF_F_NO_PREALLOC, + BtfKeyTypeId: 1, + BtfValueTypeId: 1, + BtfFd: ^uint32(0), + }) + if errors.Is(err, unix.EBADF) { + // Triggered by BtfFd. + return nil + } + return err +} + +func probeNestedMap(mt sys.MapType) error { + // assign invalid innerMapFd to pass validation check + // will return EBADF + err := probeMap(&sys.MapCreateAttr{ + MapType: mt, + InnerMapFd: ^uint32(0), + }) + if errors.Is(err, unix.EBADF) { + return nil + } + return err +} + +func probeMap(attr *sys.MapCreateAttr) error { + if attr.KeySize == 0 { + attr.KeySize = 4 + } + if attr.ValueSize == 0 { + attr.ValueSize = 4 + } + attr.MaxEntries = 1 + return createMap(attr) +} + +func createMap(attr *sys.MapCreateAttr) error { + fd, err := sys.MapCreate(attr) + if err == nil { + fd.Close() + return nil + } + + switch { + // EINVAL occurs when attempting to create a map with an unknown type. + // E2BIG occurs when MapCreateAttr contains non-zero bytes past the end + // of the struct known by the running kernel, meaning the kernel is too old + // to support the given map type. + case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG): + return ebpf.ErrNotSupported + } + + return err +} + +var haveMapTypeMatrix = internal.FeatureMatrix[ebpf.MapType]{ + ebpf.Hash: {Version: "3.19"}, + ebpf.Array: {Version: "3.19"}, + ebpf.ProgramArray: {Version: "4.2"}, + ebpf.PerfEventArray: {Version: "4.3"}, + ebpf.PerCPUHash: {Version: "4.6"}, + ebpf.PerCPUArray: {Version: "4.6"}, + ebpf.StackTrace: { + Version: "4.6", + Fn: func() error { + return probeMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_STACK_TRACE, + ValueSize: 8, // sizeof(uint64) + }) + }, + }, + ebpf.CGroupArray: {Version: "4.8"}, + ebpf.LRUHash: {Version: "4.10"}, + ebpf.LRUCPUHash: {Version: "4.10"}, + ebpf.LPMTrie: { + Version: "4.11", + Fn: func() error { + // keySize and valueSize need to be sizeof(struct{u32 + u8}) + 1 + padding = 8 + // BPF_F_NO_PREALLOC needs to be set + return probeMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_LPM_TRIE, + KeySize: 8, + ValueSize: 8, + MapFlags: sys.BPF_F_NO_PREALLOC, + }) + }, + }, + ebpf.ArrayOfMaps: { + Version: "4.12", + Fn: func() error { return probeNestedMap(sys.BPF_MAP_TYPE_ARRAY_OF_MAPS) }, + }, + ebpf.HashOfMaps: { + Version: "4.12", + Fn: func() error { return probeNestedMap(sys.BPF_MAP_TYPE_HASH_OF_MAPS) }, + }, + ebpf.DevMap: {Version: "4.14"}, + ebpf.SockMap: {Version: "4.14"}, + ebpf.CPUMap: {Version: "4.15"}, + ebpf.XSKMap: {Version: "4.18"}, + ebpf.SockHash: {Version: "4.18"}, + ebpf.CGroupStorage: { + Version: "4.19", + Fn: func() error { return probeCgroupStorageMap(sys.BPF_MAP_TYPE_CGROUP_STORAGE) }, + }, + ebpf.ReusePortSockArray: {Version: "4.19"}, + ebpf.PerCPUCGroupStorage: { + Version: "4.20", + Fn: func() error { return probeCgroupStorageMap(sys.BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE) }, + }, + ebpf.Queue: { + Version: "4.20", + Fn: func() error { + return createMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_QUEUE, + KeySize: 0, + ValueSize: 4, + MaxEntries: 1, + }) + }, + }, + ebpf.Stack: { + Version: "4.20", + Fn: func() error { + return createMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_STACK, + KeySize: 0, + ValueSize: 4, + MaxEntries: 1, + }) + }, + }, + ebpf.SkStorage: { + Version: "5.2", + Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_SK_STORAGE) }, + }, + ebpf.DevMapHash: {Version: "5.4"}, + ebpf.StructOpsMap: { + Version: "5.6", + Fn: func() error { + // StructOps requires setting a vmlinux type id, but id 1 will always + // resolve to some type of integer. This will cause ENOTSUPP. + err := probeMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_STRUCT_OPS, + BtfVmlinuxValueTypeId: 1, + }) + if errors.Is(err, sys.ENOTSUPP) { + // ENOTSUPP means the map type is at least known to the kernel. + return nil + } + return err + }, + }, + ebpf.RingBuf: { + Version: "5.8", + Fn: func() error { + // keySize and valueSize need to be 0 + // maxEntries needs to be power of 2 and PAGE_ALIGNED + return createMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_RINGBUF, + KeySize: 0, + ValueSize: 0, + MaxEntries: uint32(os.Getpagesize()), + }) + }, + }, + ebpf.InodeStorage: { + Version: "5.10", + Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_INODE_STORAGE) }, + }, + ebpf.TaskStorage: { + Version: "5.11", + Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_TASK_STORAGE) }, + }, + ebpf.BloomFilter: { + Version: "5.16", + Fn: func() error { + return createMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_BLOOM_FILTER, + KeySize: 0, + ValueSize: 4, + MaxEntries: 1, + }) + }, + }, + ebpf.UserRingbuf: { + Version: "6.1", + Fn: func() error { + // keySize and valueSize need to be 0 + // maxEntries needs to be power of 2 and PAGE_ALIGNED + return createMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_USER_RINGBUF, + KeySize: 0, + ValueSize: 0, + MaxEntries: uint32(os.Getpagesize()), + }) + }, + }, + ebpf.CgroupStorage: { + Version: "6.2", + Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_CGRP_STORAGE) }, + }, + ebpf.Arena: { + Version: "6.9", + Fn: func() error { + return createMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_ARENA, + KeySize: 0, + ValueSize: 0, + MaxEntries: 1, // one page + MapExtra: 0, // can mmap() at any address + MapFlags: sys.BPF_F_MMAPABLE, + }) + }, + }, +} + +func init() { + for mt, ft := range haveMapTypeMatrix { + ft.Name = mt.String() + if ft.Fn == nil { + // Avoid referring to the loop variable in the closure. + mt := sys.MapType(mt) + ft.Fn = func() error { return probeMap(&sys.MapCreateAttr{MapType: mt}) } + } + } +} + +// MapFlags document which flags may be feature probed. +type MapFlags uint32 + +// Flags which may be feature probed. +const ( + BPF_F_NO_PREALLOC = sys.BPF_F_NO_PREALLOC + BPF_F_RDONLY_PROG = sys.BPF_F_RDONLY_PROG + BPF_F_WRONLY_PROG = sys.BPF_F_WRONLY_PROG + BPF_F_MMAPABLE = sys.BPF_F_MMAPABLE + BPF_F_INNER_MAP = sys.BPF_F_INNER_MAP +) + +// HaveMapFlag probes the running kernel for the availability of the specified map flag. +// +// Returns an error if flag is not one of the flags declared in this package. +// See the package documentation for the meaning of the error return value. +func HaveMapFlag(flag MapFlags) (err error) { + return haveMapFlagsMatrix.Result(flag) +} + +func probeMapFlag(attr *sys.MapCreateAttr) error { + // For now, we do not check if the map type is supported because we only support + // probing for flags defined on arrays and hashes that are always supported. + // In the future, if we allow probing on flags defined on newer types, checking for map type + // support will be required. + if attr.MapType == sys.BPF_MAP_TYPE_UNSPEC { + attr.MapType = sys.BPF_MAP_TYPE_ARRAY + } + + attr.KeySize = 4 + attr.ValueSize = 4 + attr.MaxEntries = 1 + + fd, err := sys.MapCreate(attr) + if err == nil { + fd.Close() + } else if errors.Is(err, unix.EINVAL) { + // EINVAL occurs when attempting to create a map with an unknown type or an unknown flag. + err = ebpf.ErrNotSupported + } + + return err +} + +var haveMapFlagsMatrix = internal.FeatureMatrix[MapFlags]{ + BPF_F_NO_PREALLOC: { + Version: "4.6", + Fn: func() error { + return probeMapFlag(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_HASH, + MapFlags: BPF_F_NO_PREALLOC, + }) + }, + }, + BPF_F_RDONLY_PROG: { + Version: "5.2", + Fn: func() error { + return probeMapFlag(&sys.MapCreateAttr{ + MapFlags: BPF_F_RDONLY_PROG, + }) + }, + }, + BPF_F_WRONLY_PROG: { + Version: "5.2", + Fn: func() error { + return probeMapFlag(&sys.MapCreateAttr{ + MapFlags: BPF_F_WRONLY_PROG, + }) + }, + }, + BPF_F_MMAPABLE: { + Version: "5.5", + Fn: func() error { + return probeMapFlag(&sys.MapCreateAttr{ + MapFlags: BPF_F_MMAPABLE, + }) + }, + }, + BPF_F_INNER_MAP: { + Version: "5.10", + Fn: func() error { + return probeMapFlag(&sys.MapCreateAttr{ + MapFlags: BPF_F_INNER_MAP, + }) + }, + }, +} + +func init() { + for mf, ft := range haveMapFlagsMatrix { + ft.Name = fmt.Sprint(mf) + } +} diff --git a/vendor/github.com/cilium/ebpf/features/misc.go b/vendor/github.com/cilium/ebpf/features/misc.go new file mode 100644 index 0000000000..c039020a95 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/features/misc.go @@ -0,0 +1,135 @@ +package features + +import ( + "errors" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/sys" +) + +// HaveLargeInstructions probes the running kernel if more than 4096 instructions +// per program are supported. +// +// Upstream commit c04c0d2b968a ("bpf: increase complexity limit and maximum program size"). +// +// See the package documentation for the meaning of the error return value. +func HaveLargeInstructions() error { + return haveLargeInstructions() +} + +var haveLargeInstructions = internal.NewFeatureTest(">4096 instructions", func() error { + const maxInsns = 4096 + + insns := make(asm.Instructions, maxInsns, maxInsns+1) + for i := range insns { + insns[i] = asm.Mov.Imm(asm.R0, 1) + } + insns = append(insns, asm.Return()) + + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.SocketFilter, + Instructions: insns, + }) +}, "5.2") + +// HaveBoundedLoops probes the running kernel if bounded loops are supported. +// +// Upstream commit 2589726d12a1 ("bpf: introduce bounded loops"). +// +// See the package documentation for the meaning of the error return value. +func HaveBoundedLoops() error { + return haveBoundedLoops() +} + +var haveBoundedLoops = internal.NewFeatureTest("bounded loops", func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.SocketFilter, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 10), + asm.Sub.Imm(asm.R0, 1).WithSymbol("loop"), + asm.JNE.Imm(asm.R0, 0, "loop"), + asm.Return(), + }, + }) +}, "5.3") + +// HaveV2ISA probes the running kernel if instructions of the v2 ISA are supported. +// +// Upstream commit 92b31a9af73b ("bpf: add BPF_J{LT,LE,SLT,SLE} instructions"). +// +// See the package documentation for the meaning of the error return value. +func HaveV2ISA() error { + return haveV2ISA() +} + +var haveV2ISA = internal.NewFeatureTest("v2 ISA", func() error { + err := probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.SocketFilter, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.JLT.Imm(asm.R0, 0, "exit"), + asm.Mov.Imm(asm.R0, 1), + asm.Return().WithSymbol("exit"), + }, + }) + // This sometimes bubbles up from the JIT on aarch64. + if errors.Is(err, sys.ENOTSUPP) { + return ebpf.ErrNotSupported + } + return err +}, "4.14") + +// HaveV3ISA probes the running kernel if instructions of the v3 ISA are supported. +// +// Upstream commit 092ed0968bb6 ("bpf: verifier support JMP32"). +// +// See the package documentation for the meaning of the error return value. +func HaveV3ISA() error { + return haveV3ISA() +} + +var haveV3ISA = internal.NewFeatureTest("v3 ISA", func() error { + err := probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.SocketFilter, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.JLT.Imm32(asm.R0, 0, "exit"), + asm.Mov.Imm(asm.R0, 1), + asm.Return().WithSymbol("exit"), + }, + }) + // This sometimes bubbles up from the JIT on aarch64. + if errors.Is(err, sys.ENOTSUPP) { + return ebpf.ErrNotSupported + } + return err +}, "5.1") + +// HaveV4ISA probes the running kernel if instructions of the v4 ISA are supported. +// +// Upstream commit 1f9a1ea821ff ("bpf: Support new sign-extension load insns"). +// +// See the package documentation for the meaning of the error return value. +func HaveV4ISA() error { + return haveV4ISA() +} + +var haveV4ISA = internal.NewFeatureTest("v4 ISA", func() error { + err := probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.SocketFilter, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.JEq.Imm(asm.R0, 1, "error"), + asm.LongJump("exit"), + asm.Mov.Imm(asm.R0, 1).WithSymbol("error"), + asm.Return().WithSymbol("exit"), + }, + }) + // This sometimes bubbles up from the JIT on aarch64. + if errors.Is(err, sys.ENOTSUPP) { + return ebpf.ErrNotSupported + } + return err +}, "6.6") diff --git a/vendor/github.com/cilium/ebpf/features/prog.go b/vendor/github.com/cilium/ebpf/features/prog.go new file mode 100644 index 0000000000..6441d5931d --- /dev/null +++ b/vendor/github.com/cilium/ebpf/features/prog.go @@ -0,0 +1,332 @@ +package features + +import ( + "errors" + "fmt" + "slices" + "strings" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" + "github.com/cilium/ebpf/btf" + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/sys" + "github.com/cilium/ebpf/internal/unix" +) + +// HaveProgramType probes the running kernel for the availability of the specified program type. +// +// See the package documentation for the meaning of the error return value. +func HaveProgramType(pt ebpf.ProgramType) (err error) { + return haveProgramTypeMatrix.Result(pt) +} + +func probeProgram(spec *ebpf.ProgramSpec) error { + if spec.Instructions == nil { + spec.Instructions = asm.Instructions{ + asm.LoadImm(asm.R0, 0, asm.DWord), + asm.Return(), + } + } + prog, err := ebpf.NewProgramWithOptions(spec, ebpf.ProgramOptions{ + LogDisabled: true, + }) + if err == nil { + prog.Close() + } + + switch { + // EINVAL occurs when attempting to create a program with an unknown type. + // E2BIG occurs when ProgLoadAttr contains non-zero bytes past the end + // of the struct known by the running kernel, meaning the kernel is too old + // to support the given prog type. + case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG): + err = ebpf.ErrNotSupported + } + + return err +} + +var haveProgramTypeMatrix = internal.FeatureMatrix[ebpf.ProgramType]{ + ebpf.SocketFilter: {Version: "3.19"}, + ebpf.Kprobe: {Version: "4.1"}, + ebpf.SchedCLS: {Version: "4.1"}, + ebpf.SchedACT: {Version: "4.1"}, + ebpf.TracePoint: {Version: "4.7"}, + ebpf.XDP: {Version: "4.8"}, + ebpf.PerfEvent: {Version: "4.9"}, + ebpf.CGroupSKB: {Version: "4.10"}, + ebpf.CGroupSock: {Version: "4.10"}, + ebpf.LWTIn: {Version: "4.10"}, + ebpf.LWTOut: {Version: "4.10"}, + ebpf.LWTXmit: {Version: "4.10"}, + ebpf.SockOps: {Version: "4.13"}, + ebpf.SkSKB: {Version: "4.14"}, + ebpf.CGroupDevice: {Version: "4.15"}, + ebpf.SkMsg: {Version: "4.17"}, + ebpf.RawTracepoint: {Version: "4.17"}, + ebpf.CGroupSockAddr: { + Version: "4.17", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.CGroupSockAddr, + AttachType: ebpf.AttachCGroupInet4Connect, + }) + }, + }, + ebpf.LWTSeg6Local: {Version: "4.18"}, + ebpf.LircMode2: {Version: "4.18"}, + ebpf.SkReuseport: {Version: "4.19"}, + ebpf.FlowDissector: {Version: "4.20"}, + ebpf.CGroupSysctl: {Version: "5.2"}, + ebpf.RawTracepointWritable: {Version: "5.2"}, + ebpf.CGroupSockopt: { + Version: "5.3", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.CGroupSockopt, + AttachType: ebpf.AttachCGroupGetsockopt, + }) + }, + }, + ebpf.Tracing: { + Version: "5.5", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.Tracing, + AttachType: ebpf.AttachTraceFEntry, + AttachTo: "bpf_init", + }) + }, + }, + ebpf.StructOps: { + Version: "5.6", + Fn: func() error { + err := probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.StructOps, + License: "GPL", + }) + if errors.Is(err, sys.ENOTSUPP) { + // ENOTSUPP means the program type is at least known to the kernel. + return nil + } + return err + }, + }, + ebpf.Extension: { + Version: "5.6", + Fn: func() error { + // create btf.Func to add to first ins of target and extension so both progs are btf powered + btfFn := btf.Func{ + Name: "a", + Type: &btf.FuncProto{ + Return: &btf.Int{}, + Params: []btf.FuncParam{ + {Name: "ctx", Type: &btf.Pointer{Target: &btf.Struct{Name: "xdp_md"}}}, + }, + }, + Linkage: btf.GlobalFunc, + } + insns := asm.Instructions{ + btf.WithFuncMetadata(asm.Mov.Imm(asm.R0, 0), &btfFn), + asm.Return(), + } + + // create target prog + prog, err := ebpf.NewProgramWithOptions( + &ebpf.ProgramSpec{ + Type: ebpf.XDP, + Instructions: insns, + }, + ebpf.ProgramOptions{ + LogDisabled: true, + }, + ) + if err != nil { + return err + } + defer prog.Close() + + // probe for Extension prog with target + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.Extension, + Instructions: insns, + AttachTarget: prog, + AttachTo: btfFn.Name, + }) + }, + }, + ebpf.LSM: { + Version: "5.7", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.LSM, + AttachType: ebpf.AttachLSMMac, + AttachTo: "file_mprotect", + License: "GPL", + }) + }, + }, + ebpf.SkLookup: { + Version: "5.9", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.SkLookup, + AttachType: ebpf.AttachSkLookup, + }) + }, + }, + ebpf.Syscall: { + Version: "5.14", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.Syscall, + Flags: sys.BPF_F_SLEEPABLE, + }) + }, + }, + ebpf.Netfilter: { + Version: "6.4", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.Netfilter, + AttachType: ebpf.AttachNetfilter, + }) + }, + }, +} + +func init() { + for key, ft := range haveProgramTypeMatrix { + ft.Name = key.String() + if ft.Fn == nil { + key := key // avoid the dreaded loop variable problem + ft.Fn = func() error { return probeProgram(&ebpf.ProgramSpec{Type: key}) } + } + } +} + +type helperKey struct { + typ ebpf.ProgramType + helper asm.BuiltinFunc +} + +var helperCache = internal.NewFeatureCache(func(key helperKey) *internal.FeatureTest { + return &internal.FeatureTest{ + Name: fmt.Sprintf("%s for program type %s", key.helper, key.typ), + Fn: func() error { + return haveProgramHelper(key.typ, key.helper) + }, + } +}) + +// HaveProgramHelper probes the running kernel for the availability of the specified helper +// function to a specified program type. +// Return values have the following semantics: +// +// err == nil: The feature is available. +// errors.Is(err, ebpf.ErrNotSupported): The feature is not available. +// err != nil: Any errors encountered during probe execution, wrapped. +// +// Note that the latter case may include false negatives, and that program creation may +// succeed despite an error being returned. +// Only `nil` and `ebpf.ErrNotSupported` are conclusive. +// +// Probe results are cached and persist throughout any process capability changes. +func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error { + return helperCache.Result(helperKey{pt, helper}) +} + +func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error { + if ok := helperProbeNotImplemented(pt); ok { + return fmt.Errorf("no feature probe for %v/%v", pt, helper) + } + + if err := HaveProgramType(pt); err != nil { + return err + } + + spec := &ebpf.ProgramSpec{ + Type: pt, + Instructions: asm.Instructions{ + helper.Call(), + asm.LoadImm(asm.R0, 0, asm.DWord), + asm.Return(), + }, + License: "GPL", + } + + switch pt { + case ebpf.CGroupSockAddr: + spec.AttachType = ebpf.AttachCGroupInet4Connect + case ebpf.CGroupSockopt: + spec.AttachType = ebpf.AttachCGroupGetsockopt + case ebpf.SkLookup: + spec.AttachType = ebpf.AttachSkLookup + case ebpf.Syscall: + spec.Flags = sys.BPF_F_SLEEPABLE + case ebpf.Netfilter: + spec.AttachType = ebpf.AttachNetfilter + } + + prog, err := ebpf.NewProgramWithOptions(spec, ebpf.ProgramOptions{ + LogLevel: 1, + }) + if err == nil { + prog.Close() + } + + var verr *ebpf.VerifierError + if !errors.As(err, &verr) { + return err + } + + helperTag := fmt.Sprintf("#%d", helper) + + switch { + // EACCES occurs when attempting to create a program probe with a helper + // while the register args when calling this helper aren't set up properly. + // We interpret this as the helper being available, because the verifier + // returns EINVAL if the helper is not supported by the running kernel. + case errors.Is(err, unix.EACCES): + err = nil + + // EINVAL occurs when attempting to create a program with an unknown helper. + case errors.Is(err, unix.EINVAL): + // https://github.com/torvalds/linux/blob/09a0fa92e5b45e99cf435b2fbf5ebcf889cf8780/kernel/bpf/verifier.c#L10663 + if logContainsAll(verr.Log, "invalid func", helperTag) { + return ebpf.ErrNotSupported + } + + // https://github.com/torvalds/linux/blob/09a0fa92e5b45e99cf435b2fbf5ebcf889cf8780/kernel/bpf/verifier.c#L10668 + wrongProgramType := logContainsAll(verr.Log, "program of this type cannot use helper", helperTag) + // https://github.com/torvalds/linux/blob/59b418c7063d30e0a3e1f592d47df096db83185c/kernel/bpf/verifier.c#L10204 + // 4.9 doesn't include # in verifier output. + wrongProgramType = wrongProgramType || logContainsAll(verr.Log, "unknown func") + if wrongProgramType { + return fmt.Errorf("program of this type cannot use helper: %w", ebpf.ErrNotSupported) + } + } + + return err +} + +func logContainsAll(log []string, needles ...string) bool { + first := max(len(log)-5, 0) // Check last 5 lines. + return slices.ContainsFunc(log[first:], func(line string) bool { + for _, needle := range needles { + if !strings.Contains(line, needle) { + return false + } + } + return true + }) +} + +func helperProbeNotImplemented(pt ebpf.ProgramType) bool { + switch pt { + case ebpf.Extension, ebpf.LSM, ebpf.StructOps, ebpf.Tracing: + return true + } + return false +} diff --git a/vendor/github.com/cilium/ebpf/features/version.go b/vendor/github.com/cilium/ebpf/features/version.go new file mode 100644 index 0000000000..d54d3ea212 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/features/version.go @@ -0,0 +1,18 @@ +package features + +import "github.com/cilium/ebpf/internal/linux" + +// LinuxVersionCode returns the version of the currently running kernel +// as defined in the LINUX_VERSION_CODE compile-time macro. It is represented +// in the format described by the KERNEL_VERSION macro from linux/version.h. +// +// Do not use the version to make assumptions about the presence of certain +// kernel features, always prefer feature probes in this package. Some +// distributions backport or disable eBPF features. +func LinuxVersionCode() (uint32, error) { + v, err := linux.KernelVersion() + if err != nil { + return 0, err + } + return v.Kernel(), nil +} diff --git a/vendor/github.com/cilium/ebpf/info.go b/vendor/github.com/cilium/ebpf/info.go index b15202a0f5..b2d08de4be 100644 --- a/vendor/github.com/cilium/ebpf/info.go +++ b/vendor/github.com/cilium/ebpf/info.go @@ -140,7 +140,7 @@ func newMapInfoFromFd(fd *sys.FD) (*MapInfo, error) { // /proc/self/fdinfo. It only writes data into fields that have a zero value. func readMapInfoFromProc(fd *sys.FD, mi *MapInfo) error { var mapType uint32 - err := scanFdInfo(fd, map[string]interface{}{ + err := scanFdInfo(fd, map[string]any{ "map_type": &mapType, "map_id": &mi.id, "key_size": &mi.KeySize, @@ -301,8 +301,11 @@ type ProgramInfo struct { btf btf.ID loadTime time.Duration + restricted bool + maps []MapID insns []byte + numInsns uint32 jitedSize uint32 verifiedInstructions uint32 @@ -370,6 +373,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { jitedSize: info.JitedProgLen, loadTime: time.Duration(info.LoadTime), verifiedInstructions: info.VerifiedInsns, + numInsns: info.XlatedProgLen, } // Supplement OBJ_INFO with data from /proc/self/fdinfo. It contains fields @@ -414,9 +418,20 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { if info.XlatedProgLen > 0 { pi.insns = make([]byte, info.XlatedProgLen) - info2.XlatedProgLen = info.XlatedProgLen - info2.XlatedProgInsns = sys.SlicePointer(pi.insns) - makeSecondCall = true + var info3 sys.ProgInfo + info3.XlatedProgLen = info.XlatedProgLen + info3.XlatedProgInsns = sys.SlicePointer(pi.insns) + + // When kernel.kptr_restrict and net.core.bpf_jit_harden are both set, it causes the + // syscall to abort when trying to readback xlated instructions, skipping other info + // as well. So request xlated instructions separately. + if err := sys.ObjInfo(fd, &info3); err != nil { + return nil, err + } + if info3.XlatedProgInsns.IsNil() { + pi.restricted = true + pi.insns = nil + } } if info.NrLineInfo > 0 { @@ -475,14 +490,56 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { if err := sys.ObjInfo(fd, &info2); err != nil { return nil, err } + if info.JitedProgLen > 0 && info2.JitedProgInsns.IsNil() { + // JIT information is not available due to kernel.kptr_restrict + pi.jitedInfo.lineInfos = nil + pi.jitedInfo.ksyms = nil + pi.jitedInfo.insns = nil + pi.jitedInfo.funcLens = nil + } + } + + if len(pi.Name) == len(info.Name)-1 { // Possibly truncated, check BTF info for full name + name, err := readNameFromFunc(&pi) + if err == nil { + pi.Name = name + } // If an error occurs, keep the truncated name, which is better than none } return &pi, nil } +func readNameFromFunc(pi *ProgramInfo) (string, error) { + if pi.numFuncInfos == 0 { + return "", errors.New("no function info") + } + + spec, err := pi.btfSpec() + if err != nil { + return "", err + } + + funcInfos, err := btf.LoadFuncInfos( + bytes.NewReader(pi.funcInfos), + internal.NativeEndian, + pi.numFuncInfos, + spec, + ) + if err != nil { + return "", err + } + + for _, funcInfo := range funcInfos { + if funcInfo.Offset == 0 { // Information about the whole program + return funcInfo.Func.Name, nil + } + } + return "", errors.New("no function info about program") +} + func readProgramInfoFromProc(fd *sys.FD, pi *ProgramInfo) error { var progType uint32 - err := scanFdInfo(fd, map[string]interface{}{ + err := scanFdInfo(fd, map[string]any{ "prog_type": &progType, "prog_tag": &pi.Tag, "memlock": &pi.memlock, @@ -556,10 +613,17 @@ func (pi *ProgramInfo) btfSpec() (*btf.Spec, error) { return spec, nil } +// ErrRestrictedKernel is returned when kernel address information is restricted +// by kernel.kptr_restrict and/or net.core.bpf_jit_harden sysctls. +var ErrRestrictedKernel = internal.ErrRestrictedKernel + // LineInfos returns the BTF line information of the program. // // Available from 5.0. // +// Returns an error wrapping [ErrRestrictedKernel] if line infos are restricted +// by sysctls. +// // Requires CAP_SYS_ADMIN or equivalent for reading BTF information. Returns // ErrNotSupported if the program was created without BTF or if the kernel // doesn't support the field. @@ -599,6 +663,9 @@ func (pi *ProgramInfo) LineInfos() (btf.LineOffsets, error) { // this metadata requires CAP_SYS_ADMIN or equivalent. If capability is // unavailable, the instructions will be returned without metadata. // +// Returns an error wrapping [ErrRestrictedKernel] if instructions are +// restricted by sysctls. +// // Available from 4.13. Requires CAP_BPF or equivalent for plain instructions. // Requires CAP_SYS_ADMIN for instructions with metadata. func (pi *ProgramInfo) Instructions() (asm.Instructions, error) { @@ -606,6 +673,10 @@ func (pi *ProgramInfo) Instructions() (asm.Instructions, error) { return nil, fmt.Errorf("read instructions: %w", internal.ErrNotSupportedOnOS) } + if pi.restricted { + return nil, fmt.Errorf("instructions: %w", ErrRestrictedKernel) + } + // If the calling process is not BPF-capable or if the kernel doesn't // support getting xlated instructions, the field will be zero. if len(pi.insns) == 0 { @@ -637,27 +708,20 @@ func (pi *ProgramInfo) Instructions() (asm.Instructions, error) { return nil, fmt.Errorf("unable to get BTF spec: %w", err) } - lineInfos, err := btf.LoadLineInfos( - bytes.NewReader(pi.lineInfos), - internal.NativeEndian, - pi.numLineInfos, - spec, - ) + lineInfos, err := btf.LoadLineInfos(bytes.NewReader(pi.lineInfos), internal.NativeEndian, pi.numLineInfos, spec) if err != nil { return nil, fmt.Errorf("parse line info: %w", err) } - funcInfos, err := btf.LoadFuncInfos( - bytes.NewReader(pi.funcInfos), - internal.NativeEndian, - pi.numFuncInfos, - spec, - ) + funcInfos, err := btf.LoadFuncInfos(bytes.NewReader(pi.funcInfos), internal.NativeEndian, pi.numFuncInfos, spec) if err != nil { return nil, fmt.Errorf("parse func info: %w", err) } - btf.AssignMetadataToInstructions(insns, funcInfos, lineInfos, btf.CORERelocationInfos{}) + iter := insns.Iterate() + for iter.Next() { + assignMetadata(iter.Ins, iter.Offset, &funcInfos, &lineInfos, nil) + } } } @@ -671,8 +735,12 @@ func (pi *ProgramInfo) Instructions() (asm.Instructions, error) { return insns, nil } -// JitedSize returns the size of the program's JIT-compiled machine code in bytes, which is the -// actual code executed on the host's CPU. This field requires the BPF JIT compiler to be enabled. +// JitedSize returns the size of the program's JIT-compiled machine code in +// bytes, which is the actual code executed on the host's CPU. This field +// requires the BPF JIT compiler to be enabled. +// +// Returns an error wrapping [ErrRestrictedKernel] if jited program size is +// restricted by sysctls. // // Available from 4.13. Reading this metadata requires CAP_BPF or equivalent. func (pi *ProgramInfo) JitedSize() (uint32, error) { @@ -682,16 +750,15 @@ func (pi *ProgramInfo) JitedSize() (uint32, error) { return pi.jitedSize, nil } -// TranslatedSize returns the size of the program's translated instructions in bytes, after it has -// been verified and rewritten by the kernel. +// TranslatedSize returns the size of the program's translated instructions in +// bytes, after it has been verified and rewritten by the kernel. // // Available from 4.13. Reading this metadata requires CAP_BPF or equivalent. func (pi *ProgramInfo) TranslatedSize() (int, error) { - insns := len(pi.insns) - if insns == 0 { + if pi.numInsns == 0 { return 0, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported) } - return insns, nil + return int(pi.numInsns), nil } // MapIDs returns the maps related to the program. @@ -782,6 +849,9 @@ func (pi *ProgramInfo) JitedFuncLens() ([]uint32, bool) { // // Available from 5.0. // +// Returns an error wrapping [ErrRestrictedKernel] if function information is +// restricted by sysctls. +// // Requires CAP_SYS_ADMIN or equivalent for reading BTF information. Returns // ErrNotSupported if the program was created without BTF or if the kernel // doesn't support the field. @@ -812,7 +882,7 @@ func (pi *ProgramInfo) Memlock() (uint64, bool) { return pi.memlock, pi.memlock > 0 } -func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error { +func scanFdInfo(fd *sys.FD, fields map[string]any) error { if platform.IsWindows { return fmt.Errorf("read fdinfo: %w", internal.ErrNotSupportedOnOS) } @@ -829,7 +899,7 @@ func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error { return nil } -func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error { +func scanFdInfoReader(r io.Reader, fields map[string]any) error { var ( scanner = bufio.NewScanner(r) scanned int diff --git a/vendor/github.com/cilium/ebpf/internal/efw/map.go b/vendor/github.com/cilium/ebpf/internal/efw/map.go new file mode 100644 index 0000000000..82f510fef0 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/efw/map.go @@ -0,0 +1,109 @@ +//go:build windows + +package efw + +import ( + "runtime" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +/* +ebpf_ring_buffer_map_map_buffer( + + fd_t map_fd, + _Outptr_result_maybenull_ void** consumer, + _Outptr_result_maybenull_ const void** producer, + _Outptr_result_buffer_maybenull_(*data_size) const uint8_t** data, + _Out_ size_t* data_size) EBPF_NO_EXCEPT; +*/ +var ebpfRingBufferMapMapBufferProc = newProc("ebpf_ring_buffer_map_map_buffer") + +func EbpfRingBufferMapMapBuffer(mapFd int) (consumer, producer, data *uint8, dataLen Size, _ error) { + addr, err := ebpfRingBufferMapMapBufferProc.Find() + if err != nil { + return nil, nil, nil, 0, err + } + + err = errorResult(syscall.SyscallN(addr, + uintptr(mapFd), + uintptr(unsafe.Pointer(&consumer)), + uintptr(unsafe.Pointer(&producer)), + uintptr(unsafe.Pointer(&data)), + uintptr(unsafe.Pointer(&dataLen)), + )) + if err != nil { + return nil, nil, nil, 0, err + } + + return consumer, producer, data, dataLen, nil +} + +/* +ebpf_ring_buffer_map_unmap_buffer( + + fd_t map_fd, _In_ void* consumer, _In_ const void* producer, _In_ const void* data) EBPF_NO_EXCEPT; +*/ +var ebpfRingBufferMapUnmapBufferProc = newProc("ebpf_ring_buffer_map_unmap_buffer") + +func EbpfRingBufferMapUnmapBuffer(mapFd int, consumer, producer, data *uint8) error { + addr, err := ebpfRingBufferMapUnmapBufferProc.Find() + if err != nil { + return err + } + + return errorResult(syscall.SyscallN(addr, + uintptr(mapFd), + uintptr(unsafe.Pointer(consumer)), + uintptr(unsafe.Pointer(producer)), + uintptr(unsafe.Pointer(data)), + )) +} + +/* +ebpf_result_t ebpf_map_set_wait_handle( + + fd_t map_fd, + uint64_t index, + ebpf_handle_t handle) +*/ +var ebpfMapSetWaitHandleProc = newProc("ebpf_map_set_wait_handle") + +func EbpfMapSetWaitHandle(mapFd int, index uint64, handle windows.Handle) error { + addr, err := ebpfMapSetWaitHandleProc.Find() + if err != nil { + return err + } + + return errorResult(syscall.SyscallN(addr, + uintptr(mapFd), + uintptr(index), + uintptr(handle), + )) +} + +/* +ebpf_result_t ebpf_ring_buffer_map_write( + + fd_t ring_buffer_map_fd, + const void* data, + size_t data_length) +*/ +var ebpfRingBufferMapWriteProc = newProc("ebpf_ring_buffer_map_write") + +func EbpfRingBufferMapWrite(ringBufferMapFd int, data []byte) error { + addr, err := ebpfRingBufferMapWriteProc.Find() + if err != nil { + return err + } + + err = errorResult(syscall.SyscallN(addr, + uintptr(ringBufferMapFd), + uintptr(unsafe.Pointer(&data[0])), + uintptr(len(data)), + )) + runtime.KeepAlive(data) + return err +} diff --git a/vendor/github.com/cilium/ebpf/internal/efw/result.go b/vendor/github.com/cilium/ebpf/internal/efw/result.go index 3275941d3e..4c68da9315 100644 --- a/vendor/github.com/cilium/ebpf/internal/efw/result.go +++ b/vendor/github.com/cilium/ebpf/internal/efw/result.go @@ -5,7 +5,7 @@ package efw // See https://github.com/microsoft/ebpf-for-windows/blob/main/include/ebpf_result.h type Result int32 -//go:generate go run golang.org/x/tools/cmd/stringer@latest -tags windows -output result_string_windows.go -type=Result +//go:generate go tool stringer -tags windows -output result_string_windows.go -type=Result const ( EBPF_SUCCESS Result = iota diff --git a/vendor/github.com/cilium/ebpf/internal/feature.go b/vendor/github.com/cilium/ebpf/internal/feature.go index 82b8d93956..e27064c23e 100644 --- a/vendor/github.com/cilium/ebpf/internal/feature.go +++ b/vendor/github.com/cilium/ebpf/internal/feature.go @@ -16,6 +16,10 @@ var ErrNotSupported = errors.New("not supported") // operating system. var ErrNotSupportedOnOS = fmt.Errorf("%w on %s", ErrNotSupported, runtime.GOOS) +// ErrRestrictedKernel is returned when kernel address information is restricted +// by kernel.kptr_restrict and/or net.core.bpf_jit_harden sysctls. +var ErrRestrictedKernel = errors.New("restricted by kernel.kptr_restrict and/or net.core.bpf_jit_harden sysctls") + // UnsupportedFeatureError is returned by FeatureTest() functions. type UnsupportedFeatureError struct { // The minimum version required for this feature. diff --git a/vendor/github.com/cilium/ebpf/internal/kallsyms/kallsyms.go b/vendor/github.com/cilium/ebpf/internal/kallsyms/kallsyms.go index 9154a8a794..efc64a5030 100644 --- a/vendor/github.com/cilium/ebpf/internal/kallsyms/kallsyms.go +++ b/vendor/github.com/cilium/ebpf/internal/kallsyms/kallsyms.go @@ -16,151 +16,6 @@ import ( var errAmbiguousKsym = errors.New("multiple kernel symbols with the same name") var symAddrs cache[string, uint64] -var symModules cache[string, string] - -// Module returns the kernel module providing the given symbol in the kernel, if -// any. Returns an empty string and no error if the symbol is not present in the -// kernel. Only function symbols are considered. Returns an error if multiple -// symbols with the same name were found. -// -// Consider [AssignModules] if you need to resolve multiple symbols, as it will -// only perform one iteration over /proc/kallsyms. -func Module(name string) (string, error) { - if name == "" { - return "", nil - } - - if mod, ok := symModules.Load(name); ok { - return mod, nil - } - - request := map[string]string{name: ""} - if err := AssignModules(request); err != nil { - return "", err - } - - return request[name], nil -} - -// AssignModules looks up the kernel module providing each given symbol, if any, -// and assigns them to their corresponding values in the symbols map. Only -// function symbols are considered. Results of all lookups are cached, -// successful or otherwise. -// -// Any symbols missing in the kernel are ignored. Returns an error if multiple -// symbols with a given name were found. -func AssignModules(symbols map[string]string) error { - if !platform.IsLinux { - return fmt.Errorf("read /proc/kallsyms: %w", internal.ErrNotSupportedOnOS) - } - - if len(symbols) == 0 { - return nil - } - - // Attempt to fetch symbols from cache. - request := make(map[string]string) - for name := range symbols { - if mod, ok := symModules.Load(name); ok { - symbols[name] = mod - continue - } - - // Mark the symbol to be read from /proc/kallsyms. - request[name] = "" - } - if len(request) == 0 { - // All symbols satisfied from cache. - return nil - } - - f, err := os.Open("/proc/kallsyms") - if err != nil { - return err - } - defer f.Close() - - if err := assignModules(f, request); err != nil { - return fmt.Errorf("assigning symbol modules: %w", err) - } - - // Update the cache with the new symbols. Cache all requested symbols, even if - // they're missing or don't belong to a module. - for name, mod := range request { - symModules.Store(name, mod) - symbols[name] = mod - } - - return nil -} - -// assignModules assigns kernel symbol modules read from f to values requested -// by symbols. Always scans the whole input to make sure the user didn't request -// an ambiguous symbol. -func assignModules(f io.Reader, symbols map[string]string) error { - if len(symbols) == 0 { - return nil - } - - found := make(map[string]struct{}) - r := newReader(f) - for r.Line() { - // Only look for function symbols in the kernel's text section (tT). - s, err, skip := parseSymbol(r, []rune{'t', 'T'}) - if err != nil { - return fmt.Errorf("parsing kallsyms line: %w", err) - } - if skip { - continue - } - - if _, requested := symbols[string(s.name)]; !requested { - continue - } - - if _, ok := found[string(s.name)]; ok { - // We've already seen this symbol. Return an error to avoid silently - // attaching to a symbol in the wrong module. libbpf also rejects - // referring to ambiguous symbols. - // - // We can't simply check if we already have a value for the given symbol, - // since many won't have an associated kernel module. - return fmt.Errorf("symbol %s: duplicate found at address 0x%x (module %q): %w", - s.name, s.addr, s.mod, errAmbiguousKsym) - } - - symbols[string(s.name)] = string(s.mod) - found[string(s.name)] = struct{}{} - } - if err := r.Err(); err != nil { - return fmt.Errorf("reading kallsyms: %w", err) - } - - return nil -} - -// Address returns the address of the given symbol in the kernel. Returns 0 and -// no error if the symbol is not present. Returns an error if multiple addresses -// were found for a symbol. -// -// Consider [AssignAddresses] if you need to resolve multiple symbols, as it -// will only perform one iteration over /proc/kallsyms. -func Address(symbol string) (uint64, error) { - if symbol == "" { - return 0, nil - } - - if addr, ok := symAddrs.Load(symbol); ok { - return addr, nil - } - - request := map[string]uint64{symbol: 0} - if err := AssignAddresses(request); err != nil { - return 0, err - } - - return request[symbol], nil -} // AssignAddresses looks up the addresses of the requested symbols in the kernel // and assigns them to their corresponding values in the symbols map. Results @@ -238,6 +93,19 @@ func assignAddresses(f io.Reader, symbols map[string]uint64) error { return fmt.Errorf("symbol %s(0x%x): duplicate found at address 0x%x: %w", s.name, existing, s.addr, errAmbiguousKsym) } if requested { + // Reading a symbol with a zero address is a strong indication that + // kptr_restrict is set and the process doesn't have CAP_SYSLOG, or + // kptr_restrict is set to 2 (never show addresses). + // + // When running the kernel with KASLR disabled (like CI kernels running in + // microVMs), kallsyms will display many absolute symbols at address 0. + // This memory is unlikely to contain anything useful, and production + // machines are unlikely to run without KASLR. + // + // Return a helpful error instead of silently returning zero addresses. + if s.addr == 0 { + return fmt.Errorf("symbol %s: %w", s.name, internal.ErrRestrictedKernel) + } symbols[string(s.name)] = s.addr } } diff --git a/vendor/github.com/cilium/ebpf/internal/kconfig/kconfig.go b/vendor/github.com/cilium/ebpf/internal/kconfig/kconfig.go index 29c62b6266..de18dc8290 100644 --- a/vendor/github.com/cilium/ebpf/internal/kconfig/kconfig.go +++ b/vendor/github.com/cilium/ebpf/internal/kconfig/kconfig.go @@ -183,8 +183,8 @@ func putValueString(data []byte, typ btf.Type, value string) error { // Any Int, which is not bool, of one byte could be used to store char: // https://github.com/torvalds/linux/blob/1a5304fecee5/tools/lib/bpf/libbpf.c#L3637-L3638 - if contentType.Size != 1 && contentType.Encoding != btf.Bool { - return fmt.Errorf("cannot add string value, expected array of btf.Int of size 1, got array of btf.Int of size: %v", contentType.Size) + if contentType.Size != 1 || contentType.Encoding == btf.Bool { + return fmt.Errorf("cannot add string value, expected array of non-bool btf.Int of size 1, got array of btf.Int with encoding %v and size %v", contentType.Encoding, contentType.Size) } if !strings.HasPrefix(value, `"`) || !strings.HasSuffix(value, `"`) { diff --git a/vendor/github.com/cilium/ebpf/internal/mountinfo/mountinfo.go b/vendor/github.com/cilium/ebpf/internal/mountinfo/mountinfo.go new file mode 100644 index 0000000000..a3cb1d19ba --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/mountinfo/mountinfo.go @@ -0,0 +1,150 @@ +// Package mountinfo parses /proc/self/mountinfo to discover filesystem +// mounts visible in the calling process's mount namespace. +package mountinfo + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "slices" + "strings" + "sync" + + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/platform" +) + +// Entry represents one parsed line from /proc/self/mountinfo. +type Entry struct { + // MountPoint is the absolute path where the filesystem is mounted, + // with octal escapes (\040, \011, \012, \134) decoded. + MountPoint string + + // Root is the path within the source filesystem that's exposed at + // MountPoint, octal-decoded. It is "/" for a full filesystem mount and + // some other path (e.g. "/events") when a subdirectory of the source + // filesystem has been bind-mounted onto MountPoint. Callers that want + // to use MountPoint as the root of a filesystem should reject entries + // where Root != "/". + Root string + + // FSType is the filesystem type, e.g. "bpf", "tracefs", "debugfs". + FSType string +} + +// Read parses /proc/self/mountinfo and returns one Entry per mount line. +// The result is cached for the lifetime of the process; mounts that appear +// or disappear after the first call are not reflected. +// +// On non-Linux, returns an error wrapping [internal.ErrNotSupportedOnOS]. +func Read() ([]Entry, error) { + return readOnce() +} + +// FindByFSType returns the mount points for filesystems of the given type, +// in order of appearance, with duplicates removed. +func FindByFSType(fstype string) ([]string, error) { + entries, err := Read() + if err != nil { + return nil, err + } + return filterByFSType(entries, fstype), nil +} + +func filterByFSType(entries []Entry, fstype string) []string { + var mounts []string + for _, e := range entries { + if e.FSType != fstype { + continue + } + // Number of matching mounts is expected to be very low; linear + // search is faster and more cache-friendly than a map at this + // scale, and avoids the allocations a map would incur. + if !slices.Contains(mounts, e.MountPoint) { + mounts = append(mounts, e.MountPoint) + } + } + return mounts +} + +// parseEntries parses mountinfo data read from r. +func parseEntries(r io.Reader) ([]Entry, error) { + var entries []Entry + // Format of /proc/self/mountinfo: + // + // {id} {parent id} {major:minor} {root} {mount point} {mount options} [optional fields...] - {filesystem type} {source} {superblock options} + br := bufio.NewReader(r) + for { + line, err := br.ReadString('\n') + if line != "" { + line = strings.TrimRight(line, "\n") + if line != "" { + entry, perr := parseLine(line) + if perr != nil { + return nil, perr + } + entries = append(entries, entry) + } + } + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, fmt.Errorf("read mountinfo: %w", err) + } + } + + return entries, nil +} + +func parseLine(line string) (Entry, error) { + firstHalfStr, secondHalfStr, ok := strings.Cut(line, " - ") + if !ok { + return Entry{}, fmt.Errorf("invalid mountinfo line, missing dash: %q", line) + } + + secondHalf := strings.Fields(strings.TrimSpace(secondHalfStr)) + if len(secondHalf) == 0 { + return Entry{}, fmt.Errorf("invalid mountinfo line, too few fields after dash: %q", line) + } + + firstHalf := strings.Fields(strings.TrimSpace(firstHalfStr)) + if len(firstHalf) < 6 { + return Entry{}, fmt.Errorf("invalid mountinfo line, too few fields: %q", line) + } + + return Entry{ + Root: unescape(firstHalf[3]), + MountPoint: unescape(firstHalf[4]), + FSType: secondHalf[0], + }, nil +} + +// show_mountinfo in the kernel has the escape set of ' \t\n\\'. Instead of +// a full octal unescaper, only replace these specific characters. +var unescaper = strings.NewReplacer( + `\040`, " ", + `\011`, "\t", + `\012`, "\n", + `\134`, `\`, +) + +func unescape(s string) string { + return unescaper.Replace(s) +} + +var readOnce = sync.OnceValues(func() ([]Entry, error) { + if !platform.IsLinux { + return nil, fmt.Errorf("mountinfo: %w", internal.ErrNotSupportedOnOS) + } + + f, err := os.Open("/proc/self/mountinfo") + if err != nil { + return nil, fmt.Errorf("open mountinfo: %w", err) + } + defer f.Close() + + return parseEntries(f) +}) diff --git a/vendor/github.com/cilium/ebpf/internal/nil.go b/vendor/github.com/cilium/ebpf/internal/nil.go new file mode 100644 index 0000000000..83ba6c6f22 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/nil.go @@ -0,0 +1,24 @@ +package internal + +import ( + "fmt" + "reflect" +) + +// IsNilPointer returns an error if i is a nil interface or a nil pointer. +// Otherwise, it returns nil. +func IsNilPointer(i any) error { + if i == nil { + return fmt.Errorf("nil interface") + } + v := reflect.ValueOf(i) + if v.Kind() != reflect.Pointer { + return nil + } + + if v.IsNil() { + return fmt.Errorf("nil %T", i) + } + + return nil +} diff --git a/vendor/github.com/cilium/ebpf/internal/sys/doc.go b/vendor/github.com/cilium/ebpf/internal/sys/doc.go index dfe174448e..75d7e4013a 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/doc.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/doc.go @@ -3,4 +3,4 @@ package sys // Regenerate types.go by invoking go generate in the current directory. -//go:generate go run github.com/cilium/ebpf/internal/cmd/gentypes ../../btf/testdata/vmlinux.btf.gz +//go:generate go tool gentypes ../../btf/testdata/vmlinux.btf.gz diff --git a/vendor/github.com/cilium/ebpf/internal/sys/fd.go b/vendor/github.com/cilium/ebpf/internal/sys/fd.go index 7e769fb54e..f12d11c206 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/fd.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/fd.go @@ -21,23 +21,14 @@ const invalidFd = -1 func newFD(value int) *FD { testmain.TraceFD(value, 1) - fd := &FD{value} - runtime.SetFinalizer(fd, (*FD).finalize) + fd := &FD{raw: value} + fd.cleanup = runtime.AddCleanup(fd, func(raw int) { + testmain.LeakFD(raw) + _ = unix.Close(raw) + }, fd.raw) return fd } -// finalize is set as the FD's runtime finalizer and -// sends a leak trace before calling FD.Close(). -func (fd *FD) finalize() { - if fd.raw == invalidFd { - return - } - - testmain.LeakFD(fd.raw) - - _ = fd.Close() -} - func (fd *FD) String() string { return strconv.FormatInt(int64(fd.raw), 10) } @@ -63,6 +54,6 @@ func (fd *FD) Disown() int { testmain.ForgetFD(value) fd.raw = invalidFd - runtime.SetFinalizer(fd, nil) + fd.cleanup.Stop() return value } diff --git a/vendor/github.com/cilium/ebpf/internal/sys/fd_other.go b/vendor/github.com/cilium/ebpf/internal/sys/fd_other.go index 47057395ec..2a6423a591 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/fd_other.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/fd_other.go @@ -5,12 +5,14 @@ package sys import ( "fmt" "os" + "runtime" "github.com/cilium/ebpf/internal/unix" ) type FD struct { - raw int + raw int + cleanup runtime.Cleanup } // NewFD wraps a raw fd with a finalizer. diff --git a/vendor/github.com/cilium/ebpf/internal/sys/fd_windows.go b/vendor/github.com/cilium/ebpf/internal/sys/fd_windows.go index f392763823..1291c763f1 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/fd_windows.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/fd_windows.go @@ -3,6 +3,7 @@ package sys import ( "fmt" "os" + "runtime" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/efw" @@ -12,7 +13,8 @@ import ( // // It is not equivalent to a real file descriptor or handle. type FD struct { - raw int + raw int + cleanup runtime.Cleanup } // NewFD wraps a raw fd with a finalizer. diff --git a/vendor/github.com/cilium/ebpf/internal/sys/ptr.go b/vendor/github.com/cilium/ebpf/internal/sys/ptr.go index 173665c2a9..aa6c2e91aa 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/ptr.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/ptr.go @@ -28,6 +28,10 @@ type TypedPointer[T any] struct { ptr Pointer } +func (p TypedPointer[T]) IsNil() bool { + return p.ptr.ptr == nil +} + // SlicePointer creates a [TypedPointer] from a slice. func SlicePointer[T comparable](s []T) TypedPointer[T] { return TypedPointer[T]{ptr: UnsafeSlicePointer(s)} diff --git a/vendor/github.com/cilium/ebpf/internal/sys/syscall.go b/vendor/github.com/cilium/ebpf/internal/sys/syscall.go index f2fffd26b7..3a2ae9c5ab 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/syscall.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/syscall.go @@ -18,6 +18,7 @@ const ENOTSUPP = unix.Errno(524) // ProgInfo // LinkInfo // BtfInfo +// TokenInfo type Info interface { info() (unsafe.Pointer, uint32) } @@ -72,10 +73,30 @@ func (i *KprobeMultiLinkInfo) info() (unsafe.Pointer, uint32) { return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) } +func (i *UprobeMultiLinkInfo) info() (unsafe.Pointer, uint32) { + return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) +} + +func (i *RawTracepointLinkInfo) info() (unsafe.Pointer, uint32) { + return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) +} + func (i *KprobeLinkInfo) info() (unsafe.Pointer, uint32) { return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) } +func (i *UprobeLinkInfo) info() (unsafe.Pointer, uint32) { + return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) +} + +func (i *TracepointLinkInfo) info() (unsafe.Pointer, uint32) { + return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) +} + +func (i *EventLinkInfo) info() (unsafe.Pointer, uint32) { + return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) +} + var _ Info = (*BtfInfo)(nil) func (i *BtfInfo) info() (unsafe.Pointer, uint32) { @@ -86,9 +107,15 @@ func (i *PerfEventLinkInfo) info() (unsafe.Pointer, uint32) { return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) } +var _ Info = (*TokenInfo)(nil) + +func (i *TokenInfo) info() (unsafe.Pointer, uint32) { + return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) +} + // ObjInfo retrieves information about a BPF Fd. // -// info may be one of MapInfo, ProgInfo, LinkInfo and BtfInfo. +// info may be one of MapInfo, ProgInfo, LinkInfo, BtfInfo and TokenInfo. func ObjInfo(fd *FD, info Info) error { ptr, len := info.info() err := ObjGetInfoByFd(&ObjGetInfoByFdAttr{ diff --git a/vendor/github.com/cilium/ebpf/internal/sys/syscall_other.go b/vendor/github.com/cilium/ebpf/internal/sys/syscall_other.go index b99e6e462d..969eaa4106 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/syscall_other.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/syscall_other.go @@ -3,6 +3,7 @@ package sys import ( + "errors" "fmt" "os" "path/filepath" @@ -24,6 +25,11 @@ func BPF(cmd Cmd, attr unsafe.Pointer, size uintptr) (uintptr, error) { defer unmaskProfilerSignal() } + tok, err := tokenAttr(cmd, attr) + if err != nil { + return 0, fmt.Errorf("apply token attributes: %w", err) + } + for { r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size) runtime.KeepAlive(attr) @@ -39,6 +45,24 @@ func BPF(cmd Cmd, attr unsafe.Pointer, size uintptr) (uintptr, error) { err = wrappedErrno{errNo} } + // Tokens are in use, attempt to enrich EPERM with capability information. + if tok != nil && errors.Is(err, unix.EPERM) { + // Kernels before 6.17 don't have token info, so we can't return accurate + // errors. Make the token error a hint by adding a question mark. + // + // Returning ErrTokenCapabilities here isn't ideal since it's not + // conclusive, but since this behaviour is limited to one LTS release + // (6.12), and only when tokens are enabled, it's better than having to + // account for it in all existing feature probes and tests. + if tok.info == nil { + return r1, fmt.Errorf("%w (%w?)", err, ErrTokenCapabilities) + } + + if terr := tok.Capable(cmd, attr); terr != nil { + return r1, fmt.Errorf("%w: %w", terr, err) + } + } + return r1, err } } diff --git a/vendor/github.com/cilium/ebpf/internal/sys/token.go b/vendor/github.com/cilium/ebpf/internal/sys/token.go new file mode 100644 index 0000000000..9a5df39221 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/sys/token.go @@ -0,0 +1,226 @@ +package sys + +import ( + "errors" + "fmt" + "sync" + "unsafe" + + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/mountinfo" + "github.com/cilium/ebpf/internal/platform" + "github.com/cilium/ebpf/internal/testutils/testmain" + "github.com/cilium/ebpf/internal/unix" +) + +var ( + // ErrTokenCapabilities indicates that the operation failed due to + // insufficient capabilities being delegated to the token. + ErrTokenCapabilities = errors.New("token insufficiently privileged") +) + +// Token is a BPF token that can be used to create BPF objects that normally +// require CAP_SYS_ADMIN or CAP_NET_ADMIN. +// +// With a valid token, equivalent BPF objects can be created while only being +// granted CAP_BPF. +type Token struct { + fd *FD + + // Token info is available as of Linux 6.17, may be nil. + info *TokenInfo +} + +// FD returns the raw file descriptor of the token. +func (t *Token) FD() int { + return t.fd.Int() +} + +// Capable returns an error if the token does not allow using cmd or some of its +// provided attributes. +// +// Always returns nil on kernels before 6.17, as they don't provide token info. +func (t *Token) Capable(cmd Cmd, attr unsafe.Pointer) error { + if t.info == nil { + return nil + } + + if !t.cmd(cmd) { + return fmt.Errorf("command %s: %w", cmd, ErrTokenCapabilities) + } + + switch cmd { + case BPF_MAP_CREATE: + attr := (*MapCreateAttr)(attr) + if !t.mapType(attr.MapType) { + return fmt.Errorf("map type %s: %w", attr.MapType, ErrTokenCapabilities) + } + + case BPF_PROG_LOAD: + attr := (*ProgLoadAttr)(attr) + if !t.progType(attr.ProgType) { + return fmt.Errorf("program type %s: %w", attr.ProgType, ErrTokenCapabilities) + } + + if !t.attachType(attr.ExpectedAttachType) { + return fmt.Errorf("attach type %s: %w", attr.ExpectedAttachType, ErrTokenCapabilities) + } + } + + return nil +} + +// cmd returns true if cmd is allowed by the Token. +func (t *Token) cmd(cmd Cmd) bool { + if t.info == nil { + return true + } + return t.info.AllowedCmds&(uint64(1)<= len(_Cmd_index)-1 { + return "Cmd(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Cmd_name[_Cmd_index[idx]:_Cmd_index[idx+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[BPF_MAP_TYPE_UNSPEC-0] + _ = x[BPF_MAP_TYPE_HASH-1] + _ = x[BPF_MAP_TYPE_ARRAY-2] + _ = x[BPF_MAP_TYPE_PROG_ARRAY-3] + _ = x[BPF_MAP_TYPE_PERF_EVENT_ARRAY-4] + _ = x[BPF_MAP_TYPE_PERCPU_HASH-5] + _ = x[BPF_MAP_TYPE_PERCPU_ARRAY-6] + _ = x[BPF_MAP_TYPE_STACK_TRACE-7] + _ = x[BPF_MAP_TYPE_CGROUP_ARRAY-8] + _ = x[BPF_MAP_TYPE_LRU_HASH-9] + _ = x[BPF_MAP_TYPE_LRU_PERCPU_HASH-10] + _ = x[BPF_MAP_TYPE_LPM_TRIE-11] + _ = x[BPF_MAP_TYPE_ARRAY_OF_MAPS-12] + _ = x[BPF_MAP_TYPE_HASH_OF_MAPS-13] + _ = x[BPF_MAP_TYPE_DEVMAP-14] + _ = x[BPF_MAP_TYPE_SOCKMAP-15] + _ = x[BPF_MAP_TYPE_CPUMAP-16] + _ = x[BPF_MAP_TYPE_XSKMAP-17] + _ = x[BPF_MAP_TYPE_SOCKHASH-18] + _ = x[BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED-19] + _ = x[BPF_MAP_TYPE_CGROUP_STORAGE-19] + _ = x[BPF_MAP_TYPE_REUSEPORT_SOCKARRAY-20] + _ = x[BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED-21] + _ = x[BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE-21] + _ = x[BPF_MAP_TYPE_QUEUE-22] + _ = x[BPF_MAP_TYPE_STACK-23] + _ = x[BPF_MAP_TYPE_SK_STORAGE-24] + _ = x[BPF_MAP_TYPE_DEVMAP_HASH-25] + _ = x[BPF_MAP_TYPE_STRUCT_OPS-26] + _ = x[BPF_MAP_TYPE_RINGBUF-27] + _ = x[BPF_MAP_TYPE_INODE_STORAGE-28] + _ = x[BPF_MAP_TYPE_TASK_STORAGE-29] + _ = x[BPF_MAP_TYPE_BLOOM_FILTER-30] + _ = x[BPF_MAP_TYPE_USER_RINGBUF-31] + _ = x[BPF_MAP_TYPE_CGRP_STORAGE-32] + _ = x[BPF_MAP_TYPE_ARENA-33] + _ = x[BPF_MAP_TYPE_INSN_ARRAY-34] + _ = x[__MAX_BPF_MAP_TYPE-35] +} + +const _MapType_name = "BPF_MAP_TYPE_UNSPECBPF_MAP_TYPE_HASHBPF_MAP_TYPE_ARRAYBPF_MAP_TYPE_PROG_ARRAYBPF_MAP_TYPE_PERF_EVENT_ARRAYBPF_MAP_TYPE_PERCPU_HASHBPF_MAP_TYPE_PERCPU_ARRAYBPF_MAP_TYPE_STACK_TRACEBPF_MAP_TYPE_CGROUP_ARRAYBPF_MAP_TYPE_LRU_HASHBPF_MAP_TYPE_LRU_PERCPU_HASHBPF_MAP_TYPE_LPM_TRIEBPF_MAP_TYPE_ARRAY_OF_MAPSBPF_MAP_TYPE_HASH_OF_MAPSBPF_MAP_TYPE_DEVMAPBPF_MAP_TYPE_SOCKMAPBPF_MAP_TYPE_CPUMAPBPF_MAP_TYPE_XSKMAPBPF_MAP_TYPE_SOCKHASHBPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATEDBPF_MAP_TYPE_REUSEPORT_SOCKARRAYBPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATEDBPF_MAP_TYPE_QUEUEBPF_MAP_TYPE_STACKBPF_MAP_TYPE_SK_STORAGEBPF_MAP_TYPE_DEVMAP_HASHBPF_MAP_TYPE_STRUCT_OPSBPF_MAP_TYPE_RINGBUFBPF_MAP_TYPE_INODE_STORAGEBPF_MAP_TYPE_TASK_STORAGEBPF_MAP_TYPE_BLOOM_FILTERBPF_MAP_TYPE_USER_RINGBUFBPF_MAP_TYPE_CGRP_STORAGEBPF_MAP_TYPE_ARENABPF_MAP_TYPE_INSN_ARRAY__MAX_BPF_MAP_TYPE" + +var _MapType_index = [...]uint16{0, 19, 36, 54, 77, 106, 130, 155, 179, 204, 225, 253, 274, 300, 325, 344, 364, 383, 402, 423, 461, 493, 538, 556, 574, 597, 621, 644, 664, 690, 715, 740, 765, 790, 808, 831, 849} + +func (i MapType) String() string { + idx := int(i) - 0 + if i < 0 || idx >= len(_MapType_index)-1 { + return "MapType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _MapType_name[_MapType_index[idx]:_MapType_index[idx+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[BPF_PROG_TYPE_UNSPEC-0] + _ = x[BPF_PROG_TYPE_SOCKET_FILTER-1] + _ = x[BPF_PROG_TYPE_KPROBE-2] + _ = x[BPF_PROG_TYPE_SCHED_CLS-3] + _ = x[BPF_PROG_TYPE_SCHED_ACT-4] + _ = x[BPF_PROG_TYPE_TRACEPOINT-5] + _ = x[BPF_PROG_TYPE_XDP-6] + _ = x[BPF_PROG_TYPE_PERF_EVENT-7] + _ = x[BPF_PROG_TYPE_CGROUP_SKB-8] + _ = x[BPF_PROG_TYPE_CGROUP_SOCK-9] + _ = x[BPF_PROG_TYPE_LWT_IN-10] + _ = x[BPF_PROG_TYPE_LWT_OUT-11] + _ = x[BPF_PROG_TYPE_LWT_XMIT-12] + _ = x[BPF_PROG_TYPE_SOCK_OPS-13] + _ = x[BPF_PROG_TYPE_SK_SKB-14] + _ = x[BPF_PROG_TYPE_CGROUP_DEVICE-15] + _ = x[BPF_PROG_TYPE_SK_MSG-16] + _ = x[BPF_PROG_TYPE_RAW_TRACEPOINT-17] + _ = x[BPF_PROG_TYPE_CGROUP_SOCK_ADDR-18] + _ = x[BPF_PROG_TYPE_LWT_SEG6LOCAL-19] + _ = x[BPF_PROG_TYPE_LIRC_MODE2-20] + _ = x[BPF_PROG_TYPE_SK_REUSEPORT-21] + _ = x[BPF_PROG_TYPE_FLOW_DISSECTOR-22] + _ = x[BPF_PROG_TYPE_CGROUP_SYSCTL-23] + _ = x[BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE-24] + _ = x[BPF_PROG_TYPE_CGROUP_SOCKOPT-25] + _ = x[BPF_PROG_TYPE_TRACING-26] + _ = x[BPF_PROG_TYPE_STRUCT_OPS-27] + _ = x[BPF_PROG_TYPE_EXT-28] + _ = x[BPF_PROG_TYPE_LSM-29] + _ = x[BPF_PROG_TYPE_SK_LOOKUP-30] + _ = x[BPF_PROG_TYPE_SYSCALL-31] + _ = x[BPF_PROG_TYPE_NETFILTER-32] + _ = x[__MAX_BPF_PROG_TYPE-33] +} + +const _ProgType_name = "BPF_PROG_TYPE_UNSPECBPF_PROG_TYPE_SOCKET_FILTERBPF_PROG_TYPE_KPROBEBPF_PROG_TYPE_SCHED_CLSBPF_PROG_TYPE_SCHED_ACTBPF_PROG_TYPE_TRACEPOINTBPF_PROG_TYPE_XDPBPF_PROG_TYPE_PERF_EVENTBPF_PROG_TYPE_CGROUP_SKBBPF_PROG_TYPE_CGROUP_SOCKBPF_PROG_TYPE_LWT_INBPF_PROG_TYPE_LWT_OUTBPF_PROG_TYPE_LWT_XMITBPF_PROG_TYPE_SOCK_OPSBPF_PROG_TYPE_SK_SKBBPF_PROG_TYPE_CGROUP_DEVICEBPF_PROG_TYPE_SK_MSGBPF_PROG_TYPE_RAW_TRACEPOINTBPF_PROG_TYPE_CGROUP_SOCK_ADDRBPF_PROG_TYPE_LWT_SEG6LOCALBPF_PROG_TYPE_LIRC_MODE2BPF_PROG_TYPE_SK_REUSEPORTBPF_PROG_TYPE_FLOW_DISSECTORBPF_PROG_TYPE_CGROUP_SYSCTLBPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLEBPF_PROG_TYPE_CGROUP_SOCKOPTBPF_PROG_TYPE_TRACINGBPF_PROG_TYPE_STRUCT_OPSBPF_PROG_TYPE_EXTBPF_PROG_TYPE_LSMBPF_PROG_TYPE_SK_LOOKUPBPF_PROG_TYPE_SYSCALLBPF_PROG_TYPE_NETFILTER__MAX_BPF_PROG_TYPE" + +var _ProgType_index = [...]uint16{0, 20, 47, 67, 90, 113, 137, 154, 178, 202, 227, 247, 268, 290, 312, 332, 359, 379, 407, 437, 464, 488, 514, 542, 569, 606, 634, 655, 679, 696, 713, 736, 757, 780, 799} + +func (i ProgType) String() string { + idx := int(i) - 0 + if i < 0 || idx >= len(_ProgType_index)-1 { + return "ProgType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ProgType_name[_ProgType_index[idx]:_ProgType_index[idx+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[BPF_CGROUP_INET_INGRESS-0] + _ = x[BPF_CGROUP_INET_EGRESS-1] + _ = x[BPF_CGROUP_INET_SOCK_CREATE-2] + _ = x[BPF_CGROUP_SOCK_OPS-3] + _ = x[BPF_SK_SKB_STREAM_PARSER-4] + _ = x[BPF_SK_SKB_STREAM_VERDICT-5] + _ = x[BPF_CGROUP_DEVICE-6] + _ = x[BPF_SK_MSG_VERDICT-7] + _ = x[BPF_CGROUP_INET4_BIND-8] + _ = x[BPF_CGROUP_INET6_BIND-9] + _ = x[BPF_CGROUP_INET4_CONNECT-10] + _ = x[BPF_CGROUP_INET6_CONNECT-11] + _ = x[BPF_CGROUP_INET4_POST_BIND-12] + _ = x[BPF_CGROUP_INET6_POST_BIND-13] + _ = x[BPF_CGROUP_UDP4_SENDMSG-14] + _ = x[BPF_CGROUP_UDP6_SENDMSG-15] + _ = x[BPF_LIRC_MODE2-16] + _ = x[BPF_FLOW_DISSECTOR-17] + _ = x[BPF_CGROUP_SYSCTL-18] + _ = x[BPF_CGROUP_UDP4_RECVMSG-19] + _ = x[BPF_CGROUP_UDP6_RECVMSG-20] + _ = x[BPF_CGROUP_GETSOCKOPT-21] + _ = x[BPF_CGROUP_SETSOCKOPT-22] + _ = x[BPF_TRACE_RAW_TP-23] + _ = x[BPF_TRACE_FENTRY-24] + _ = x[BPF_TRACE_FEXIT-25] + _ = x[BPF_MODIFY_RETURN-26] + _ = x[BPF_LSM_MAC-27] + _ = x[BPF_TRACE_ITER-28] + _ = x[BPF_CGROUP_INET4_GETPEERNAME-29] + _ = x[BPF_CGROUP_INET6_GETPEERNAME-30] + _ = x[BPF_CGROUP_INET4_GETSOCKNAME-31] + _ = x[BPF_CGROUP_INET6_GETSOCKNAME-32] + _ = x[BPF_XDP_DEVMAP-33] + _ = x[BPF_CGROUP_INET_SOCK_RELEASE-34] + _ = x[BPF_XDP_CPUMAP-35] + _ = x[BPF_SK_LOOKUP-36] + _ = x[BPF_XDP-37] + _ = x[BPF_SK_SKB_VERDICT-38] + _ = x[BPF_SK_REUSEPORT_SELECT-39] + _ = x[BPF_SK_REUSEPORT_SELECT_OR_MIGRATE-40] + _ = x[BPF_PERF_EVENT-41] + _ = x[BPF_TRACE_KPROBE_MULTI-42] + _ = x[BPF_LSM_CGROUP-43] + _ = x[BPF_STRUCT_OPS-44] + _ = x[BPF_NETFILTER-45] + _ = x[BPF_TCX_INGRESS-46] + _ = x[BPF_TCX_EGRESS-47] + _ = x[BPF_TRACE_UPROBE_MULTI-48] + _ = x[BPF_CGROUP_UNIX_CONNECT-49] + _ = x[BPF_CGROUP_UNIX_SENDMSG-50] + _ = x[BPF_CGROUP_UNIX_RECVMSG-51] + _ = x[BPF_CGROUP_UNIX_GETPEERNAME-52] + _ = x[BPF_CGROUP_UNIX_GETSOCKNAME-53] + _ = x[BPF_NETKIT_PRIMARY-54] + _ = x[BPF_NETKIT_PEER-55] + _ = x[BPF_TRACE_KPROBE_SESSION-56] + _ = x[BPF_TRACE_UPROBE_SESSION-57] + _ = x[BPF_TRACE_FSESSION-58] + _ = x[__MAX_BPF_ATTACH_TYPE-59] +} + +const _AttachType_name = "BPF_CGROUP_INET_INGRESSBPF_CGROUP_INET_EGRESSBPF_CGROUP_INET_SOCK_CREATEBPF_CGROUP_SOCK_OPSBPF_SK_SKB_STREAM_PARSERBPF_SK_SKB_STREAM_VERDICTBPF_CGROUP_DEVICEBPF_SK_MSG_VERDICTBPF_CGROUP_INET4_BINDBPF_CGROUP_INET6_BINDBPF_CGROUP_INET4_CONNECTBPF_CGROUP_INET6_CONNECTBPF_CGROUP_INET4_POST_BINDBPF_CGROUP_INET6_POST_BINDBPF_CGROUP_UDP4_SENDMSGBPF_CGROUP_UDP6_SENDMSGBPF_LIRC_MODE2BPF_FLOW_DISSECTORBPF_CGROUP_SYSCTLBPF_CGROUP_UDP4_RECVMSGBPF_CGROUP_UDP6_RECVMSGBPF_CGROUP_GETSOCKOPTBPF_CGROUP_SETSOCKOPTBPF_TRACE_RAW_TPBPF_TRACE_FENTRYBPF_TRACE_FEXITBPF_MODIFY_RETURNBPF_LSM_MACBPF_TRACE_ITERBPF_CGROUP_INET4_GETPEERNAMEBPF_CGROUP_INET6_GETPEERNAMEBPF_CGROUP_INET4_GETSOCKNAMEBPF_CGROUP_INET6_GETSOCKNAMEBPF_XDP_DEVMAPBPF_CGROUP_INET_SOCK_RELEASEBPF_XDP_CPUMAPBPF_SK_LOOKUPBPF_XDPBPF_SK_SKB_VERDICTBPF_SK_REUSEPORT_SELECTBPF_SK_REUSEPORT_SELECT_OR_MIGRATEBPF_PERF_EVENTBPF_TRACE_KPROBE_MULTIBPF_LSM_CGROUPBPF_STRUCT_OPSBPF_NETFILTERBPF_TCX_INGRESSBPF_TCX_EGRESSBPF_TRACE_UPROBE_MULTIBPF_CGROUP_UNIX_CONNECTBPF_CGROUP_UNIX_SENDMSGBPF_CGROUP_UNIX_RECVMSGBPF_CGROUP_UNIX_GETPEERNAMEBPF_CGROUP_UNIX_GETSOCKNAMEBPF_NETKIT_PRIMARYBPF_NETKIT_PEERBPF_TRACE_KPROBE_SESSIONBPF_TRACE_UPROBE_SESSIONBPF_TRACE_FSESSION__MAX_BPF_ATTACH_TYPE" + +var _AttachType_index = [...]uint16{0, 23, 45, 72, 91, 115, 140, 157, 175, 196, 217, 241, 265, 291, 317, 340, 363, 377, 395, 412, 435, 458, 479, 500, 516, 532, 547, 564, 575, 589, 617, 645, 673, 701, 715, 743, 757, 770, 777, 795, 818, 852, 866, 888, 902, 916, 929, 944, 958, 980, 1003, 1026, 1049, 1076, 1103, 1121, 1136, 1160, 1184, 1202, 1223} + +func (i AttachType) String() string { + idx := int(i) - 0 + if i < 0 || idx >= len(_AttachType_index)-1 { + return "AttachType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _AttachType_name[_AttachType_index[idx]:_AttachType_index[idx+1]] +} diff --git a/vendor/github.com/cilium/ebpf/internal/sysenc/marshal.go b/vendor/github.com/cilium/ebpf/internal/sysenc/marshal.go index 3f7deb80f1..fdd81ac126 100644 --- a/vendor/github.com/cilium/ebpf/internal/sysenc/marshal.go +++ b/vendor/github.com/cilium/ebpf/internal/sysenc/marshal.go @@ -68,7 +68,7 @@ func Marshal(data any, size int) (Buffer, error) { // // Returns an error if buf can't be unmarshalled according to the behaviour // of [binary.Decode]. -func Unmarshal(data interface{}, buf []byte) error { +func Unmarshal(data any, buf []byte) error { switch value := data.(type) { case encoding.BinaryUnmarshaler: return value.UnmarshalBinary(buf) diff --git a/vendor/github.com/cilium/ebpf/internal/tracefs/kprobe.go b/vendor/github.com/cilium/ebpf/internal/tracefs/kprobe.go index 7f7b3cba51..c42725b4d7 100644 --- a/vendor/github.com/cilium/ebpf/internal/tracefs/kprobe.go +++ b/vendor/github.com/cilium/ebpf/internal/tracefs/kprobe.go @@ -13,6 +13,7 @@ import ( "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/linux" + "github.com/cilium/ebpf/internal/mountinfo" "github.com/cilium/ebpf/internal/platform" "github.com/cilium/ebpf/internal/unix" ) @@ -23,7 +24,7 @@ var ( ErrInvalidMaxActive = errors.New("can only set maxactive on kretprobes") ) -//go:generate go run golang.org/x/tools/cmd/stringer@latest -type=ProbeType -linecomment +//go:generate go tool stringer -type=ProbeType -linecomment type ProbeType uint8 @@ -72,11 +73,11 @@ func RandomGroup(prefix string) (string, error) { } // validIdentifier implements the equivalent of a regex match -// against "^[a-zA-Z_][0-9a-zA-Z_]*$". +// against "^[a-zA-Z_][0-9a-zA-Z_-]*$". // -// Trace event groups, names and kernel symbols must adhere to this set -// of characters. Non-empty, first character must not be a number, all -// characters must be alphanumeric or underscore. +// Trace event groups, names and kernel symbols must adhere to this set of +// characters. Non-empty, first character must not be a number or hyphen, all +// characters must be alphanumeric, underscore or hyphen. func validIdentifier(s string) bool { if len(s) < 1 { return false @@ -86,7 +87,7 @@ func validIdentifier(s string) bool { case c >= 'a' && c <= 'z': case c >= 'A' && c <= 'Z': case c == '_': - case i > 0 && c >= '0' && c <= '9': + case i > 0 && (c == '-' || c >= '0' && c <= '9'): default: return false @@ -109,15 +110,31 @@ func sanitizeTracefsPath(path ...string) (string, error) { return p, nil } -// getTracefsPath will return a correct path to the tracefs mount point. -// Since kernel 4.1 tracefs should be mounted by default at /sys/kernel/tracing, -// but may be also be available at /sys/kernel/debug/tracing if debugfs is mounted. -// The available tracefs paths will depends on distribution choices. +// getTracefsPath returns a correct path to the tracefs mount point. +// +// The discovery order is: +// +// 1. Any tracefs mount listed in /proc/self/mountinfo (kernel 4.1+). +// This works regardless of where the mount sits in the filesystem, +// so containers that bind-mount tracefs at a non-canonical path are +// supported automatically. +// 2. A debugfs mount with a tracing/ subdirectory, for older systems +// where tracefs has not been lifted out of debugfs. +// 3. As a final fallback, probe the canonical kernel paths +// (/sys/kernel/tracing, /sys/kernel/debug/tracing) directly with +// statfs. This catches edge cases where /proc/self/mountinfo is +// unavailable or doesn't report the mount. var getTracefsPath = sync.OnceValues(func() (string, error) { if !platform.IsLinux { return "", fmt.Errorf("tracefs: %w", internal.ErrNotSupportedOnOS) } + if entries, err := mountinfo.Read(); err == nil { + if path := findTracefsInEntries(entries); path != "" { + return path, nil + } + } + for _, p := range []struct { path string fsType int64 @@ -135,6 +152,28 @@ var getTracefsPath = sync.OnceValues(func() (string, error) { return "", errors.New("neither debugfs nor tracefs are mounted") }) +// findTracefsInEntries returns the first occurrence of a tracefs in the +// given entries. +// +// Returns an empty string when no usable mount is found. +func findTracefsInEntries(entries []mountinfo.Entry) string { + for _, e := range entries { + if e.FSType == "tracefs" && e.Root == "/" { + return e.MountPoint + } + } + for _, e := range entries { + if e.FSType != "debugfs" || e.Root != "/" { + continue + } + tracing := filepath.Join(e.MountPoint, "tracing") + if info, err := os.Stat(tracing); err == nil && info.IsDir() { + return tracing + } + } + return "" +} + // sanitizeIdentifier replaces every invalid character for the tracefs api with an underscore. // // It is equivalent to calling regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString("_"). @@ -200,6 +239,8 @@ type Event struct { group, name string // event id allocated by the kernel. 0 if the event has already been removed. id uint64 + + cleanup runtime.Cleanup } // NewEvent creates a new ephemeral trace event. @@ -316,8 +357,11 @@ func NewEvent(args ProbeArgs) (*Event, error) { return nil, fmt.Errorf("get trace event id: %w", err) } - evt := &Event{args.Type, args.Group, eventName, tid} - runtime.SetFinalizer(evt, (*Event).Close) + evt := &Event{typ: args.Type, group: args.Group, name: eventName, id: tid} + evt.cleanup = runtime.AddCleanup(evt, func(*byte) { + _ = removeEvent(args.Type, fmt.Sprintf("%s/%s", args.Group, eventName)) + }, nil) + return evt, nil } @@ -330,7 +374,7 @@ func (evt *Event) Close() error { } evt.id = 0 - runtime.SetFinalizer(evt, nil) + evt.cleanup.Stop() pe := fmt.Sprintf("%s/%s", evt.group, evt.name) return removeEvent(evt.typ, pe) } diff --git a/vendor/github.com/cilium/ebpf/internal/tracefs/probetype_string.go b/vendor/github.com/cilium/ebpf/internal/tracefs/probetype_string.go index 87cb0a059b..ed8471a893 100644 --- a/vendor/github.com/cilium/ebpf/internal/tracefs/probetype_string.go +++ b/vendor/github.com/cilium/ebpf/internal/tracefs/probetype_string.go @@ -17,8 +17,9 @@ const _ProbeType_name = "kprobeuprobe" var _ProbeType_index = [...]uint8{0, 6, 12} func (i ProbeType) String() string { - if i >= ProbeType(len(_ProbeType_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_ProbeType_index)-1 { return "ProbeType(" + strconv.FormatInt(int64(i), 10) + ")" } - return _ProbeType_name[_ProbeType_index[i]:_ProbeType_index[i+1]] + return _ProbeType_name[_ProbeType_index[idx]:_ProbeType_index[idx+1]] } diff --git a/vendor/github.com/cilium/ebpf/internal/unix/errno_windows.go b/vendor/github.com/cilium/ebpf/internal/unix/errno_windows.go index 7500cd6d4e..266e43daa0 100644 --- a/vendor/github.com/cilium/ebpf/internal/unix/errno_windows.go +++ b/vendor/github.com/cilium/ebpf/internal/unix/errno_windows.go @@ -9,7 +9,7 @@ import ( "syscall" ) -//go:generate go run golang.org/x/tools/cmd/stringer@latest -type=Errno -tags=windows -output=errno_string_windows.go +//go:generate go tool stringer -type=Errno -tags=windows -output=errno_string_windows.go // Windows specific constants for Unix errnos. // @@ -37,6 +37,7 @@ const ( EILSEQ Errno = 42 ENOTSUP Errno = 129 EOPNOTSUPP Errno = 130 + EOTHER Errno = 131 ETIMEDOUT Errno = 138 EWOULDBLOCK Errno = 140 ) diff --git a/vendor/github.com/cilium/ebpf/internal/unix/types_linux.go b/vendor/github.com/cilium/ebpf/internal/unix/types_linux.go index 14a0a19290..7cb13372b3 100644 --- a/vendor/github.com/cilium/ebpf/internal/unix/types_linux.go +++ b/vendor/github.com/cilium/ebpf/internal/unix/types_linux.go @@ -10,69 +10,83 @@ import ( ) const ( - BPF_F_NO_PREALLOC = linux.BPF_F_NO_PREALLOC - BPF_F_NUMA_NODE = linux.BPF_F_NUMA_NODE - BPF_F_RDONLY = linux.BPF_F_RDONLY - BPF_F_WRONLY = linux.BPF_F_WRONLY - BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG - BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG - BPF_F_SLEEPABLE = linux.BPF_F_SLEEPABLE - BPF_F_XDP_HAS_FRAGS = linux.BPF_F_XDP_HAS_FRAGS - BPF_F_MMAPABLE = linux.BPF_F_MMAPABLE - BPF_F_INNER_MAP = linux.BPF_F_INNER_MAP - BPF_F_KPROBE_MULTI_RETURN = linux.BPF_F_KPROBE_MULTI_RETURN - BPF_F_UPROBE_MULTI_RETURN = linux.BPF_F_UPROBE_MULTI_RETURN - BPF_F_LOCK = linux.BPF_F_LOCK - BPF_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN - BPF_TAG_SIZE = linux.BPF_TAG_SIZE - BPF_RINGBUF_BUSY_BIT = linux.BPF_RINGBUF_BUSY_BIT - BPF_RINGBUF_DISCARD_BIT = linux.BPF_RINGBUF_DISCARD_BIT - BPF_RINGBUF_HDR_SZ = linux.BPF_RINGBUF_HDR_SZ - SYS_BPF = linux.SYS_BPF - F_DUPFD_CLOEXEC = linux.F_DUPFD_CLOEXEC - EPOLL_CTL_ADD = linux.EPOLL_CTL_ADD - EPOLL_CLOEXEC = linux.EPOLL_CLOEXEC - O_CLOEXEC = linux.O_CLOEXEC - O_NONBLOCK = linux.O_NONBLOCK - PROT_NONE = linux.PROT_NONE - PROT_READ = linux.PROT_READ - PROT_WRITE = linux.PROT_WRITE - MAP_ANON = linux.MAP_ANON - MAP_SHARED = linux.MAP_SHARED - MAP_FIXED = linux.MAP_FIXED - MAP_PRIVATE = linux.MAP_PRIVATE - PERF_ATTR_SIZE_VER1 = linux.PERF_ATTR_SIZE_VER1 - PERF_TYPE_SOFTWARE = linux.PERF_TYPE_SOFTWARE - PERF_TYPE_TRACEPOINT = linux.PERF_TYPE_TRACEPOINT - PERF_COUNT_SW_BPF_OUTPUT = linux.PERF_COUNT_SW_BPF_OUTPUT - PERF_EVENT_IOC_DISABLE = linux.PERF_EVENT_IOC_DISABLE - PERF_EVENT_IOC_ENABLE = linux.PERF_EVENT_IOC_ENABLE - PERF_EVENT_IOC_SET_BPF = linux.PERF_EVENT_IOC_SET_BPF - PerfBitWatermark = linux.PerfBitWatermark - PerfBitWriteBackward = linux.PerfBitWriteBackward - PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW - PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC - RLIM_INFINITY = linux.RLIM_INFINITY - RLIMIT_MEMLOCK = linux.RLIMIT_MEMLOCK - BPF_STATS_RUN_TIME = linux.BPF_STATS_RUN_TIME - PERF_RECORD_LOST = linux.PERF_RECORD_LOST - PERF_RECORD_SAMPLE = linux.PERF_RECORD_SAMPLE - AT_FDCWD = linux.AT_FDCWD - RENAME_NOREPLACE = linux.RENAME_NOREPLACE - SO_ATTACH_BPF = linux.SO_ATTACH_BPF - SO_DETACH_BPF = linux.SO_DETACH_BPF - SOL_SOCKET = linux.SOL_SOCKET - SIGPROF = linux.SIGPROF - SIGUSR1 = linux.SIGUSR1 - SIG_BLOCK = linux.SIG_BLOCK - SIG_UNBLOCK = linux.SIG_UNBLOCK - BPF_FS_MAGIC = linux.BPF_FS_MAGIC - TRACEFS_MAGIC = linux.TRACEFS_MAGIC - DEBUGFS_MAGIC = linux.DEBUGFS_MAGIC - BPF_RB_NO_WAKEUP = linux.BPF_RB_NO_WAKEUP - BPF_RB_FORCE_WAKEUP = linux.BPF_RB_FORCE_WAKEUP - AF_UNSPEC = linux.AF_UNSPEC - IFF_UP = linux.IFF_UP + BPF_F_NO_PREALLOC = linux.BPF_F_NO_PREALLOC + BPF_F_NUMA_NODE = linux.BPF_F_NUMA_NODE + BPF_F_RDONLY = linux.BPF_F_RDONLY + BPF_F_WRONLY = linux.BPF_F_WRONLY + BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG + BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG + BPF_F_SLEEPABLE = linux.BPF_F_SLEEPABLE + BPF_F_XDP_HAS_FRAGS = linux.BPF_F_XDP_HAS_FRAGS + BPF_F_MMAPABLE = linux.BPF_F_MMAPABLE + BPF_F_INNER_MAP = linux.BPF_F_INNER_MAP + BPF_F_KPROBE_MULTI_RETURN = linux.BPF_F_KPROBE_MULTI_RETURN + BPF_F_UPROBE_MULTI_RETURN = linux.BPF_F_UPROBE_MULTI_RETURN + BPF_F_LOCK = linux.BPF_F_LOCK + BPF_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN + BPF_TAG_SIZE = linux.BPF_TAG_SIZE + BPF_RINGBUF_BUSY_BIT = linux.BPF_RINGBUF_BUSY_BIT + BPF_RINGBUF_DISCARD_BIT = linux.BPF_RINGBUF_DISCARD_BIT + BPF_RINGBUF_HDR_SZ = linux.BPF_RINGBUF_HDR_SZ + SYS_BPF = linux.SYS_BPF + F_DUPFD_CLOEXEC = linux.F_DUPFD_CLOEXEC + EPOLL_CTL_ADD = linux.EPOLL_CTL_ADD + EPOLL_CLOEXEC = linux.EPOLL_CLOEXEC + O_RDONLY = linux.O_RDONLY + O_DIRECTORY = linux.O_DIRECTORY + O_CLOEXEC = linux.O_CLOEXEC + O_NONBLOCK = linux.O_NONBLOCK + PROT_NONE = linux.PROT_NONE + PROT_READ = linux.PROT_READ + PROT_WRITE = linux.PROT_WRITE + MAP_ANON = linux.MAP_ANON + MAP_SHARED = linux.MAP_SHARED + MAP_FIXED = linux.MAP_FIXED + MAP_PRIVATE = linux.MAP_PRIVATE + PERF_ATTR_SIZE_VER1 = linux.PERF_ATTR_SIZE_VER1 + PERF_TYPE_SOFTWARE = linux.PERF_TYPE_SOFTWARE + PERF_TYPE_TRACEPOINT = linux.PERF_TYPE_TRACEPOINT + PERF_COUNT_SW_BPF_OUTPUT = linux.PERF_COUNT_SW_BPF_OUTPUT + PERF_EVENT_IOC_DISABLE = linux.PERF_EVENT_IOC_DISABLE + PERF_EVENT_IOC_ENABLE = linux.PERF_EVENT_IOC_ENABLE + PERF_EVENT_IOC_SET_BPF = linux.PERF_EVENT_IOC_SET_BPF + PerfBitWatermark = linux.PerfBitWatermark + PerfBitWriteBackward = linux.PerfBitWriteBackward + PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW + PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC + RLIM_INFINITY = linux.RLIM_INFINITY + RLIMIT_MEMLOCK = linux.RLIMIT_MEMLOCK + BPF_STATS_RUN_TIME = linux.BPF_STATS_RUN_TIME + PERF_RECORD_LOST = linux.PERF_RECORD_LOST + PERF_RECORD_SAMPLE = linux.PERF_RECORD_SAMPLE + AT_FDCWD = linux.AT_FDCWD + RENAME_NOREPLACE = linux.RENAME_NOREPLACE + SO_ATTACH_BPF = linux.SO_ATTACH_BPF + SO_DETACH_BPF = linux.SO_DETACH_BPF + SOL_SOCKET = linux.SOL_SOCKET + SIGPROF = linux.SIGPROF + SIGUSR1 = linux.SIGUSR1 + SIG_BLOCK = linux.SIG_BLOCK + SIG_UNBLOCK = linux.SIG_UNBLOCK + BPF_FS_MAGIC = linux.BPF_FS_MAGIC + TRACEFS_MAGIC = linux.TRACEFS_MAGIC + DEBUGFS_MAGIC = linux.DEBUGFS_MAGIC + BPF_RB_NO_WAKEUP = linux.BPF_RB_NO_WAKEUP + BPF_RB_FORCE_WAKEUP = linux.BPF_RB_FORCE_WAKEUP + AF_UNSPEC = linux.AF_UNSPEC + IFF_UP = linux.IFF_UP + LINUX_CAPABILITY_VERSION_3 = linux.LINUX_CAPABILITY_VERSION_3 + CLONE_NEWNET = linux.CLONE_NEWNET + CLONE_NEWUSER = linux.CLONE_NEWUSER + CLONE_NEWNS = linux.CLONE_NEWNS + MOVE_MOUNT_F_EMPTY_PATH = linux.MOVE_MOUNT_F_EMPTY_PATH + AF_UNIX = linux.AF_UNIX + SOCK_STREAM = linux.SOCK_STREAM + SOCK_CLOEXEC = linux.SOCK_CLOEXEC + FSOPEN_CLOEXEC = linux.FSOPEN_CLOEXEC + FSMOUNT_CLOEXEC = linux.FSMOUNT_CLOEXEC + MSG_CMSG_CLOEXEC = linux.MSG_CMSG_CLOEXEC + SizeofInt = linux.SizeofInt ) type Statfs_t = linux.Statfs_t @@ -85,6 +99,9 @@ type EpollEvent = linux.EpollEvent type PerfEventAttr = linux.PerfEventAttr type Utsname = linux.Utsname type CPUSet = linux.CPUSet +type CapUserData = linux.CapUserData +type CapUserHeader = linux.CapUserHeader +type SysProcAttr = linux.SysProcAttr func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { return linux.Syscall(trap, a1, a2, a3) @@ -210,3 +227,67 @@ func SchedGetaffinity(pid int, set *CPUSet) error { func Auxv() ([][2]uintptr, error) { return linux.Auxv() } + +func Unshare(flag int) error { + return linux.Unshare(flag) +} + +func Setns(fd int, nstype int) error { + return linux.Setns(fd, nstype) +} + +func Capget(hdr *CapUserHeader, data *CapUserData) (err error) { + return linux.Capget(hdr, data) +} + +func Capset(hdr *CapUserHeader, data *CapUserData) (err error) { + return linux.Capset(hdr, data) +} + +func Sendmsg(fd int, p []byte, oob []byte, to linux.Sockaddr, flags int) (err error) { + return linux.Sendmsg(fd, p, oob, to, flags) +} + +func Fsopen(fsname string, flags int) (fd int, err error) { + return linux.Fsopen(fsname, flags) +} + +func FsconfigSetString(fd int, key string, value string) error { + return linux.FsconfigSetString(fd, key, value) +} + +func FsconfigCreate(fd int) (err error) { + return linux.FsconfigCreate(fd) +} + +func Fsmount(fd int, flags int, mountAttrs int) (fsfd int, err error) { + return linux.Fsmount(fd, flags, mountAttrs) +} + +func MoveMount(fromDirfd int, fromPathName string, toDirfd int, toPathName string, flags int) (err error) { + return linux.MoveMount(fromDirfd, fromPathName, toDirfd, toPathName, flags) +} + +func UnixRights(fds ...int) []byte { + return linux.UnixRights(fds...) +} + +func Recvmsg(fd int, p []byte, oob []byte, flags int) (n int, oobn int, recvflags int, from linux.Sockaddr, err error) { + return linux.Recvmsg(fd, p, oob, flags) +} + +func Socketpair(domain int, typ int, proto int) (fd [2]int, err error) { + return linux.Socketpair(domain, typ, proto) +} + +func CmsgSpace(datalen int) int { + return linux.CmsgSpace(datalen) +} + +func ParseSocketControlMessage(b []byte) ([]linux.SocketControlMessage, error) { + return linux.ParseSocketControlMessage(b) +} + +func ParseUnixRights(m *linux.SocketControlMessage) ([]int, error) { + return linux.ParseUnixRights(m) +} diff --git a/vendor/github.com/cilium/ebpf/internal/unix/types_other.go b/vendor/github.com/cilium/ebpf/internal/unix/types_other.go index f3f764ebe6..5d75582e2d 100644 --- a/vendor/github.com/cilium/ebpf/internal/unix/types_other.go +++ b/vendor/github.com/cilium/ebpf/internal/unix/types_other.go @@ -33,6 +33,8 @@ const ( EPOLL_CLOEXEC O_CLOEXEC O_NONBLOCK + O_DIRECTORY + O_RDONLY PROT_NONE PROT_READ PROT_WRITE @@ -73,6 +75,12 @@ const ( BPF_F_LOCK AF_UNSPEC IFF_UP + LINUX_CAPABILITY_VERSION_3 + SOCK_CLOEXEC + FSOPEN_CLOEXEC + FSMOUNT_CLOEXEC + MSG_CMSG_CLOEXEC + SizeofInt ) type Statfs_t struct { @@ -115,6 +123,17 @@ type Sigset_t struct { Val [4]uint64 } +type CapUserHeader struct { + Version uint32 + Pid int32 +} + +type CapUserData struct { + Effective uint32 + Permitted uint32 + Inheritable uint32 +} + func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) { return 0, 0, ENOTSUP } @@ -288,3 +307,11 @@ func SchedGetaffinity(pid int, set *CPUSet) error { func Auxv() ([][2]uintptr, error) { return nil, errNonLinux() } + +func Capget(hdr *CapUserHeader, data *CapUserData) (err error) { + return errNonLinux() +} + +func Capset(hdr *CapUserHeader, data *CapUserData) (err error) { + return errNonLinux() +} diff --git a/vendor/github.com/cilium/ebpf/link/cgroup.go b/vendor/github.com/cilium/ebpf/link/cgroup.go index 1f1416aa2b..64aea1ae3c 100644 --- a/vendor/github.com/cilium/ebpf/link/cgroup.go +++ b/vendor/github.com/cilium/ebpf/link/cgroup.go @@ -167,6 +167,10 @@ func (cg *progAttachCgroup) Unpin() error { return fmt.Errorf("can't unpin cgroup: %w", ErrNotSupported) } +func (cg *progAttachCgroup) Detach() error { + return fmt.Errorf("can't detach cgroup: %w", ErrNotSupported) +} + func (cg *progAttachCgroup) Info() (*Info, error) { return nil, fmt.Errorf("can't get cgroup info: %w", ErrNotSupported) } diff --git a/vendor/github.com/cilium/ebpf/link/kprobe.go b/vendor/github.com/cilium/ebpf/link/kprobe.go index 0912e0a082..8b27677f90 100644 --- a/vendor/github.com/cilium/ebpf/link/kprobe.go +++ b/vendor/github.com/cilium/ebpf/link/kprobe.go @@ -33,8 +33,9 @@ type KprobeOptions struct { // Increase the maximum number of concurrent invocations of a kretprobe. // Required when tracing some long running functions in the kernel. // - // Deprecated: this setting forces the use of an outdated kernel API and is not portable - // across kernel versions. + // Warning: this setting forces the use of an outdated kernel API and is + // not portable across kernel versions. On supported kernels, consider using + // fexit programs instead, as they don't have this MaxActive limitation. RetprobeMaxActive int // Prefix used for the event name if the kprobe must be attached using tracefs. // The group name will be formatted as `_`. diff --git a/vendor/github.com/cilium/ebpf/link/kprobe_multi.go b/vendor/github.com/cilium/ebpf/link/kprobe_multi.go index 3a2b06a241..b72ea95878 100644 --- a/vendor/github.com/cilium/ebpf/link/kprobe_multi.go +++ b/vendor/github.com/cilium/ebpf/link/kprobe_multi.go @@ -8,8 +8,7 @@ import ( "os" "github.com/cilium/ebpf" - "github.com/cilium/ebpf/asm" - "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/features" "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/unix" ) @@ -126,11 +125,11 @@ func kprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions, flags uint32) (Lin } if opts.Session { - if haveFeatErr := haveBPFLinkKprobeSession(); haveFeatErr != nil { + if haveFeatErr := features.HaveBPFLinkKprobeSession(); haveFeatErr != nil { return nil, haveFeatErr } } else { - if haveFeatErr := haveBPFLinkKprobeMulti(); haveFeatErr != nil { + if haveFeatErr := features.HaveBPFLinkKprobeMulti(); haveFeatErr != nil { return nil, haveFeatErr } } @@ -157,12 +156,30 @@ func (kml *kprobeMultiLink) Update(_ *ebpf.Program) error { func (kml *kprobeMultiLink) Info() (*Info, error) { var info sys.KprobeMultiLinkInfo if err := sys.ObjInfo(kml.fd, &info); err != nil { - return nil, fmt.Errorf("kprobe multi link info: %s", err) + return nil, fmt.Errorf("kprobe multi link info: %w", err) + } + var addrs = make([]uint64, info.Count) + var cookies = make([]uint64, info.Count) + info = sys.KprobeMultiLinkInfo{ + Addrs: sys.SlicePointer(addrs), + Cookies: sys.SlicePointer(cookies), + Count: uint32(len(addrs)), + } + if err := sys.ObjInfo(kml.fd, &info); err != nil { + return nil, fmt.Errorf("kprobe multi link info: %w", err) + } + if info.Addrs.IsNil() { + addrs = nil + } + if info.Cookies.IsNil() { + cookies = nil } extra := &KprobeMultiInfo{ - count: info.Count, - flags: info.Flags, - missed: info.Missed, + Count: info.Count, + Flags: info.Flags, + Missed: info.Missed, + addrs: addrs, + cookies: cookies, } return &Info{ @@ -172,85 +189,3 @@ func (kml *kprobeMultiLink) Info() (*Info, error) { extra, }, nil } - -var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", func() error { - prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ - Name: "probe_kpm_link", - Type: ebpf.Kprobe, - Instructions: asm.Instructions{ - asm.Mov.Imm(asm.R0, 0), - asm.Return(), - }, - AttachType: ebpf.AttachTraceKprobeMulti, - License: "MIT", - }) - if errors.Is(err, unix.E2BIG) { - // Kernel doesn't support AttachType field. - return internal.ErrNotSupported - } - if err != nil { - return err - } - defer prog.Close() - - fd, err := sys.LinkCreateKprobeMulti(&sys.LinkCreateKprobeMultiAttr{ - ProgFd: uint32(prog.FD()), - AttachType: sys.BPF_TRACE_KPROBE_MULTI, - Count: 1, - Syms: sys.NewStringSlicePointer([]string{"vprintk"}), - }) - switch { - case errors.Is(err, unix.EINVAL): - return internal.ErrNotSupported - // If CONFIG_FPROBE isn't set. - case errors.Is(err, unix.EOPNOTSUPP): - return internal.ErrNotSupported - case err != nil: - return err - } - - fd.Close() - - return nil -}, "5.18") - -var haveBPFLinkKprobeSession = internal.NewFeatureTest("bpf_link_kprobe_session", func() error { - prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ - Name: "probe_kps_link", - Type: ebpf.Kprobe, - Instructions: asm.Instructions{ - asm.Mov.Imm(asm.R0, 0), - asm.Return(), - }, - AttachType: ebpf.AttachTraceKprobeSession, - License: "MIT", - }) - if errors.Is(err, unix.E2BIG) { - // Kernel doesn't support AttachType field. - return internal.ErrNotSupported - } - if err != nil { - return err - } - defer prog.Close() - - fd, err := sys.LinkCreateKprobeMulti(&sys.LinkCreateKprobeMultiAttr{ - ProgFd: uint32(prog.FD()), - AttachType: sys.BPF_TRACE_KPROBE_SESSION, - Count: 1, - Syms: sys.NewStringSlicePointer([]string{"vprintk"}), - }) - switch { - case errors.Is(err, unix.EINVAL): - return internal.ErrNotSupported - // If CONFIG_FPROBE isn't set. - case errors.Is(err, unix.EOPNOTSUPP): - return internal.ErrNotSupported - case err != nil: - return err - } - - fd.Close() - - return nil -}, "6.10") diff --git a/vendor/github.com/cilium/ebpf/link/link.go b/vendor/github.com/cilium/ebpf/link/link.go index 425811e777..57b328c2eb 100644 --- a/vendor/github.com/cilium/ebpf/link/link.go +++ b/vendor/github.com/cilium/ebpf/link/link.go @@ -9,6 +9,7 @@ import ( "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/sys" + "github.com/cilium/ebpf/internal/unix" ) // Type is the kind of link. @@ -40,6 +41,11 @@ type Link interface { // not called. Close() error + // Detach the link from its corresponding attachment point. + // + // May return an error wrapping ErrNotSupported. + Detach() error + // Info returns metadata on a link. // // May return an error wrapping ErrNotSupported. @@ -49,13 +55,6 @@ type Link interface { isLink() } -// NewLinkFromFD creates a link from a raw fd. -// -// Deprecated: use [NewFromFD] instead. -func NewLinkFromFD(fd int) (Link, error) { - return NewFromFD(fd) -} - // NewFromFD creates a link from a raw fd. // // You should not use fd after calling this function. @@ -115,7 +114,7 @@ type Info struct { Type Type ID ID Program ebpf.ProgramID - extra interface{} + extra any } // RawLink is the low-level API to bpf_link. @@ -225,6 +224,24 @@ func (l *RawLink) UpdateArgs(opts RawLinkUpdateOptions) error { return nil } +// Detach the link from its corresponding attachment point. +func (l *RawLink) Detach() error { + attr := sys.LinkDetachAttr{ + LinkFd: l.fd.Uint(), + } + + err := sys.LinkDetach(&attr) + + switch { + case errors.Is(err, unix.EOPNOTSUPP): + return internal.ErrNotSupported + case err != nil: + return fmt.Errorf("detach link: %w", err) + default: + return nil + } +} + // Info returns metadata about the link. // // Linktype specific metadata is not included and can be retrieved diff --git a/vendor/github.com/cilium/ebpf/link/link_other.go b/vendor/github.com/cilium/ebpf/link/link_other.go index cd9452fd83..31904aaf5d 100644 --- a/vendor/github.com/cilium/ebpf/link/link_other.go +++ b/vendor/github.com/cilium/ebpf/link/link_other.go @@ -25,6 +25,7 @@ const ( UprobeMultiType = sys.BPF_LINK_TYPE_UPROBE_MULTI NetfilterType = sys.BPF_LINK_TYPE_NETFILTER NetkitType = sys.BPF_LINK_TYPE_NETKIT + StructOpsType = sys.BPF_LINK_TYPE_STRUCT_OPS ) // AttachRawLink creates a raw link. @@ -102,15 +103,17 @@ func wrapRawLink(raw *RawLink) (_ Link, err error) { return &netkitLink{*raw}, nil case XDPType: return &xdpLink{*raw}, nil + case StructOpsType: + return &structOpsLink{*raw}, nil default: return raw, nil } } type TracingInfo struct { - AttachType sys.AttachType - TargetObjId uint32 - TargetBtfId sys.TypeID + AttachType sys.AttachType + TargetObjectId uint32 + TargetBtfId sys.TypeID } type CgroupInfo struct { @@ -120,7 +123,7 @@ type CgroupInfo struct { } type NetNsInfo struct { - NetnsIno uint32 + NetnsInode uint32 AttachType sys.AttachType } @@ -134,10 +137,10 @@ type XDPInfo struct { } type NetfilterInfo struct { - Pf uint32 - Hooknum uint32 - Priority int32 - Flags uint32 + ProtocolFamily NetfilterProtocolFamily + Hook NetfilterInetHook + Priority int32 + Flags uint32 } type NetkitInfo struct { @@ -145,28 +148,98 @@ type NetkitInfo struct { AttachType sys.AttachType } +type RawTracepointInfo struct { + Name string +} + type KprobeMultiInfo struct { - count uint32 - flags uint32 - missed uint64 + // Count is the number of addresses hooked by the kprobe. + Count uint32 + Flags uint32 + Missed uint64 + addrs []uint64 + cookies []uint64 +} + +type KprobeMultiAddress struct { + Address uint64 + Cookie uint64 +} + +// Addresses are the addresses hooked by the kprobe. +func (kpm *KprobeMultiInfo) Addresses() ([]KprobeMultiAddress, bool) { + if kpm.addrs == nil || len(kpm.addrs) != len(kpm.cookies) { + return nil, false + } + addrs := make([]KprobeMultiAddress, len(kpm.addrs)) + for i := range kpm.addrs { + addrs[i] = KprobeMultiAddress{ + Address: kpm.addrs[i], + Cookie: kpm.cookies[i], + } + } + return addrs, true +} + +type UprobeMultiInfo struct { + Count uint32 + Flags uint32 + Missed uint64 + offsets []uint64 + cookies []uint64 + refCtrOffsets []uint64 + // File is the path that the file the uprobe was attached to + // had at creation time. + // + // However, due to various circumstances (differing mount namespaces, + // file replacement, ...), this path may not point to the same binary + // the uprobe was originally attached to. + File string + pid uint32 } -// AddressCount is the number of addresses hooked by the kprobe. -func (kpm *KprobeMultiInfo) AddressCount() (uint32, bool) { - return kpm.count, kpm.count > 0 +type UprobeMultiOffset struct { + Offset uint64 + Cookie uint64 + ReferenceCount uint64 } -func (kpm *KprobeMultiInfo) Flags() (uint32, bool) { - return kpm.flags, kpm.count > 0 +// Offsets returns the offsets that the uprobe was attached to along with the related cookies and ref counters. +func (umi *UprobeMultiInfo) Offsets() ([]UprobeMultiOffset, bool) { + if umi.offsets == nil || len(umi.cookies) != len(umi.offsets) || len(umi.refCtrOffsets) != len(umi.offsets) { + return nil, false + } + var adresses = make([]UprobeMultiOffset, len(umi.offsets)) + for i := range umi.offsets { + adresses[i] = UprobeMultiOffset{ + Offset: umi.offsets[i], + Cookie: umi.cookies[i], + ReferenceCount: umi.refCtrOffsets[i], + } + } + return adresses, true } -func (kpm *KprobeMultiInfo) Missed() (uint64, bool) { - return kpm.missed, kpm.count > 0 +// Pid returns the process ID that this uprobe is attached to. +// +// If it does not exist, the uprobe will trigger for all processes. +func (umi *UprobeMultiInfo) Pid() (uint32, bool) { + return umi.pid, umi.pid > 0 } +const ( + PerfEventUnspecified = sys.BPF_PERF_EVENT_UNSPEC + PerfEventUprobe = sys.BPF_PERF_EVENT_UPROBE + PerfEventUretprobe = sys.BPF_PERF_EVENT_URETPROBE + PerfEventKprobe = sys.BPF_PERF_EVENT_KPROBE + PerfEventKretprobe = sys.BPF_PERF_EVENT_KRETPROBE + PerfEventTracepoint = sys.BPF_PERF_EVENT_TRACEPOINT + PerfEventEvent = sys.BPF_PERF_EVENT_EVENT +) + type PerfEventInfo struct { Type sys.PerfEventType - extra interface{} + extra any } func (r *PerfEventInfo) Kprobe() *KprobeInfo { @@ -174,17 +247,50 @@ func (r *PerfEventInfo) Kprobe() *KprobeInfo { return e } +func (r *PerfEventInfo) Uprobe() *UprobeInfo { + e, _ := r.extra.(*UprobeInfo) + return e +} + +func (r *PerfEventInfo) Tracepoint() *TracepointInfo { + e, _ := r.extra.(*TracepointInfo) + return e +} + +func (r *PerfEventInfo) Event() *EventInfo { + e, _ := r.extra.(*EventInfo) + return e +} + type KprobeInfo struct { - address uint64 - missed uint64 + Address uint64 + Missed uint64 + Function string + Offset uint32 } -func (kp *KprobeInfo) Address() (uint64, bool) { - return kp.address, kp.address > 0 +type UprobeInfo struct { + // File is the path that the file the uprobe was attached to + // had at creation time. + // + // However, due to various circumstances (differing mount namespaces, + // file replacement, ...), this path may not point to the same binary + // the uprobe was originally attached to. + File string + Offset uint32 + Cookie uint64 + OffsetReferenceCount uint64 } -func (kp *KprobeInfo) Missed() (uint64, bool) { - return kp.missed, kp.address > 0 +type TracepointInfo struct { + Tracepoint string + Cookie uint64 +} + +type EventInfo struct { + Config uint64 + Type uint32 + Cookie uint64 } // Tracing returns tracing type-specific link info. @@ -251,6 +357,14 @@ func (r Info) KprobeMulti() *KprobeMultiInfo { return e } +// UprobeMulti returns uprobe-multi type-specific link info. +// +// Returns nil if the type-specific link info isn't available. +func (r Info) UprobeMulti() *UprobeMultiInfo { + e, _ := r.extra.(*UprobeMultiInfo) + return e +} + // PerfEvent returns perf-event type-specific link info. // // Returns nil if the type-specific link info isn't available. @@ -258,3 +372,11 @@ func (r Info) PerfEvent() *PerfEventInfo { e, _ := r.extra.(*PerfEventInfo) return e } + +// RawTracepoint returns raw-tracepoint type-specific link info. +// +// Returns nil if the type-specific link info isn't available. +func (r Info) RawTracepoint() *RawTracepointInfo { + e, _ := r.extra.(*RawTracepointInfo) + return e +} diff --git a/vendor/github.com/cilium/ebpf/link/netfilter.go b/vendor/github.com/cilium/ebpf/link/netfilter.go index 90e914c519..6b5177794e 100644 --- a/vendor/github.com/cilium/ebpf/link/netfilter.go +++ b/vendor/github.com/cilium/ebpf/link/netfilter.go @@ -13,13 +13,35 @@ const NetfilterIPDefrag NetfilterAttachFlags = 0 // Enable IP packet defragmenta type NetfilterAttachFlags uint32 +type NetfilterInetHook = sys.NetfilterInetHook + +const ( + NetfilterInetPreRouting = sys.NF_INET_PRE_ROUTING + NetfilterInetLocalIn = sys.NF_INET_LOCAL_IN + NetfilterInetForward = sys.NF_INET_FORWARD + NetfilterInetLocalOut = sys.NF_INET_LOCAL_OUT + NetfilterInetPostRouting = sys.NF_INET_POST_ROUTING +) + +type NetfilterProtocolFamily = sys.NetfilterProtocolFamily + +const ( + NetfilterProtoUnspec = sys.NFPROTO_UNSPEC + NetfilterProtoInet = sys.NFPROTO_INET // Inet applies to both IPv4 and IPv6 + NetfilterProtoIPv4 = sys.NFPROTO_IPV4 + NetfilterProtoARP = sys.NFPROTO_ARP + NetfilterProtoNetdev = sys.NFPROTO_NETDEV + NetfilterProtoBridge = sys.NFPROTO_BRIDGE + NetfilterProtoIPv6 = sys.NFPROTO_IPV6 +) + type NetfilterOptions struct { // Program must be a netfilter BPF program. Program *ebpf.Program // The protocol family. - ProtocolFamily uint32 - // The number of the hook you are interested in. - HookNumber uint32 + ProtocolFamily NetfilterProtocolFamily + // The netfilter hook to attach to. + Hook NetfilterInetHook // Priority within hook Priority int32 // Extra link flags @@ -51,8 +73,8 @@ func AttachNetfilter(opts NetfilterOptions) (Link, error) { ProgFd: uint32(opts.Program.FD()), AttachType: sys.BPF_NETFILTER, Flags: opts.Flags, - Pf: uint32(opts.ProtocolFamily), - Hooknum: uint32(opts.HookNumber), + Pf: opts.ProtocolFamily, + Hooknum: opts.Hook, Priority: opts.Priority, NetfilterFlags: uint32(opts.NetfilterFlags), } @@ -75,10 +97,10 @@ func (nf *netfilterLink) Info() (*Info, error) { return nil, fmt.Errorf("netfilter link info: %s", err) } extra := &NetfilterInfo{ - Pf: info.Pf, - Hooknum: info.Hooknum, - Priority: info.Priority, - Flags: info.Flags, + ProtocolFamily: info.Pf, + Hook: info.Hooknum, + Priority: info.Priority, + Flags: info.Flags, } return &Info{ diff --git a/vendor/github.com/cilium/ebpf/link/netns.go b/vendor/github.com/cilium/ebpf/link/netns.go index a9f7ee79c9..fd7d6c6c23 100644 --- a/vendor/github.com/cilium/ebpf/link/netns.go +++ b/vendor/github.com/cilium/ebpf/link/netns.go @@ -44,7 +44,7 @@ func (ns *NetNsLink) Info() (*Info, error) { return nil, fmt.Errorf("netns link info: %s", err) } extra := &NetNsInfo{ - NetnsIno: info.NetnsIno, + NetnsInode: info.NetnsIno, AttachType: info.AttachType, } diff --git a/vendor/github.com/cilium/ebpf/link/perf_event.go b/vendor/github.com/cilium/ebpf/link/perf_event.go index f1f5b84a9e..7938b9d9d8 100644 --- a/vendor/github.com/cilium/ebpf/link/perf_event.go +++ b/vendor/github.com/cilium/ebpf/link/perf_event.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "os" - "runtime" "unsafe" "github.com/cilium/ebpf" @@ -61,15 +60,11 @@ type perfEvent struct { func newPerfEvent(fd *sys.FD, event *tracefs.Event) *perfEvent { pe := &perfEvent{event, fd} - // Both event and fd have their own finalizer, but we want to - // guarantee that they are closed in a certain order. - runtime.SetFinalizer(pe, (*perfEvent).Close) return pe } func (pe *perfEvent) Close() error { - runtime.SetFinalizer(pe, nil) - + // We close the perf event before attempting to remove the tracefs event. if err := pe.fd.Close(); err != nil { return fmt.Errorf("closing perf event fd: %w", err) } @@ -137,22 +132,84 @@ func (pl *perfEventLink) PerfEvent() (*os.File, error) { return fd.File("perf-event") } +// queryInfoWithString queries object info that contains a string field. +// +// The passed stringField and stringLengthField must point to the string field +// and its length field inside the info struct respectively. +// +// It returns the queried string and fills in the passed info struct. +func queryInfoWithString(fd *sys.FD, info sys.Info, stringField *sys.TypedPointer[byte], stringLengthField *uint32) (string, error) { + // Query info to get the length + if err := sys.ObjInfo(fd, info); err != nil { + return "", err + } + + // The stringLengthField pointer points to a field inside info, so it is now populated. + var stringData = make([]byte, *stringLengthField) + *stringField = sys.SlicePointer(stringData) + + // Query info again to fill in the string. + // Since the stringField pointer points to a field inside info, + // the info now contains the pointer to our allocated stringData. + if err := sys.ObjInfo(fd, info); err != nil { + return "", fmt.Errorf("object info with string: %s", err) + } + + return unix.ByteSliceToString(stringData), nil +} + func (pl *perfEventLink) Info() (*Info, error) { var info sys.PerfEventLinkInfo if err := sys.ObjInfo(pl.fd, &info); err != nil { return nil, fmt.Errorf("perf event link info: %s", err) } - var extra2 interface{} + var extra2 any switch info.PerfEventType { - case sys.BPF_PERF_EVENT_KPROBE, sys.BPF_PERF_EVENT_KRETPROBE: + case PerfEventKprobe, PerfEventKretprobe: var kprobeInfo sys.KprobeLinkInfo - if err := sys.ObjInfo(pl.fd, &kprobeInfo); err != nil { + funcName, err := queryInfoWithString(pl.fd, &kprobeInfo, &kprobeInfo.FuncName, &kprobeInfo.NameLen) + if err != nil { return nil, fmt.Errorf("kprobe link info: %s", err) } extra2 = &KprobeInfo{ - address: kprobeInfo.Addr, - missed: kprobeInfo.Missed, + Address: kprobeInfo.Addr, + Missed: kprobeInfo.Missed, + Function: funcName, + Offset: kprobeInfo.Offset, + } + case PerfEventUprobe, PerfEventUretprobe: + var uprobeInfo sys.UprobeLinkInfo + fileName, err := queryInfoWithString(pl.fd, &uprobeInfo, &uprobeInfo.FileName, &uprobeInfo.NameLen) + if err != nil { + return nil, fmt.Errorf("uprobe link info: %s", err) + } + extra2 = &UprobeInfo{ + Offset: uprobeInfo.Offset, + Cookie: uprobeInfo.Cookie, + OffsetReferenceCount: uprobeInfo.RefCtrOffset, + File: fileName, + } + case PerfEventTracepoint: + var tracepointInfo sys.TracepointLinkInfo + tpName, err := queryInfoWithString(pl.fd, &tracepointInfo, &tracepointInfo.TpName, &tracepointInfo.NameLen) + if err != nil { + return nil, fmt.Errorf("perf event link info: %w", err) + } + extra2 = &TracepointInfo{ + Tracepoint: tpName, + Cookie: tracepointInfo.Cookie, + } + case PerfEventEvent: + var eventInfo sys.EventLinkInfo + err := sys.ObjInfo(pl.fd, &eventInfo) + if err != nil { + return nil, fmt.Errorf("trace point link info: %s", err) + } + extra2 = &EventInfo{ + Config: eventInfo.Config, + Type: eventInfo.EventType, + Cookie: eventInfo.Cookie, } } @@ -199,6 +256,10 @@ func (pi *perfEventIoctl) Unpin() error { return fmt.Errorf("perf event ioctl unpin: %w", ErrNotSupported) } +func (pi *perfEventIoctl) Detach() error { + return fmt.Errorf("perf event ioctl detach: %w", ErrNotSupported) +} + func (pi *perfEventIoctl) Info() (*Info, error) { return nil, fmt.Errorf("perf event ioctl info: %w", ErrNotSupported) } diff --git a/vendor/github.com/cilium/ebpf/link/program.go b/vendor/github.com/cilium/ebpf/link/program.go index dbd7a9727c..4928800bf9 100644 --- a/vendor/github.com/cilium/ebpf/link/program.go +++ b/vendor/github.com/cilium/ebpf/link/program.go @@ -50,12 +50,11 @@ func RawAttachProgram(opts RawAttachProgramOptions) error { return fmt.Errorf("attach program: %w", err) } - if flags == sys.BPF_F_REPLACE { - // Ensure that replacing a program works on old kernels. + attr.AttachFlags |= flags + if flags&sys.BPF_F_REPLACE != 0 { attr.ReplaceBpfFd = fdOrID } else { attr.RelativeFdOrId = fdOrID - attr.AttachFlags |= flags } } diff --git a/vendor/github.com/cilium/ebpf/link/query.go b/vendor/github.com/cilium/ebpf/link/query.go index eeca828116..c722ab27b3 100644 --- a/vendor/github.com/cilium/ebpf/link/query.go +++ b/vendor/github.com/cilium/ebpf/link/query.go @@ -4,6 +4,7 @@ package link import ( "fmt" + "slices" "github.com/cilium/ebpf" "github.com/cilium/ebpf/internal/sys" @@ -33,7 +34,12 @@ type QueryResult struct { // HaveLinkInfo returns true if the kernel supports querying link information // for a particular [ebpf.AttachType]. func (qr *QueryResult) HaveLinkInfo() bool { - return qr.Revision > 0 + return slices.ContainsFunc(qr.Programs, + func(ap AttachedProgram) bool { + _, ok := ap.LinkID() + return ok + }, + ) } type AttachedProgram struct { @@ -44,8 +50,7 @@ type AttachedProgram struct { // LinkID returns the ID associated with the program. // // Returns 0, false if the kernel doesn't support retrieving the ID or if the -// program wasn't attached via a link. See [QueryResult.HaveLinkInfo] if you -// need to tell the two apart. +// program wasn't attached via a link. func (ap *AttachedProgram) LinkID() (ID, bool) { return ap.linkID, ap.linkID != 0 } diff --git a/vendor/github.com/cilium/ebpf/link/raw_tracepoint.go b/vendor/github.com/cilium/ebpf/link/raw_tracepoint.go index 4be9c6a276..5e974e3de6 100644 --- a/vendor/github.com/cilium/ebpf/link/raw_tracepoint.go +++ b/vendor/github.com/cilium/ebpf/link/raw_tracepoint.go @@ -74,6 +74,10 @@ func (frt *simpleRawTracepoint) Unpin() error { return fmt.Errorf("unpin raw_tracepoint: %w", ErrNotSupported) } +func (frt *simpleRawTracepoint) Detach() error { + return fmt.Errorf("detach raw_tracepoint: %w", ErrNotSupported) +} + func (frt *simpleRawTracepoint) Info() (*Info, error) { return nil, fmt.Errorf("can't get raw_tracepoint info: %w", ErrNotSupported) } @@ -87,3 +91,19 @@ var _ Link = (*rawTracepoint)(nil) func (rt *rawTracepoint) Update(_ *ebpf.Program) error { return fmt.Errorf("update raw_tracepoint: %w", ErrNotSupported) } + +func (rt *rawTracepoint) Info() (*Info, error) { + var info sys.RawTracepointLinkInfo + name, err := queryInfoWithString(rt.fd, &info, &info.TpName, &info.TpNameLen) + if err != nil { + return nil, err + } + return &Info{ + info.Type, + info.Id, + ebpf.ProgramID(info.ProgId), + &RawTracepointInfo{ + Name: name, + }, + }, nil +} diff --git a/vendor/github.com/cilium/ebpf/link/struct_ops.go b/vendor/github.com/cilium/ebpf/link/struct_ops.go new file mode 100644 index 0000000000..d1b7f25174 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/link/struct_ops.go @@ -0,0 +1,51 @@ +package link + +import ( + "fmt" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/internal/sys" +) + +type structOpsLink struct { + RawLink +} + +func (*structOpsLink) Update(*ebpf.Program) error { + return fmt.Errorf("update struct_ops link: %w", ErrNotSupported) +} + +type StructOpsOptions struct { + Map *ebpf.Map +} + +// AttachStructOps attaches a struct_ops map (created from a ".struct_ops.link" +// section) to its kernel subsystem via a BPF link. +func AttachStructOps(opts StructOpsOptions) (Link, error) { + m := opts.Map + + if m == nil { + return nil, fmt.Errorf("map cannot be nil") + } + + if t := m.Type(); t != ebpf.StructOpsMap { + return nil, fmt.Errorf("can't attach non-struct_ops map") + } + + mapFD := m.FD() + if mapFD <= 0 { + return nil, fmt.Errorf("invalid map: %s", sys.ErrClosedFd) + } + + fd, err := sys.LinkCreate(&sys.LinkCreateAttr{ + // For struct_ops links, the mapFD must be passed as ProgFd. + ProgFd: uint32(mapFD), + AttachType: sys.AttachType(ebpf.AttachStructOps), + TargetFd: 0, + }) + if err != nil { + return nil, fmt.Errorf("attach StructOps: create link: %w", err) + } + + return &structOpsLink{RawLink{fd: fd}}, nil +} diff --git a/vendor/github.com/cilium/ebpf/link/tracing.go b/vendor/github.com/cilium/ebpf/link/tracing.go index b33b3dc0eb..d0e595e6f9 100644 --- a/vendor/github.com/cilium/ebpf/link/tracing.go +++ b/vendor/github.com/cilium/ebpf/link/tracing.go @@ -26,9 +26,9 @@ func (f *tracing) Info() (*Info, error) { return nil, fmt.Errorf("tracing link info: %s", err) } extra := &TracingInfo{ - TargetObjId: info.TargetObjId, - TargetBtfId: info.TargetBtfId, - AttachType: info.AttachType, + TargetObjectId: info.TargetObjId, + TargetBtfId: info.TargetBtfId, + AttachType: info.AttachType, } return &Info{ diff --git a/vendor/github.com/cilium/ebpf/link/uprobe.go b/vendor/github.com/cilium/ebpf/link/uprobe.go index d20997e9d8..83333c4ec7 100644 --- a/vendor/github.com/cilium/ebpf/link/uprobe.go +++ b/vendor/github.com/cilium/ebpf/link/uprobe.go @@ -38,10 +38,22 @@ var ( type Executable struct { // Path of the executable on the filesystem. path string - // Parsed ELF and dynamic symbols' cachedAddresses. - cachedAddresses map[string]uint64 - // Keep track of symbol table lazy load. - cacheAddressesOnce sync.Once + // Cached symbol values for all ELF and dynamic symbols. + // Before using this, lazyLoadSymbols() must be called. + cachedSymbols map[string]symbol + // cachedSymbolsOnce tracks the lazy load of cachedSymbols. + cachedSymbolsOnce sync.Once +} + +type symbol struct { + addr uint64 + size uint64 +} + +// contains returns true if the given address falls within the range +// covered by the symbol. +func (s symbol) contains(address uint64) bool { + return s.addr <= address && address < s.addr+s.size } // UprobeOptions defines additional parameters that will be used @@ -98,20 +110,13 @@ func OpenExecutable(path string) (*Executable, error) { return nil, fmt.Errorf("path cannot be empty") } - f, err := internal.OpenSafeELFFile(path) - if err != nil { - return nil, fmt.Errorf("parse ELF file: %w", err) - } - defer f.Close() - - if f.Type != elf.ET_EXEC && f.Type != elf.ET_DYN { - // ELF is not an executable or a shared object. - return nil, errors.New("the given file is not an executable or a shared object") + if _, err := os.Stat(path); err != nil { + return nil, fmt.Errorf("stat executable: %w", err) } return &Executable{ - path: path, - cachedAddresses: make(map[string]uint64), + path: path, + cachedSymbols: make(map[string]symbol), }, nil } @@ -155,22 +160,18 @@ func (ex *Executable) load(f *internal.SafeELFFile) error { } } - ex.cachedAddresses[s.Name] = address + ex.cachedSymbols[s.Name] = symbol{ + addr: address, + size: s.Size, + } } return nil } -// address calculates the address of a symbol in the executable. -// -// opts must not be nil. -func (ex *Executable) address(symbol string, address, offset uint64) (uint64, error) { - if address > 0 { - return address + offset, nil - } - +func (ex *Executable) lazyLoadSymbols() error { var err error - ex.cacheAddressesOnce.Do(func() { + ex.cachedSymbolsOnce.Do(func() { var f *internal.SafeELFFile f, err = internal.OpenSafeELFFile(ex.path) if err != nil { @@ -179,13 +180,30 @@ func (ex *Executable) address(symbol string, address, offset uint64) (uint64, er } defer f.Close() + if f.Type != elf.ET_EXEC && f.Type != elf.ET_DYN { + // ELF is not an executable or a shared object. + err = errors.New("the given file is not an executable or a shared object") + return + } err = ex.load(f) }) + return err +} + +// address calculates the address of a symbol in the executable. +// +// opts must not be nil. +func (ex *Executable) address(symbol string, address, offset uint64) (uint64, error) { + if address > 0 { + return address + offset, nil + } + + err := ex.lazyLoadSymbols() if err != nil { return 0, fmt.Errorf("lazy load symbols: %w", err) } - address, ok := ex.cachedAddresses[symbol] + sym, ok := ex.cachedSymbols[symbol] if !ok { return 0, fmt.Errorf("symbol %s: %w", symbol, ErrNoSymbol) } @@ -196,12 +214,39 @@ func (ex *Executable) address(symbol string, address, offset uint64) (uint64, er // // Since only offset values are stored and not elf.Symbol, if the value is 0, // assume it's an external symbol. - if address == 0 { + if sym.addr == 0 { return 0, fmt.Errorf("cannot resolve %s library call '%s': %w "+ "(consider providing UprobeOptions.Address)", ex.path, symbol, ErrNotSupported) } - return address + offset, nil + if offset >= sym.size { + return 0, fmt.Errorf("offset %d is out of range of symbol %s", offset, symbol) + } + + return sym.addr + offset, nil +} + +// SymbolOffset represents an offset within a symbol within a binary. +type SymbolOffset struct { + Symbol string + Offset uint64 +} + +// Symbol returns the SymbolOffset that the given address points to. +// This includes the symbol name and the offset within that symbol. +// +// If no symbol is found for the given address, ErrNoSymbol is returned. +func (ex *Executable) Symbol(address uint64) (SymbolOffset, error) { + if err := ex.lazyLoadSymbols(); err != nil { + return SymbolOffset{}, fmt.Errorf("lazy load symbols: %w", err) + } + + for name, symbol := range ex.cachedSymbols { + if symbol.contains(address) { + return SymbolOffset{name, address - symbol.addr}, nil + } + } + return SymbolOffset{}, ErrNoSymbol } // Uprobe attaches the given eBPF program to a perf event that fires when the @@ -211,12 +256,15 @@ func (ex *Executable) address(symbol string, address, offset uint64) (uint64, er // ex, _ = OpenExecutable("/bin/bash") // ex.Uprobe("main", prog, nil) // -// When using symbols which belongs to shared libraries, -// an offset must be provided via options: +// When tracing a location for which no ELF symbol is available, or a shared +// library symbol whose static address is zero, set UprobeOptions.Address to +// the absolute file offset and pass an empty symbol: // -// up, err := ex.Uprobe("main", prog, &UprobeOptions{Offset: 0x123}) +// up, err := ex.Uprobe("", prog, &UprobeOptions{Address: 0x123}) // -// Note: Setting the Offset field in the options supersedes the symbol's offset. +// Address bypasses symbol lookup. Offset, if set, is added to the resolved +// address (whether from symbol resolution or from Address); on its own it +// does not replace symbol resolution. // // Losing the reference to the resulting Link (up) will close the Uprobe // and prevent further execution of prog. The Link must be Closed during @@ -247,12 +295,15 @@ func (ex *Executable) Uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti // ex, _ = OpenExecutable("/bin/bash") // ex.Uretprobe("main", prog, nil) // -// When using symbols which belongs to shared libraries, -// an offset must be provided via options: +// When tracing a location for which no ELF symbol is available, or a shared +// library symbol whose static address is zero, set UprobeOptions.Address to +// the absolute file offset and pass an empty symbol: // -// up, err := ex.Uretprobe("main", prog, &UprobeOptions{Offset: 0x123}) +// up, err := ex.Uretprobe("", prog, &UprobeOptions{Address: 0x123}) // -// Note: Setting the Offset field in the options supersedes the symbol's offset. +// Address bypasses symbol lookup. Offset, if set, is added to the resolved +// address (whether from symbol resolution or from Address); on its own it +// does not replace symbol resolution. // // Losing the reference to the resulting Link (up) will close the Uprobe // and prevent further execution of prog. The Link must be Closed during diff --git a/vendor/github.com/cilium/ebpf/link/uprobe_multi.go b/vendor/github.com/cilium/ebpf/link/uprobe_multi.go index e34ad7168b..f017405aab 100644 --- a/vendor/github.com/cilium/ebpf/link/uprobe_multi.go +++ b/vendor/github.com/cilium/ebpf/link/uprobe_multi.go @@ -8,8 +8,7 @@ import ( "os" "github.com/cilium/ebpf" - "github.com/cilium/ebpf/asm" - "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/features" "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/unix" ) @@ -108,7 +107,7 @@ func (ex *Executable) uprobeMulti(symbols []string, prog *ebpf.Program, opts *Up } if err != nil { - if haveFeatErr := haveBPFLinkUprobeMulti(); haveFeatErr != nil { + if haveFeatErr := features.HaveBPFLinkUprobeMulti(); haveFeatErr != nil { return nil, haveFeatErr } return nil, err @@ -176,45 +175,54 @@ func (kml *uprobeMultiLink) Update(_ *ebpf.Program) error { return fmt.Errorf("update uprobe_multi: %w", ErrNotSupported) } -var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", func() error { - prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ - Name: "probe_upm_link", - Type: ebpf.Kprobe, - Instructions: asm.Instructions{ - asm.Mov.Imm(asm.R0, 0), - asm.Return(), - }, - AttachType: ebpf.AttachTraceUprobeMulti, - License: "MIT", - }) - if errors.Is(err, unix.E2BIG) { - // Kernel doesn't support AttachType field. - return internal.ErrNotSupported - } - if err != nil { - return err - } - defer prog.Close() - - // We try to create uprobe multi link on '/' path which results in - // error with -EBADF in case uprobe multi link is supported. - fd, err := sys.LinkCreateUprobeMulti(&sys.LinkCreateUprobeMultiAttr{ - ProgFd: uint32(prog.FD()), - AttachType: sys.BPF_TRACE_UPROBE_MULTI, - Path: sys.NewStringPointer("/"), - Offsets: sys.SlicePointer([]uint64{0}), - Count: 1, - }) - switch { - case errors.Is(err, unix.EBADF): - return nil - case errors.Is(err, unix.EINVAL): - return internal.ErrNotSupported - case err != nil: - return err - } - - // should not happen - fd.Close() - return errors.New("successfully attached uprobe_multi to /, kernel bug?") -}, "6.6") +func (kml *uprobeMultiLink) Info() (*Info, error) { + var info sys.UprobeMultiLinkInfo + if err := sys.ObjInfo(kml.fd, &info); err != nil { + return nil, fmt.Errorf("uprobe multi link info: %s", err) + } + var ( + path = make([]byte, info.PathSize) + refCtrOffsets = make([]uint64, info.Count) + addrs = make([]uint64, info.Count) + cookies = make([]uint64, info.Count) + ) + info = sys.UprobeMultiLinkInfo{ + Path: sys.SlicePointer(path), + PathSize: uint32(len(path)), + Offsets: sys.SlicePointer(addrs), + RefCtrOffsets: sys.SlicePointer(refCtrOffsets), + Cookies: sys.SlicePointer(cookies), + Count: uint32(len(addrs)), + } + if err := sys.ObjInfo(kml.fd, &info); err != nil { + return nil, fmt.Errorf("uprobe multi link info: %s", err) + } + if info.Path.IsNil() { + path = nil + } + if info.Cookies.IsNil() { + cookies = nil + } + if info.Offsets.IsNil() { + addrs = nil + } + if info.RefCtrOffsets.IsNil() { + refCtrOffsets = nil + } + extra := &UprobeMultiInfo{ + Count: info.Count, + Flags: info.Flags, + pid: info.Pid, + offsets: addrs, + cookies: cookies, + refCtrOffsets: refCtrOffsets, + File: unix.ByteSliceToString(path), + } + + return &Info{ + info.Type, + info.Id, + ebpf.ProgramID(info.ProgId), + extra, + }, nil +} diff --git a/vendor/github.com/cilium/ebpf/linker.go b/vendor/github.com/cilium/ebpf/linker.go index 0b966477f3..519c0aed0b 100644 --- a/vendor/github.com/cilium/ebpf/linker.go +++ b/vendor/github.com/cilium/ebpf/linker.go @@ -54,47 +54,6 @@ func (hs *handles) Close() error { return errors.Join(errs...) } -// splitSymbols splits insns into subsections delimited by Symbol Instructions. -// insns cannot be empty and must start with a Symbol Instruction. -// -// The resulting map is indexed by Symbol name. -func splitSymbols(insns asm.Instructions) (map[string]asm.Instructions, error) { - if len(insns) == 0 { - return nil, errors.New("insns is empty") - } - - currentSym := insns[0].Symbol() - if currentSym == "" { - return nil, errors.New("insns must start with a Symbol") - } - - start := 0 - progs := make(map[string]asm.Instructions) - for i, ins := range insns[1:] { - i := i + 1 - - sym := ins.Symbol() - if sym == "" { - continue - } - - // New symbol, flush the old one out. - progs[currentSym] = slices.Clone(insns[start:i]) - - if progs[sym] != nil { - return nil, fmt.Errorf("insns contains duplicate Symbol %s", sym) - } - currentSym = sym - start = i - } - - if tail := insns[start:]; len(tail) > 0 { - progs[currentSym] = slices.Clone(tail) - } - - return progs, nil -} - // The linker is responsible for resolving bpf-to-bpf calls between programs // within an ELF. Each BPF program must be a self-contained binary blob, // so when an instruction in one ELF program section wants to jump to @@ -122,9 +81,8 @@ func hasFunctionReferences(insns asm.Instructions) bool { // applyRelocations collects and applies any CO-RE relocations in insns. // -// Passing a nil target will relocate against the running kernel. insns are -// modified in place. -func applyRelocations(insns asm.Instructions, bo binary.ByteOrder, b *btf.Builder, c *btf.Cache) error { +// insns are modified in place. +func applyRelocations(insns asm.Instructions, bo binary.ByteOrder, b *btf.Builder, c *btf.Cache, kernelOverride *btf.Spec, extraTargets []*btf.Spec) error { var relos []*btf.CORERelocation var reloInsns []*asm.Instruction iter := insns.Iterate() @@ -143,30 +101,39 @@ func applyRelocations(insns asm.Instructions, bo binary.ByteOrder, b *btf.Builde bo = internal.NativeEndian } - kernelTarget, err := c.Kernel() - if err != nil { - return fmt.Errorf("load kernel spec: %w", err) - } + var targets []*btf.Spec + if kernelOverride == nil { + kernel, err := c.Kernel() + if err != nil { + return fmt.Errorf("load kernel spec: %w", err) + } - modules, err := c.Modules() - // Ignore ErrNotExists to cater to kernels which have CONFIG_DEBUG_INFO_BTF_MODULES - // or CONFIG_DEBUG_INFO_BTF disabled. - if err != nil && !errors.Is(err, fs.ErrNotExist) { - return err - } + modules, err := c.Modules() + // Ignore ErrNotExists to cater to kernels which have CONFIG_DEBUG_INFO_BTF_MODULES + // or CONFIG_DEBUG_INFO_BTF disabled. + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return err + } - targets := make([]*btf.Spec, 0, 1+len(modules)) - targets = append(targets, kernelTarget) + targets = make([]*btf.Spec, 0, 1+len(modules)+len(extraTargets)) + targets = append(targets, kernel) - for _, kmod := range modules { - spec, err := c.Module(kmod) - if err != nil { - return fmt.Errorf("load BTF for kmod %s: %w", kmod, err) - } + for _, kmod := range modules { + spec, err := c.Module(kmod) + if err != nil { + return fmt.Errorf("load BTF for kmod %s: %w", kmod, err) + } - targets = append(targets, spec) + targets = append(targets, spec) + } + } else { + // We expect kernelOverride to contain the merged types + // of vmlinux and kernel modules, as distributed by btfhub. + targets = []*btf.Spec{kernelOverride} } + targets = append(targets, extraTargets...) + fixups, err := btf.CORERelocate(relos, targets, bo, b.Add) if err != nil { return err @@ -287,7 +254,7 @@ const kfuncCallPoisonBase = 0xdedc0de // fixupKfuncs loops over all instructions in search for kfunc calls. // If at least one is found, the current kernels BTF and module BTFis are searched to set Instruction.Constant // and Instruction.Offset to the correct values. -func fixupKfuncs(insns asm.Instructions) (_ handles, err error) { +func fixupKfuncs(insns asm.Instructions, cache *btf.Cache) (_ handles, err error) { closeOnError := func(c io.Closer) { if err != nil { c.Close() @@ -308,7 +275,7 @@ fixups: // Only load kernel BTF if we found at least one kfunc call. kernelSpec can be // nil if the kernel does not have BTF, in which case we poison all kfunc // calls. - kernelSpec, err := btf.LoadKernelSpec() + _, err = cache.Kernel() // ErrNotSupportedOnOS wraps ErrNotSupported, check for it first. if errors.Is(err, internal.ErrNotSupportedOnOS) { return nil, fmt.Errorf("kfuncs are not supported on this platform: %w", err) @@ -338,9 +305,10 @@ fixups: return nil, fmt.Errorf("kfuncMetaKey doesn't contain kfuncMeta") } - // findTargetInKernel returns btf.ErrNotFound if the input btf.Spec is nil. + // findTargetInKernel returns [btf.ErrNotFound] if the target can't be found + // or if BTF is not enabled. target := btf.Type((*btf.Func)(nil)) - spec, module, err := findTargetInKernel(kernelSpec, kfm.Func.Name, &target) + spec, module, err := findTargetInKernel(kfm.Func.Name, &target, cache) if errors.Is(err, btf.ErrNotFound) { if kfm.Binding == elf.STB_WEAK { if ins.IsKfuncCall() { @@ -485,7 +453,13 @@ func resolveKconfigReferences(insns asm.Instructions) (_ *Map, err error) { } func resolveKsymReferences(insns asm.Instructions) error { - var missing []string + type fixup struct { + *asm.Instruction + *ksymMeta + } + + var symbols map[string]uint64 + var fixups []fixup iter := insns.Iterate() for iter.Next() { @@ -495,28 +469,47 @@ func resolveKsymReferences(insns asm.Instructions) error { continue } - addr, err := kallsyms.Address(meta.Name) - if err != nil { - return fmt.Errorf("resolve ksym %s: %w", meta.Name, err) - } - if addr != 0 { - ins.Constant = int64(addr) - continue + if symbols == nil { + symbols = make(map[string]uint64) } - if meta.Binding == elf.STB_WEAK { - // A weak ksym variable in eBPF C means its resolution is optional. - // Set a zero constant explicitly for clarity. - ins.Constant = 0 + symbols[meta.Name] = 0 + fixups = append(fixups, fixup{ + iter.Ins, meta, + }) + } + + if len(symbols) == 0 { + return nil + } + + err := kallsyms.AssignAddresses(symbols) + // Tolerate ErrRestrictedKernel during initial lookup, user may have all weak + // ksyms and a fallback path. + if err != nil && !errors.Is(err, ErrRestrictedKernel) { + return fmt.Errorf("resolve ksyms: %w", err) + } + + var missing []string + for _, fixup := range fixups { + addr := symbols[fixup.Name] + // A weak ksym variable in eBPF C means its resolution is optional. + if addr == 0 && fixup.Binding != elf.STB_WEAK { + if !slices.Contains(missing, fixup.Name) { + missing = append(missing, fixup.Name) + } continue } - if !slices.Contains(missing, meta.Name) { - missing = append(missing, meta.Name) - } + fixup.Constant = int64(addr) } if len(missing) > 0 { + if err != nil { + // Program contains required ksyms, return the error from above. + return fmt.Errorf("resolve required ksyms: %s: %w", strings.Join(missing, ","), err) + } + return fmt.Errorf("kernel is missing symbol: %s", strings.Join(missing, ",")) } diff --git a/vendor/github.com/cilium/ebpf/map.go b/vendor/github.com/cilium/ebpf/map.go index 55974b4ebc..884fbebf0b 100644 --- a/vendor/github.com/cilium/ebpf/map.go +++ b/vendor/github.com/cilium/ebpf/map.go @@ -29,7 +29,6 @@ var ( ErrKeyExist = errors.New("key already exists") ErrIterationAborted = errors.New("iteration aborted") ErrMapIncompatible = errors.New("map spec is incompatible with existing map") - errMapNoBTFValue = errors.New("map spec does not contain a BTF Value") // pre-allocating these errors here since they may get called in hot code paths // and cause unnecessary memory allocations @@ -78,6 +77,11 @@ type MapSpec struct { // InnerMap is used as a template for ArrayOfMaps and HashOfMaps InnerMap *MapSpec + // MapExtra is an opaque field whose meaning is map-specific. + // + // Available from 5.16. + MapExtra uint64 + // Extra trailing bytes found in the ELF map definition when using structs // larger than libbpf's bpf_map_def. nil if no trailing bytes were present. // Must be nil or empty before instantiating the MapSpec into a Map. @@ -182,28 +186,71 @@ func (spec *MapSpec) fixupMagicFields() (*MapSpec, error) { return spec, nil } -// dataSection returns the contents and BTF Datasec descriptor of the spec. -func (ms *MapSpec) dataSection() ([]byte, *btf.Datasec, error) { - if ms.Value == nil { - return nil, nil, errMapNoBTFValue +// dataSection returns the contents of a datasec if the MapSpec represents one. +func (ms *MapSpec) dataSection() ([]byte, error) { + if n := len(ms.Contents); n != 1 { + return nil, fmt.Errorf("expected one key, found %d", n) + } + + kv := ms.Contents[0] + if key, ok := ms.Contents[0].Key.(uint32); !ok || key != 0 { + return nil, fmt.Errorf("expected contents to have key 0") } - ds, ok := ms.Value.(*btf.Datasec) + value, ok := kv.Value.([]byte) if !ok { - return nil, nil, fmt.Errorf("map value BTF is a %T, not a *btf.Datasec", ms.Value) + return nil, fmt.Errorf("value at first map key is %T, not []byte", kv.Value) } - if n := len(ms.Contents); n != 1 { - return nil, nil, fmt.Errorf("expected one key, found %d", n) + return value, nil +} + +// updateDataSection copies the values of variables into MapSpec.Contents[0].Value. +// +// Only variables declared in sectionName will be updated. +func (ms *MapSpec) updateDataSection(vars map[string]*VariableSpec, sectionName string) error { + var specs []*VariableSpec + for _, vs := range vars { + if vs.SectionName != sectionName { + continue + } + + specs = append(specs, vs) } - kv := ms.Contents[0] - value, ok := kv.Value.([]byte) - if !ok { - return nil, nil, fmt.Errorf("value at first map key is %T, not []byte", kv.Value) + if len(specs) == 0 { + return nil + } + + data, err := ms.dataSection() + if err != nil { + return err + } + + // Do not modify the original data slice, ms.Contents is a shallow copy. + data = slices.Clone(data) + + slices.SortFunc(specs, func(a, b *VariableSpec) int { + return int(int64(a.Offset) - int64(b.Offset)) + }) + + offset := uint32(0) + for _, v := range specs { + if v.Offset < offset { + return fmt.Errorf("variable %s (offset %d) overlaps with previous variable (offset %d)", v.Name, v.Offset, offset) + } + + end := v.Offset + v.Size() + if int(end) > len(data) { + return fmt.Errorf("variable %s exceeds map size", v.Name) + } + + copy(data[v.Offset:end], v.Value) + offset = end } - return value, ds, nil + ms.Contents = []MapKV{{Key: uint32(0), Value: data}} + return nil } func (ms *MapSpec) readOnly() bool { @@ -216,8 +263,8 @@ func (ms *MapSpec) writeOnly() bool { // MapKV is used to initialize the contents of a Map. type MapKV struct { - Key interface{} - Value interface{} + Key any + Value any } // Compatible returns nil if an existing map may be used instead of creating @@ -265,13 +312,15 @@ func (ms *MapSpec) Compatible(m *Map) error { // Map represents a Map file descriptor. // -// It is not safe to close a map which is used by other goroutines. +// It is not safe to close a Map which is used by other goroutines. // -// Methods which take interface{} arguments by default encode -// them using binary.Read/Write in the machine's native endianness. +// Map operations taking `any` arguments are encoded using [sysenc.Marshal], +// which is zero-copy for fixed-size types without padding as well as slices of +// bytes, but may involve allocations for other types. See its documentation for +// details. // -// Implement encoding.BinaryMarshaler or encoding.BinaryUnmarshaler -// if you require custom encoding. +// Implement encoding.BinaryMarshaler or encoding.BinaryUnmarshaler if you +// require custom encoding or decoding. type Map struct { name string fd *sys.FD @@ -329,7 +378,7 @@ func NewMap(spec *MapSpec) (*Map, error) { // // May return an error wrapping ErrMapIncompatible. func NewMapWithOptions(spec *MapSpec, opts MapOptions) (*Map, error) { - m, err := newMapWithOptions(spec, opts) + m, err := newMapWithOptions(spec, opts, btf.NewCache()) if err != nil { return nil, fmt.Errorf("creating map: %w", err) } @@ -342,7 +391,7 @@ func NewMapWithOptions(spec *MapSpec, opts MapOptions) (*Map, error) { return m, nil } -func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) { +func newMapWithOptions(spec *MapSpec, opts MapOptions, c *btf.Cache) (_ *Map, err error) { closeOnError := func(c io.Closer) { if err != nil { c.Close() @@ -392,7 +441,7 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) { return nil, errors.New("inner maps cannot be pinned") } - template, err := spec.InnerMap.createMap(nil) + template, err := spec.InnerMap.createMap(nil, c) if err != nil { return nil, fmt.Errorf("inner map: %w", err) } @@ -404,7 +453,7 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) { innerFd = template.fd } - m, err := spec.createMap(innerFd) + m, err := spec.createMap(innerFd, c) if err != nil { return nil, err } @@ -499,7 +548,7 @@ func (m *Map) memorySize() (int, error) { // createMap validates the spec's properties and creates the map in the kernel // using the given opts. It does not populate or freeze the map. -func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) { +func (spec *MapSpec) createMap(inner *sys.FD, c *btf.Cache) (_ *Map, err error) { closeOnError := func(closer io.Closer) { if err != nil { closer.Close() @@ -534,6 +583,7 @@ func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) { MaxEntries: spec.MaxEntries, MapFlags: spec.Flags, NumaNode: spec.NumaNode, + MapExtra: spec.MapExtra, } if inner != nil { @@ -554,6 +604,42 @@ func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) { attr.BtfKeyTypeId = keyTypeID attr.BtfValueTypeId = valueTypeID } + + if spec.Type == StructOpsMap { + if handle == nil { + return nil, fmt.Errorf("struct_ops requires BTF") + } + + localValue, ok := btf.As[*btf.Struct](spec.Value) + if !ok { + return nil, fmt.Errorf("struct_ops: value must be struct") + } + + targetValue, targetID, module, err := structOpsFindTarget(localValue, c) + if err != nil { + return nil, fmt.Errorf("struct_ops: %w", err) + } + defer module.Close() + + spec = spec.Copy() + spec.ValueSize = targetValue.Size + + attr.ValueSize = targetValue.Size + attr.BtfVmlinuxValueTypeId = targetID + + if module != nil { + // BPF_F_VTYPE_BTF_OBJ_FD is required if the type comes from a module + attr.MapFlags |= sys.BPF_F_VTYPE_BTF_OBJ_FD + // set FD for the kernel module + attr.ValueTypeBtfObjFd = int32(module.FD()) + } + + // StructOpsMap forbids passing BtfKeyTypeId or BtfValueTypeId, but + // requires BtfFd. Do the simple thing and just zero out the fields. + // See https://github.com/torvalds/linux/blob/9b332cece987ee1790b2ed4c989e28162fa47860/kernel/bpf/syscall.c#L1382-L1384 + attr.BtfKeyTypeId = 0 + attr.BtfValueTypeId = 0 + } } fd, err := sys.MapCreate(&attr) @@ -585,11 +671,16 @@ func handleMapCreateError(attr sys.MapCreateAttr, spec *MapSpec, err error) erro return err } + if errors.Is(err, sys.ErrTokenCapabilities) { + return fmt.Errorf("map create: %w", err) + } + if errors.Is(err, unix.EPERM) { return fmt.Errorf("map create: %w (MEMLOCK may be too low, consider rlimit.RemoveMemlock)", err) } + if errors.Is(err, unix.EINVAL) { - if spec.MaxEntries == 0 { + if spec.MaxEntries == 0 && !spec.Type.mustHaveZeroMaxEntries() { return fmt.Errorf("map create: %w (MaxEntries may be incorrectly set to zero)", err) } if spec.Type == UnspecifiedMap { @@ -743,7 +834,7 @@ const LookupLock MapLookupFlags = sys.BPF_F_LOCK // and *valueOut is not nil. // // Returns an error if the key doesn't exist, see ErrKeyNotExist. -func (m *Map) Lookup(key, valueOut interface{}) error { +func (m *Map) Lookup(key, valueOut any) error { return m.LookupWithFlags(key, valueOut, 0) } @@ -757,7 +848,7 @@ func (m *Map) Lookup(key, valueOut interface{}) error { // and *valueOut is not nil. // // Returns an error if the key doesn't exist, see ErrKeyNotExist. -func (m *Map) LookupWithFlags(key, valueOut interface{}, flags MapLookupFlags) error { +func (m *Map) LookupWithFlags(key, valueOut any, flags MapLookupFlags) error { if m.typ.hasPerCPUValue() { return m.lookupPerCPU(key, valueOut, flags) } @@ -773,7 +864,7 @@ func (m *Map) LookupWithFlags(key, valueOut interface{}, flags MapLookupFlags) e // LookupAndDelete retrieves and deletes a value from a Map. // // Returns ErrKeyNotExist if the key doesn't exist. -func (m *Map) LookupAndDelete(key, valueOut interface{}) error { +func (m *Map) LookupAndDelete(key, valueOut any) error { return m.LookupAndDeleteWithFlags(key, valueOut, 0) } @@ -784,7 +875,7 @@ func (m *Map) LookupAndDelete(key, valueOut interface{}) error { // contain a spinlock. // // Returns ErrKeyNotExist if the key doesn't exist. -func (m *Map) LookupAndDeleteWithFlags(key, valueOut interface{}, flags MapLookupFlags) error { +func (m *Map) LookupAndDeleteWithFlags(key, valueOut any, flags MapLookupFlags) error { if m.typ.hasPerCPUValue() { return m.lookupAndDeletePerCPU(key, valueOut, flags) } @@ -799,7 +890,7 @@ func (m *Map) LookupAndDeleteWithFlags(key, valueOut interface{}, flags MapLooku // LookupBytes gets a value from Map. // // Returns a nil value if a key doesn't exist. -func (m *Map) LookupBytes(key interface{}) ([]byte, error) { +func (m *Map) LookupBytes(key any) ([]byte, error) { valueBytes := make([]byte, m.fullValueSize) valuePtr := sys.UnsafeSlicePointer(valueBytes) @@ -823,7 +914,7 @@ func (m *Map) lookupPerCPU(key, valueOut any, flags MapLookupFlags) error { return unmarshalPerCPUValue(slice, int(m.valueSize), valueBytes) } -func (m *Map) lookup(key interface{}, valueOut sys.Pointer, flags MapLookupFlags) error { +func (m *Map) lookup(key any, valueOut sys.Pointer, flags MapLookupFlags) error { keyPtr, err := m.marshalKey(key) if err != nil { return fmt.Errorf("can't marshal key: %w", err) @@ -859,6 +950,10 @@ func (m *Map) lookupAndDeletePerCPU(key, valueOut any, flags MapLookupFlags) err // ensurePerCPUSlice allocates a slice for a per-CPU value if necessary. func ensurePerCPUSlice(sliceOrPtr any) (any, error) { + if err := internal.IsNilPointer(sliceOrPtr); err != nil { + return nil, fmt.Errorf("per-cpu value: %w", err) + } + sliceOrPtrType := reflect.TypeOf(sliceOrPtr) if sliceOrPtrType.Kind() == reflect.Slice { // The target is a slice, the caller is responsible for ensuring that @@ -867,7 +962,7 @@ func ensurePerCPUSlice(sliceOrPtr any) (any, error) { } slicePtrType := sliceOrPtrType - if slicePtrType.Kind() != reflect.Ptr || slicePtrType.Elem().Kind() != reflect.Slice { + if slicePtrType.Kind() != reflect.Pointer || slicePtrType.Elem().Kind() != reflect.Slice { return nil, fmt.Errorf("per-cpu value requires a slice or a pointer to slice") } @@ -880,14 +975,14 @@ func ensurePerCPUSlice(sliceOrPtr any) (any, error) { slice := reflect.MakeSlice(sliceType, possibleCPUs, possibleCPUs) sliceElemType := sliceType.Elem() - sliceElemIsPointer := sliceElemType.Kind() == reflect.Ptr + sliceElemIsPointer := sliceElemType.Kind() == reflect.Pointer reflect.ValueOf(sliceOrPtr).Elem().Set(slice) if !sliceElemIsPointer { return slice.Interface(), nil } sliceElemType = sliceElemType.Elem() - for i := 0; i < possibleCPUs; i++ { + for i := range possibleCPUs { newElem := reflect.New(sliceElemType) slice.Index(i).Set(newElem) } @@ -934,7 +1029,7 @@ const ( // Put replaces or creates a value in map. // // It is equivalent to calling Update with UpdateAny. -func (m *Map) Put(key, value interface{}) error { +func (m *Map) Put(key, value any) error { return m.Update(key, value, UpdateAny) } @@ -984,7 +1079,7 @@ func (m *Map) update(key any, valuePtr sys.Pointer, flags MapUpdateFlags) error // Delete removes a value. // // Returns ErrKeyNotExist if the key does not exist. -func (m *Map) Delete(key interface{}) error { +func (m *Map) Delete(key any) error { keyPtr, err := m.marshalKey(key) if err != nil { return fmt.Errorf("can't marshal key: %w", err) @@ -1006,7 +1101,7 @@ func (m *Map) Delete(key interface{}) error { // See NextKeyBytes for details. // // Returns ErrKeyNotExist if there is no next key. -func (m *Map) NextKey(key, nextKeyOut interface{}) error { +func (m *Map) NextKey(key, nextKeyOut any) error { nextKeyBytes := makeMapSyscallOutput(nextKeyOut, int(m.keySize)) if err := m.nextKey(key, nextKeyBytes.Pointer()); err != nil { @@ -1026,7 +1121,7 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error { // Use Iterate if you want to traverse all entries in the map. // // Returns nil if there are no more keys. -func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) { +func (m *Map) NextKeyBytes(key any) ([]byte, error) { nextKey := make([]byte, m.keySize) nextKeyPtr := sys.UnsafeSlicePointer(nextKey) @@ -1038,7 +1133,7 @@ func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) { return nextKey, err } -func (m *Map) nextKey(key interface{}, nextKeyOut sys.Pointer) error { +func (m *Map) nextKey(key any, nextKeyOut sys.Pointer) error { var ( keyPtr sys.Pointer err error @@ -1098,7 +1193,7 @@ func (m *Map) guessNonExistentKey() ([]byte, error) { randKey := make([]byte, int(m.keySize)) - for i := 0; i < 4; i++ { + for i := range 4 { switch i { // For hash maps, the 0 key is less likely to be occupied. They're often // used for storing data related to pointers, and their access pattern is @@ -1147,7 +1242,7 @@ func (m *Map) guessNonExistentKey() ([]byte, error) { // ErrKeyNotExist is returned when the batch lookup has reached // the end of all possible results, even when partial results // are returned. It should be used to evaluate when lookup is "done". -func (m *Map) BatchLookup(cursor *MapBatchCursor, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) { +func (m *Map) BatchLookup(cursor *MapBatchCursor, keysOut, valuesOut any, opts *BatchOptions) (int, error) { n, err := m.batchLookup(sys.BPF_MAP_LOOKUP_BATCH, cursor, keysOut, valuesOut, opts) if err != nil { return n, fmt.Errorf("map batch lookup: %w", err) @@ -1171,7 +1266,7 @@ func (m *Map) BatchLookup(cursor *MapBatchCursor, keysOut, valuesOut interface{} // ErrKeyNotExist is returned when the batch lookup has reached // the end of all possible results, even when partial results // are returned. It should be used to evaluate when lookup is "done". -func (m *Map) BatchLookupAndDelete(cursor *MapBatchCursor, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) { +func (m *Map) BatchLookupAndDelete(cursor *MapBatchCursor, keysOut, valuesOut any, opts *BatchOptions) (int, error) { n, err := m.batchLookup(sys.BPF_MAP_LOOKUP_AND_DELETE_BATCH, cursor, keysOut, valuesOut, opts) if err != nil { return n, fmt.Errorf("map batch lookup and delete: %w", err) @@ -1185,7 +1280,7 @@ type MapBatchCursor struct { opaque []byte } -func (m *Map) batchLookup(cmd sys.Cmd, cursor *MapBatchCursor, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) { +func (m *Map) batchLookup(cmd sys.Cmd, cursor *MapBatchCursor, keysOut, valuesOut any, opts *BatchOptions) (int, error) { if m.typ.hasPerCPUValue() { return m.batchLookupPerCPU(cmd, cursor, keysOut, valuesOut, opts) } @@ -1214,23 +1309,24 @@ func (m *Map) batchLookup(cmd sys.Cmd, cursor *MapBatchCursor, keysOut, valuesOu return n, sysErr } -func (m *Map) batchLookupPerCPU(cmd sys.Cmd, cursor *MapBatchCursor, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) { +func (m *Map) batchLookupPerCPU(cmd sys.Cmd, cursor *MapBatchCursor, keysOut, valuesOut any, opts *BatchOptions) (int, error) { count, err := sliceLen(keysOut) if err != nil { return 0, fmt.Errorf("keys: %w", err) } - valueBuf := make([]byte, count*int(m.fullValueSize)) - valuePtr := sys.UnsafeSlicePointer(valueBuf) + valueBuf := sysenc.SyscallOutput(valuesOut, count*int(m.fullValueSize)) - n, sysErr := m.batchLookupCmd(cmd, cursor, count, keysOut, valuePtr, opts) + n, sysErr := m.batchLookupCmd(cmd, cursor, count, keysOut, valueBuf.Pointer(), opts) if sysErr != nil && !errors.Is(sysErr, unix.ENOENT) { return 0, sysErr } - err = unmarshalBatchPerCPUValue(valuesOut, count, int(m.valueSize), valueBuf) - if err != nil { - return 0, err + if bytesBuf := valueBuf.Bytes(); bytesBuf != nil { + err = unmarshalBatchPerCPUValue(valuesOut, count, int(m.valueSize), bytesBuf) + if err != nil { + return 0, err + } } return n, sysErr @@ -1259,10 +1355,6 @@ func (m *Map) batchLookupCmd(cmd sys.Cmd, cursor *MapBatchCursor, count int, key return 0, errors.New("a cursor may not be reused across maps") } - if err := haveBatchAPI(); err != nil { - return 0, err - } - keyBuf := sysenc.SyscallOutput(keysOut, count*int(m.keySize)) attr := sys.MapLookupBatchAttr{ @@ -1282,6 +1374,9 @@ func (m *Map) batchLookupCmd(cmd sys.Cmd, cursor *MapBatchCursor, count int, key _, sysErr := sys.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) sysErr = wrapMapError(sysErr) if sysErr != nil && !errors.Is(sysErr, unix.ENOENT) { + if haveFeatErr := haveBatchAPI(); haveFeatErr != nil { + return 0, haveFeatErr + } return 0, sysErr } @@ -1296,7 +1391,7 @@ func (m *Map) batchLookupCmd(cmd sys.Cmd, cursor *MapBatchCursor, count int, key // simultaneously. // "keys" and "values" must be of type slice, a pointer // to a slice or buffer will not work. -func (m *Map) BatchUpdate(keys, values interface{}, opts *BatchOptions) (int, error) { +func (m *Map) BatchUpdate(keys, values any, opts *BatchOptions) (int, error) { if m.typ.hasPerCPUValue() { return m.batchUpdatePerCPU(keys, values, opts) } @@ -1358,7 +1453,7 @@ func (m *Map) batchUpdatePerCPU(keys, values any, opts *BatchOptions) (int, erro // BatchDelete batch deletes entries in the map by keys. // "keys" must be of type slice, a pointer to a slice or buffer will not work. -func (m *Map) BatchDelete(keys interface{}, opts *BatchOptions) (int, error) { +func (m *Map) BatchDelete(keys any, opts *BatchOptions) (int, error) { count, err := sliceLen(keys) if err != nil { return 0, fmt.Errorf("keys: %w", err) @@ -1538,7 +1633,7 @@ func (m *Map) finalize(spec *MapSpec) error { return nil } -func (m *Map) marshalKey(data interface{}) (sys.Pointer, error) { +func (m *Map) marshalKey(data any) (sys.Pointer, error) { if data == nil { if m.keySize == 0 { // Queues have a key length of zero, so passing nil here is valid. @@ -1550,7 +1645,7 @@ func (m *Map) marshalKey(data interface{}) (sys.Pointer, error) { return marshalMapSyscallInput(data, int(m.keySize)) } -func (m *Map) marshalValue(data interface{}) (sys.Pointer, error) { +func (m *Map) marshalValue(data any) (sys.Pointer, error) { var ( buf []byte err error @@ -1709,7 +1804,7 @@ func newMapIterator(target *Map) *MapIterator { // the result of Err afterwards. // // See Map.Get for further caveats around valueOut. -func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool { +func (mi *MapIterator) Next(keyOut, valueOut any) bool { if mi.err != nil || mi.done { return false } diff --git a/vendor/github.com/cilium/ebpf/marshalers.go b/vendor/github.com/cilium/ebpf/marshalers.go index d4e719c601..2542f2d0a3 100644 --- a/vendor/github.com/cilium/ebpf/marshalers.go +++ b/vendor/github.com/cilium/ebpf/marshalers.go @@ -62,7 +62,7 @@ func appendPerCPUSlice(buf []byte, slice any, possibleCPUs, elemLength, alignedE // Grow increases the slice's capacity, _if_necessary_ buf = slices.Grow(buf, alignedElemLength*possibleCPUs) - for i := 0; i < sliceLen; i++ { + for i := range sliceLen { elem := sliceValue.Index(i).Interface() elemBytes, err := sysenc.Marshal(elem, elemLength) if err != nil { @@ -118,7 +118,7 @@ func marshalBatchPerCPUValue(slice any, batchLen, elemLength int) ([]byte, error } alignedElemLength := internal.Align(elemLength, 8) buf := make([]byte, 0, batchLen*alignedElemLength*possibleCPUs) - for i := 0; i < batchLen; i++ { + for i := range batchLen { batch := sliceValue.Slice(i*possibleCPUs, (i+1)*possibleCPUs).Interface() buf, err = appendPerCPUSlice(buf, batch, possibleCPUs, elemLength, alignedElemLength) if err != nil { @@ -150,9 +150,9 @@ func unmarshalPerCPUValue(slice any, elemLength int, buf []byte) error { } sliceElemType := sliceType.Elem() - sliceElemIsPointer := sliceElemType.Kind() == reflect.Ptr + sliceElemIsPointer := sliceElemType.Kind() == reflect.Pointer stride := internal.Align(elemLength, 8) - for i := 0; i < possibleCPUs; i++ { + for i := range possibleCPUs { var elem any v := sliceValue.Index(i) if sliceElemIsPointer { @@ -199,7 +199,7 @@ func unmarshalBatchPerCPUValue(slice any, batchLen, elemLength int, buf []byte) len(buf), batchLen*fullValueSize) } - for i := 0; i < batchLen; i++ { + for i := range batchLen { elem := sliceValue.Slice(i*possibleCPUs, (i+1)*possibleCPUs).Interface() if err := unmarshalPerCPUValue(elem, elemLength, buf[:fullValueSize]); err != nil { return fmt.Errorf("batch %d: %w", i, err) diff --git a/vendor/github.com/cilium/ebpf/memory.go b/vendor/github.com/cilium/ebpf/memory.go index 3475fb07b8..a31fae3463 100644 --- a/vendor/github.com/cilium/ebpf/memory.go +++ b/vendor/github.com/cilium/ebpf/memory.go @@ -38,6 +38,8 @@ type Memory struct { b []byte ro bool heap bool + + cleanup runtime.Cleanup } func newMemory(fd, size int) (*Memory, error) { @@ -62,26 +64,29 @@ func newMemory(fd, size int) (*Memory, error) { return nil, fmt.Errorf("setting up memory-mapped region: %w", err) } - mm := &Memory{ - b, - ro, - false, - } - runtime.SetFinalizer(mm, (*Memory).close) + mm := &Memory{b: b, ro: ro, heap: false} + mm.cleanup = runtime.AddCleanup(mm, memoryCleanupFunc(), b) return mm, nil } -func (mm *Memory) close() { - if err := unix.Munmap(mm.b); err != nil { - panic(fmt.Errorf("unmapping memory: %w", err)) +func memoryCleanupFunc() func([]byte) { + return func(b []byte) { + if err := unix.Munmap(b); err != nil { + panic(fmt.Errorf("unmapping memory: %w", err)) + } } +} + +func (mm *Memory) close() { + mm.cleanup.Stop() + memoryCleanupFunc()(mm.b) mm.b = nil } // Size returns the size of the memory-mapped region in bytes. -func (mm *Memory) Size() int { - return len(mm.b) +func (mm *Memory) Size() uint32 { + return uint32(len(mm.b)) } // ReadOnly returns true if the memory-mapped region is read-only. @@ -90,11 +95,11 @@ func (mm *Memory) ReadOnly() bool { } // bounds returns true if an access at off of the given size is within bounds. -func (mm *Memory) bounds(off uint64, size uint64) bool { +func (mm *Memory) bounds(off, size uint32) bool { if off+size < off { return false } - return off+size <= uint64(len(mm.b)) + return off+size <= uint32(len(mm.b)) } // ReadAt implements [io.ReaderAt]. Useful for creating a new [io.OffsetWriter]. diff --git a/vendor/github.com/cilium/ebpf/memory_unsafe.go b/vendor/github.com/cilium/ebpf/memory_unsafe.go index cc254397f3..7e00b9e680 100644 --- a/vendor/github.com/cilium/ebpf/memory_unsafe.go +++ b/vendor/github.com/cilium/ebpf/memory_unsafe.go @@ -87,6 +87,7 @@ func newUnsafeMemory(fd, size int) (*Memory, error) { unsafe.Slice((*byte)(alloc), size), ro, true, + runtime.Cleanup{}, } return mm, nil @@ -117,13 +118,10 @@ func allocate(size int) (unsafe.Pointer, error) { // Allocate a new slice and store a pointer to its backing array. alloc := unsafe.Pointer(unsafe.SliceData(make([]byte, size))) - // nolint:govet - // // Align the pointer to a page boundary within the allocation. This may alias - // the initial pointer if it was already page-aligned. Ignore govet warnings - // since we're calling [runtime.KeepAlive] on the original Go memory. - aligned := unsafe.Pointer(internal.Align(uintptr(alloc), uintptr(os.Getpagesize()))) - runtime.KeepAlive(alloc) + // the initial pointer if it was already page-aligned. + aligned := internal.Align(uintptr(alloc), uintptr(os.Getpagesize())) + alloc = unsafe.Add(alloc, aligned-uintptr(alloc)) // Return an aligned pointer into the backing array, losing the original // reference. The runtime.SetFinalizer docs specify that its argument 'must be @@ -141,7 +139,7 @@ func allocate(size int) (unsafe.Pointer, error) { // pointer separately, which severely complicates finalizer setup and makes it // prone to human error. For now, just bump the pointer and treat it as the // new and only reference to the backing array. - return aligned, nil + return alloc, nil } // mapmap memory-maps the given file descriptor at the given address and sets a @@ -219,7 +217,7 @@ func unmap(size int) func(*byte) { // The comparable constraint narrows down the set of eligible types to exclude // slices, maps and functions. These complex types cannot be mapped to memory // directly. -func checkUnsafeMemory[T comparable](mm *Memory, off uint64) error { +func checkUnsafeMemory[T comparable](mm *Memory, off uint32) error { if mm.b == nil { return fmt.Errorf("memory-mapped region is nil") } @@ -240,12 +238,12 @@ func checkUnsafeMemory[T comparable](mm *Memory, off uint64) error { return fmt.Errorf("zero-sized type %s: %w", t, ErrInvalidType) } - if off%uint64(t.Align()) != 0 { + if off%uint32(t.Align()) != 0 { return fmt.Errorf("unaligned access of memory-mapped region: %d-byte aligned read at offset %d", t.Align(), off) } - vs, bs := uint64(size), uint64(len(mm.b)) - if off+vs > bs { + vs, bs := uint32(size), uint32(len(mm.b)) + if !mm.bounds(off, vs) { return fmt.Errorf("%d-byte value at offset %d exceeds mmap size of %d bytes", vs, off, bs) } @@ -334,7 +332,7 @@ func checkType(name string, t reflect.Type) error { // must be within bounds of the Memory. // // To access read-only memory, use [Memory.ReadAt]. -func memoryPointer[T comparable](mm *Memory, off uint64) (*T, error) { +func memoryPointer[T comparable](mm *Memory, off uint32) (*T, error) { if err := checkUnsafeMemory[T](mm, off); err != nil { return nil, fmt.Errorf("memory pointer: %w", err) } diff --git a/vendor/github.com/cilium/ebpf/prog.go b/vendor/github.com/cilium/ebpf/prog.go index 560c045ade..9d120ab6f5 100644 --- a/vendor/github.com/cilium/ebpf/prog.go +++ b/vendor/github.com/cilium/ebpf/prog.go @@ -8,7 +8,8 @@ import ( "math" "path/filepath" "runtime" - "sort" + "slices" + "strings" "time" "github.com/cilium/ebpf/asm" @@ -24,6 +25,10 @@ import ( // ErrNotSupported is returned whenever the kernel doesn't support a feature. var ErrNotSupported = internal.ErrNotSupported +// ErrProgIncompatible is returned when a loaded Program is incompatible with a +// given spec. +var ErrProgIncompatible = errors.New("program is incompatible") + // errBadRelocation is returned when the verifier rejects a program due to a // bad CO-RE relocation. // @@ -95,13 +100,10 @@ type ProgramOptions struct { // use the kernel BTF from a well-known location if nil. KernelTypes *btf.Spec - // Type information used for CO-RE relocations of kernel modules, - // indexed by module name. - // - // This is useful in environments where the kernel BTF is not available - // (containers) or where it is in a non-standard location. Defaults to - // use the kernel module BTF from a well-known location if nil. - KernelModuleTypes map[string]*btf.Spec + // Additional targets to consider for CO-RE relocations. This can be used to + // pass BTF information for kernel modules when it's not present on + // KernelTypes. + ExtraRelocationTargets []*btf.Spec } // ProgramSpec defines a Program. @@ -114,6 +116,13 @@ type ProgramSpec struct { // Type determines at which hook in the kernel a program will run. Type ProgramType + // Network interface index the user intends to attach this program to after + // loading. Only valid for some program types. + // + // Provides driver-specific context about the target interface to the + // verifier, required when using certain BPF helpers. + Ifindex uint32 + // AttachType of the program, needed to differentiate allowed context // accesses in some newer program types like CGroupSockAddr. // @@ -167,10 +176,34 @@ func (ps *ProgramSpec) Copy() *ProgramSpec { // Tag calculates the kernel tag for a series of instructions. // // Use asm.Instructions.Tag if you need to calculate for non-native endianness. +// +// Deprecated: The value produced by this method no longer matches tags produced +// by the kernel since Linux 6.18. Use [ProgramSpec.Compatible] instead. func (ps *ProgramSpec) Tag() (string, error) { return ps.Instructions.Tag(internal.NativeEndian) } +// Compatible returns nil if a loaded Program's kernel tag matches the one of +// the ProgramSpec. +// +// Returns [ErrProgIncompatible] if the tags do not match. +func (ps *ProgramSpec) Compatible(info *ProgramInfo) error { + if platform.IsWindows { + return fmt.Errorf("%w: Windows does not support tag readback from kernel", internal.ErrNotSupportedOnOS) + } + + ok, err := ps.Instructions.HasTag(info.Tag, internal.NativeEndian) + if err != nil { + return err + } + + if !ok { + return fmt.Errorf("%w: ProgramSpec and Program tags do not match", ErrProgIncompatible) + } + + return nil +} + // targetsKernelModule returns true if the program supports being attached to a // symbol provided by a kernel module. func (ps *ProgramSpec) targetsKernelModule() bool { @@ -209,6 +242,8 @@ type Program struct { name string pinnedPath string typ ProgramType + + btf *btf.Handle } // NewProgram creates a new Program. @@ -233,7 +268,7 @@ func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er return nil, errors.New("can't load a program from a nil spec") } - prog, err := newProgramWithOptions(spec, opts, newBTFCache(&opts)) + prog, err := newProgramWithOptions(spec, opts, btf.NewCache()) if errors.Is(err, asm.ErrUnsatisfiedMapReference) { return nil, fmt.Errorf("cannot load program without loading its whole collection: %w", err) } @@ -241,15 +276,15 @@ func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er } var ( - coreBadLoad = []byte(fmt.Sprintf("(18) r10 = 0x%x\n", btf.COREBadRelocationSentinel)) + coreBadLoad = fmt.Appendf(nil, "(18) r10 = 0x%x\n", btf.COREBadRelocationSentinel) // This log message was introduced by ebb676daa1a3 ("bpf: Print function name in // addition to function id") which first appeared in v4.10 and has remained // unchanged since. - coreBadCall = []byte(fmt.Sprintf("invalid func unknown#%d\n", btf.COREBadRelocationSentinel)) - kfuncBadCall = []byte(fmt.Sprintf("invalid func unknown#%d\n", kfuncCallPoisonBase)) + coreBadCall = fmt.Appendf(nil, "invalid func unknown#%d\n", btf.COREBadRelocationSentinel) + kfuncBadCall = fmt.Appendf(nil, "invalid func unknown#%d\n", kfuncCallPoisonBase) ) -func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, c *btf.Cache) (*Program, error) { +func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, c *btf.Cache) (result *Program, _ error) { if len(spec.Instructions) == 0 { return nil, errors.New("instructions cannot be empty") } @@ -284,6 +319,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, c *btf.Cache) ProgName: maybeFillObjName(spec.Name), ProgType: sys.ProgType(progType), ProgFlags: spec.Flags, + ProgIfindex: spec.Ifindex, ExpectedAttachType: sys.AttachType(spec.AttachType), License: sys.NewStringPointer(spec.License), KernVersion: kv, @@ -293,7 +329,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, c *btf.Cache) copy(insns, spec.Instructions) var b btf.Builder - if err := applyRelocations(insns, spec.ByteOrder, &b, c); err != nil { + if err := applyRelocations(insns, spec.ByteOrder, &b, c, opts.KernelTypes, opts.ExtraRelocationTargets); err != nil { return nil, fmt.Errorf("apply CO-RE relocations: %w", err) } @@ -322,12 +358,18 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, c *btf.Cache) attr.LineInfo = sys.SlicePointer(lib) } + var handle *btf.Handle if !b.Empty() { - handle, err := btf.NewHandle(&b) + var err error + handle, err = btf.NewHandle(&b) if err != nil { return nil, fmt.Errorf("load BTF: %w", err) } - defer handle.Close() + defer func() { + if result == nil || result.btf != handle { + handle.Close() + } + }() attr.ProgBtfFd = uint32(handle.FD()) } @@ -346,7 +388,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, c *btf.Cache) return nil, err } - handles, err := fixupKfuncs(insns) + handles, err := fixupKfuncs(insns, c) if err != nil { return nil, fmt.Errorf("fixing up kfuncs: %w", err) } @@ -377,15 +419,51 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, c *btf.Cache) attr.AttachBtfObjFd = uint32(spec.AttachTarget.FD()) defer runtime.KeepAlive(spec.AttachTarget) } else if spec.AttachTo != "" { - module, targetID, err := findProgramTargetInKernel(spec.AttachTo, spec.Type, spec.AttachType) + var targetMember string + attachTo := spec.AttachTo + + if spec.Type == StructOps { + attachTo, targetMember, _ = strings.Cut(attachTo, ":") + if targetMember == "" { + return nil, fmt.Errorf("struct_ops: AttachTo must be ':' (got %s)", spec.AttachTo) + } + } + + module, targetID, err := findProgramTargetInKernel(attachTo, spec.Type, spec.AttachType, c) if err != nil && !errors.Is(err, errUnrecognizedAttachType) { // We ignore errUnrecognizedAttachType since AttachTo may be non-empty // for programs that don't attach anywhere. return nil, fmt.Errorf("attach %s/%s: %w", spec.Type, spec.AttachType, err) } + if spec.Type == StructOps { + var s *btf.Spec + + target := btf.Type((*btf.Struct)(nil)) + s, module, err = findTargetInKernel(attachTo, &target, c) + if err != nil { + return nil, fmt.Errorf("lookup struct_ops kern type %q: %w", attachTo, err) + } + kType := target.(*btf.Struct) + + targetID, err = s.TypeID(kType) + if err != nil { + return nil, fmt.Errorf("type id for %s: %w", kType.TypeName(), err) + } + + idx := slices.IndexFunc(kType.Members, func(m btf.Member) bool { + return m.Name == targetMember + }) + if idx < 0 { + return nil, fmt.Errorf("member %q not found in %s", targetMember, kType.Name) + } + + // ExpectedAttachType: index of the target member in the struct + attr.ExpectedAttachType = sys.AttachType(idx) + } + attr.AttachBtfId = targetID - if module != nil { + if module != nil && attr.AttachBtfObjFd == 0 { attr.AttachBtfObjFd = uint32(module.FD()) defer module.Close() } @@ -401,7 +479,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, c *btf.Cache) // Loading with logging disabled should never retry. fd, err = sys.ProgLoad(attr) if err == nil { - return &Program{"", fd, spec.Name, "", spec.Type}, nil + return &Program{"", fd, spec.Name, "", spec.Type, handle}, nil } } else { // Only specify log size if log level is also specified. Setting size @@ -421,7 +499,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, c *btf.Cache) fd, err = sys.ProgLoad(attr) if err == nil { - return &Program{unix.ByteSliceToString(logBuf), fd, spec.Name, "", spec.Type}, nil + return &Program{unix.ByteSliceToString(logBuf), fd, spec.Name, "", spec.Type, handle}, nil } if !retryLogAttrs(attr, opts.LogSizeStart, err) { @@ -435,6 +513,10 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, c *btf.Cache) } } + if errors.Is(err, sys.ErrTokenCapabilities) { + return nil, fmt.Errorf("load program: %w", err) + } + end := bytes.IndexByte(logBuf, 0) if end < 0 { end = len(logBuf) @@ -562,7 +644,7 @@ func newProgramFromFD(fd *sys.FD) (*Program, error) { return nil, fmt.Errorf("discover program type: %w", err) } - return &Program{"", fd, info.Name, "", info.Type}, nil + return &Program{"", fd, info.Name, "", info.Type, nil}, nil } func (p *Program) String() string { @@ -597,6 +679,10 @@ func (p *Program) Stats() (*ProgramStats, error) { // Returns ErrNotSupported if the kernel has no BTF support, or if there is no // BTF associated with the program. func (p *Program) Handle() (*btf.Handle, error) { + if p.btf != nil { + return p.btf.Clone() + } + info, err := p.Info() if err != nil { return nil, err @@ -610,6 +696,48 @@ func (p *Program) Handle() (*btf.Handle, error) { return btf.NewHandleFromID(id) } +// SetHandle caches program's type information so that subsequent +// Handle() call doesn't need to query it from the kernel (requires +// CAP_SYS_ADMIN). +// +// The caller remains responsible for closing the btf.Handle; SetHandle +// makes a private copy. +// +// Type info is auto-cached for Programs loaded by the library itself. +// Use SetHandle() with program references obtained via NewProgramFromFD +// and LoadPinnedProgram. +func (p *Program) SetHandle(h *btf.Handle) error { + if h == nil { + return fmt.Errorf("nil BTF handle") + } + + info, err := p.Info() + if err != nil { + return err + } + + handleInfo, err := h.Info() + if err != nil { + return err + } + + if id, ok := info.BTFID(); !ok || id != handleInfo.ID { + return fmt.Errorf("program/BTF mismatch") + } + + if p.btf != nil { + return nil + } + + dup, err := h.Clone() + if err != nil { + return err + } + + p.btf = dup + return nil +} + // FD gets the file descriptor of the Program. // // It is invalid to call this function after Close has been called. @@ -627,12 +755,18 @@ func (p *Program) Clone() (*Program, error) { return nil, nil } + btfClone, err := p.btf.Clone() + if err != nil { + return nil, fmt.Errorf("can't clone program: %w", err) + } + dup, err := p.fd.Dup() if err != nil { + btfClone.Close() return nil, fmt.Errorf("can't clone program: %w", err) } - return &Program{p.VerifierLog, dup, p.name, "", p.typ}, nil + return &Program{p.VerifierLog, dup, p.name, "", p.typ, btfClone}, nil } // Pin persists the Program on the BPF virtual file system past the lifetime of @@ -677,6 +811,10 @@ func (p *Program) Close() error { return nil } + if err := p.btf.Close(); err != nil { + return err + } + return p.fd.Close() } @@ -690,9 +828,9 @@ type RunOptions struct { // Program's data after Program has run. Caller must allocate. Optional field. DataOut []byte // Program's context input. Optional field. - Context interface{} + Context any // Program's context after Program has run. Must be a pointer or slice. Optional field. - ContextOut interface{} + ContextOut any // Minimum number of times to run Program. Optional field. Defaults to 1. // // The program may be executed more often than this due to interruptions, e.g. @@ -703,6 +841,11 @@ type RunOptions struct { // CPU to run Program on. Optional field. // Note not all program types support this field. CPU uint32 + // BatchSize (default 64) affects the kernel's packet buffer allocation behaviour when running + // programs with BPF_F_TEST_XDP_LIVE_FRAMES and a non-zero [RunOptions.Repeat] value. + // For more details, see the kernel documentation on BPF_PROG_RUN: + // https://docs.kernel.org/bpf/bpf_prog_run.html#running-xdp-programs-in-live-frame-mode + BatchSize uint32 // Called whenever the syscall is interrupted, and should be set to testing.B.ResetTimer // or similar. Typically used during benchmarking. Optional field. // @@ -851,6 +994,9 @@ func (p *Program) run(opts *RunOptions) (uint32, time.Duration, error) { var ctxOut []byte if opts.ContextOut != nil { ctxOut = make([]byte, binary.Size(opts.ContextOut)) + } else if platform.IsWindows && len(ctxIn) > 0 { + // Windows rejects a non-zero ctxIn with a nil ctxOut. + ctxOut = make([]byte, len(ctxIn)) } attr := sys.ProgRunAttr{ @@ -866,6 +1012,17 @@ func (p *Program) run(opts *RunOptions) (uint32, time.Duration, error) { CtxOut: sys.SlicePointer(ctxOut), Flags: opts.Flags, Cpu: opts.CPU, + BatchSize: opts.BatchSize, + } + + if p.Type() == Syscall && ctxIn != nil && ctxOut != nil { + // Linux syscall program errors on non-nil ctxOut, uses ctxIn + // for both input and output. Shield the user from this wart. + if len(ctxIn) != len(ctxOut) { + return 0, 0, errors.New("length mismatch: Context and ContextOut") + } + attr.CtxOut, attr.CtxSizeOut = sys.TypedPointer[uint8]{}, 0 + ctxOut = ctxIn } retry: @@ -914,7 +1071,7 @@ retry: opts.DataOut = opts.DataOut[:int(attr.DataSizeOut)] } - if len(ctxOut) != 0 { + if opts.ContextOut != nil { b := bytes.NewReader(ctxOut) if err := binary.Read(b, internal.NativeEndian, opts.ContextOut); err != nil { return 0, 0, fmt.Errorf("failed to decode ContextOut: %v", err) @@ -968,20 +1125,16 @@ func LoadPinnedProgram(fileName string, opts *LoadPinOptions) (*Program, error) return nil, fmt.Errorf("%s is not a Program", fileName) } - info, err := newProgramInfoFromFd(fd) - if err != nil { - _ = fd.Close() - return nil, fmt.Errorf("info for %s: %w", fileName, err) - } + p, err := newProgramFromFD(fd) + if err == nil { + p.pinnedPath = fileName - var progName string - if haveObjName() == nil { - progName = info.Name - } else { - progName = filepath.Base(fileName) + if haveObjName() != nil { + p.name = filepath.Base(fileName) + } } - return &Program{"", fd, progName, fileName, info.Type}, nil + return p, err } // ProgramGetNextID returns the ID of the next eBPF program. @@ -1017,7 +1170,7 @@ var errUnrecognizedAttachType = errors.New("unrecognized attach type") // // Returns errUnrecognizedAttachType if the combination of progType and attachType // is not recognised. -func findProgramTargetInKernel(name string, progType ProgramType, attachType AttachType) (*btf.Handle, btf.TypeID, error) { +func findProgramTargetInKernel(name string, progType ProgramType, attachType AttachType, cache *btf.Cache) (*btf.Handle, btf.TypeID, error) { type match struct { p ProgramType a AttachType @@ -1029,6 +1182,10 @@ func findProgramTargetInKernel(name string, progType ProgramType, attachType Att ) switch (match{progType, attachType}) { + case match{StructOps, AttachStructOps}: + typeName = name + featureName = "struct_ops " + name + target = (*btf.Struct)(nil) case match{LSM, AttachLSMMac}: typeName = "bpf_lsm_" + name featureName = name + " LSM hook" @@ -1057,12 +1214,7 @@ func findProgramTargetInKernel(name string, progType ProgramType, attachType Att return nil, 0, errUnrecognizedAttachType } - spec, err := btf.LoadKernelSpec() - if err != nil { - return nil, 0, fmt.Errorf("load kernel spec: %w", err) - } - - spec, module, err := findTargetInKernel(spec, typeName, &target) + spec, module, err := findTargetInKernel(typeName, &target, cache) if errors.Is(err, btf.ErrNotFound) { return nil, 0, &internal.UnsupportedFeatureError{Name: featureName} } @@ -1090,17 +1242,24 @@ func findProgramTargetInKernel(name string, progType ProgramType, attachType Att // target will point at the found type after a successful call. Searches both // vmlinux and any loaded modules. // -// Returns a non-nil handle if the type was found in a module, or btf.ErrNotFound -// if the type wasn't found at all. -func findTargetInKernel(kernelSpec *btf.Spec, typeName string, target *btf.Type) (*btf.Spec, *btf.Handle, error) { - if kernelSpec == nil { - return nil, nil, fmt.Errorf("nil kernelSpec: %w", btf.ErrNotFound) +// Returns a non-nil handle if the type was found in a module, [btf.ErrNotFound] +// if the type wasn't found or if BTF is not enabled. +func findTargetInKernel(typeName string, target *btf.Type, cache *btf.Cache) (*btf.Spec, *btf.Handle, error) { + kernelSpec, err := cache.Kernel() + if err != nil { + return nil, nil, fmt.Errorf("load kernel spec: %w (%w)", btf.ErrNotFound, err) } - err := kernelSpec.TypeByName(typeName, target) + err = kernelSpec.TypeByName(typeName, target) if errors.Is(err, btf.ErrNotFound) { - spec, module, err := findTargetInModule(kernelSpec, typeName, target) + spec, module, err := findTargetInModule(typeName, target, cache) if err != nil { + // EPERM may be returned when we do not have CAP_SYS_ADMIN. + // Wrap error with btf.ErrNotFound so callers can handle it accordingly. + if errors.Is(err, unix.EPERM) { + return spec, nil, fmt.Errorf("find target in modules: %w (%w)", btf.ErrNotFound, err) + } + return nil, nil, fmt.Errorf("find target in modules: %w", err) } return spec, module, nil @@ -1117,7 +1276,7 @@ func findTargetInKernel(kernelSpec *btf.Spec, typeName string, target *btf.Type) // are searched in the order they were loaded. // // Returns btf.ErrNotFound if the target can't be found in any module. -func findTargetInModule(base *btf.Spec, typeName string, target *btf.Type) (*btf.Spec, *btf.Handle, error) { +func findTargetInModule(typeName string, target *btf.Type, cache *btf.Cache) (*btf.Spec, *btf.Handle, error) { it := new(btf.HandleIterator) defer it.Handle.Close() @@ -1131,7 +1290,7 @@ func findTargetInModule(base *btf.Spec, typeName string, target *btf.Type) (*btf continue } - spec, err := it.Handle.Spec(base) + spec, err := cache.Module(info.Name) if err != nil { return nil, nil, fmt.Errorf("parse types for module %s: %w", info.Name, err) } @@ -1191,19 +1350,3 @@ func findTargetInProgram(prog *Program, name string, progType ProgramType, attac return spec.TypeID(targetFunc) } - -func newBTFCache(opts *ProgramOptions) *btf.Cache { - c := btf.NewCache() - if opts.KernelTypes != nil { - c.KernelTypes = opts.KernelTypes - c.ModuleTypes = opts.KernelModuleTypes - if opts.KernelModuleTypes != nil { - c.LoadedModules = []string{} - for name := range opts.KernelModuleTypes { - c.LoadedModules = append(c.LoadedModules, name) - } - sort.Strings(c.LoadedModules) - } - } - return c -} diff --git a/vendor/github.com/cilium/ebpf/rlimit/rlimit_linux.go b/vendor/github.com/cilium/ebpf/rlimit/rlimit_linux.go index b05699c5f3..c8cada137f 100644 --- a/vendor/github.com/cilium/ebpf/rlimit/rlimit_linux.go +++ b/vendor/github.com/cilium/ebpf/rlimit/rlimit_linux.go @@ -76,6 +76,12 @@ func detectMemcgAccounting() error { return nil } + if errors.Is(mapErr, sys.ErrTokenCapabilities) { + // If BPF tokens are in use and array maps are not permitted, assume memcg + // accounting is supported since tokens are a newer feature. + return nil + } + // EPERM shows up when map creation would exceed the memory budget. if errors.Is(mapErr, unix.EPERM) { return unsupportedMemcgAccounting diff --git a/vendor/github.com/cilium/ebpf/struct_ops.go b/vendor/github.com/cilium/ebpf/struct_ops.go new file mode 100644 index 0000000000..78e4d35b20 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/struct_ops.go @@ -0,0 +1,250 @@ +package ebpf + +import ( + "errors" + "fmt" + "reflect" + "strings" + + "github.com/cilium/ebpf/btf" + "github.com/cilium/ebpf/internal" +) + +const structOpsValuePrefix = "bpf_struct_ops_" +const structOpsLinkSec = ".struct_ops.link" +const structOpsSec = ".struct_ops" +const structOpsKeySize = 4 + +// structOpsMemberLayout describes the location and type of a struct_ops member. +type structOpsMemberLayout struct { + member btf.Member + off int + size int + typ btf.Type +} + +// newStructOpsMemberLayout returns a layout information from a struct_ops member. +func newStructOpsMemberLayout(m btf.Member) (*structOpsMemberLayout, error) { + if m.BitfieldSize > 0 { + return nil, fmt.Errorf("bitfield %s not supported", m.Name) + } + + size, err := btf.Sizeof(m.Type) + if err != nil { + return nil, fmt.Errorf("sizeof(%s): %w", m.Name, err) + } + + off := int(m.Offset.Bytes()) + if off < 0 { + return nil, fmt.Errorf("member %q: invalid offset", m.Name) + } + + return &structOpsMemberLayout{ + member: m, + off: off, + size: size, + typ: btf.UnderlyingType(m.Type), + }, nil +} + +// bytes returns the portion of `buf` corresponding to the member. +func (ml *structOpsMemberLayout) bytes(buf []byte) ([]byte, error) { + if ml.off < 0 || ml.off+ml.size > len(buf) { + return nil, fmt.Errorf("member %q: value buffer too small", ml.member.Name) + } + return buf[ml.off : ml.off+ml.size], nil +} + +// structOpsFuncPtrMember returns an error unless m is a func pointer member. +func structOpsFuncPtrMember(m btf.Member) error { + kmPtr, ok := btf.As[*btf.Pointer](m.Type) + if !ok { + return fmt.Errorf("member %s is not a func pointer", m.Name) + } + if _, isFuncProto := btf.As[*btf.FuncProto](kmPtr.Target); !isFuncProto { + return fmt.Errorf("member %s is not a func pointer", m.Name) + } + return nil +} + +// structOpsFindInnerType returns the "inner" struct inside a value struct_ops type. +// +// Given a value like: +// +// struct bpf_struct_ops_bpf_testmod_ops { +// struct bpf_struct_ops_common common; +// struct bpf_testmod_ops data; +// }; +// +// this function returns the *btf.Struct for "bpf_testmod_ops" along with the +// byte offset of the "data" member inside the value type. +// +// The inner struct name is derived by trimming the "bpf_struct_ops_" prefix +// from the value's name. +func structOpsFindInnerType(vType *btf.Struct) (*btf.Struct, uint32, error) { + innerName := strings.TrimPrefix(vType.Name, structOpsValuePrefix) + + for _, m := range vType.Members { + if st, ok := btf.As[*btf.Struct](m.Type); ok && st.Name == innerName { + return st, m.Offset.Bytes(), nil + } + } + + return nil, 0, fmt.Errorf("inner struct %q not found in %s", innerName, vType.Name) +} + +// structOpsFindTarget resolves the kernel-side "value struct" for a struct_ops map. +func structOpsFindTarget(userType *btf.Struct, cache *btf.Cache) (vType *btf.Struct, id btf.TypeID, module *btf.Handle, err error) { + // the kernel value type name, e.g. "bpf_struct_ops_" + vTypeName := structOpsValuePrefix + userType.Name + + target := btf.Type((*btf.Struct)(nil)) + spec, module, err := findTargetInKernel(vTypeName, &target, cache) + if errors.Is(err, btf.ErrNotFound) { + return nil, 0, nil, fmt.Errorf("%q doesn't exist in kernel: %w", vTypeName, ErrNotSupported) + } + if err != nil { + return nil, 0, nil, fmt.Errorf("lookup value type %q: %w", vTypeName, err) + } + + id, err = spec.TypeID(target) + if err != nil { + return nil, 0, nil, err + } + + return target.(*btf.Struct), id, module, nil +} + +// structOpsPopulateValue writes a `prog FD` which references to `p` into the +// struct_ops value buffer `kernVData` at byte offset `dstOff` corresponding to +// the member `km`. +func structOpsPopulateValue(km btf.Member, kernVData []byte, p *Program) error { + if err := structOpsFuncPtrMember(km); err != nil { + return err + } + + layout, err := newStructOpsMemberLayout(km) + if err != nil { + return err + } + + dst, err := layout.bytes(kernVData) + if err != nil || len(dst) != 8 { + return fmt.Errorf("member %q: value buffer too small for func ptr", km.Name) + } + + internal.NativeEndian.PutUint64(dst, uint64(p.FD())) + return nil +} + +// structOpsValidateMemberPair checks whether `m` can be copied into `km`. +func structOpsValidateMemberPair(m, km btf.Member) (int, error) { + mLayout, err := newStructOpsMemberLayout(m) + if err != nil { + return 0, fmt.Errorf("user member %s: %w", m.Name, err) + } + + kLayout, err := newStructOpsMemberLayout(km) + if err != nil { + return 0, fmt.Errorf("kernel member %s: %w", km.Name, err) + } + + if mLayout.size != kLayout.size { + return 0, fmt.Errorf("size mismatch for %s: user=%d kernel=%d", m.Name, mLayout.size, kLayout.size) + } + + if reflect.TypeOf(mLayout.typ) != reflect.TypeOf(kLayout.typ) { + return 0, fmt.Errorf("unmatched member type %s != %s (kernel)", m.Name, km.Name) + } + + return mLayout.size, nil +} + +// structOpsCopyMemberBytes copies the bytes of `m` into `km`. +func structOpsCopyMemberBytes(m, km btf.Member, data, kernVData []byte, size int) error { + mLayout, err := newStructOpsMemberLayout(m) + if err != nil { + return fmt.Errorf("user member %s: %w", m.Name, err) + } + + kLayout, err := newStructOpsMemberLayout(km) + if err != nil { + return fmt.Errorf("kernel member %s: %w", km.Name, err) + } + + if mLayout.size != size { + return fmt.Errorf("member %q: unexpected validated size %d, got %d", m.Name, size, mLayout.size) + } + if kLayout.size != size { + return fmt.Errorf("member %q: unexpected validated size %d, got %d", km.Name, size, kLayout.size) + } + + src, err := mLayout.bytes(data) + if err != nil { + return fmt.Errorf("member %q: userdata is too small", m.Name) + } + + dst, err := kLayout.bytes(kernVData) + if err != nil { + return fmt.Errorf("member %q: value type is too small", km.Name) + } + + switch mLayout.typ.(type) { + case *btf.Struct, *btf.Union: + if !structOpsIsMemZeroed(src) { + return fmt.Errorf("non-zero nested struct %s: %w", m.Name, ErrNotSupported) + } + return nil + } + + copy(dst, src) + return nil +} + +// structOpsCopyMember copies `m` into `km`. +func structOpsCopyMember(m, km btf.Member, data []byte, kernVData []byte) error { + size, err := structOpsValidateMemberPair(m, km) + if err != nil { + return err + } + return structOpsCopyMemberBytes(m, km, data, kernVData, size) +} + +// structOpsIsMemZeroed() checks whether all bytes in data are zero. +func structOpsIsMemZeroed(data []byte) bool { + for _, b := range data { + if b != 0 { + return false + } + } + return true +} + +// structOpsSetAttachTo sets p.AttachTo in the expected "struct_name:memberName" format +// based on the struct definition. +// +// this relies on the assumption that each member in the +// `.struct_ops` section has a relocation at its starting byte offset. +func structOpsSetAttachTo( + sec *elfSection, + baseOff uint32, + userSt *btf.Struct, + progs map[string]*ProgramSpec) error { + for _, m := range userSt.Members { + memberOff := m.Offset + sym, ok := sec.relocations[uint64(baseOff+memberOff.Bytes())] + if !ok { + continue + } + p, ok := progs[sym.Name] + if !ok || p == nil { + return fmt.Errorf("program %s not found", sym.Name) + } + + if p.Type != StructOps { + return fmt.Errorf("program %s is not StructOps", sym.Name) + } + p.AttachTo = userSt.Name + ":" + m.Name + } + return nil +} diff --git a/vendor/github.com/cilium/ebpf/types.go b/vendor/github.com/cilium/ebpf/types.go index 56e3182083..f87715aaec 100644 --- a/vendor/github.com/cilium/ebpf/types.go +++ b/vendor/github.com/cilium/ebpf/types.go @@ -5,7 +5,7 @@ import ( "github.com/cilium/ebpf/internal/sys" ) -//go:generate go run golang.org/x/tools/cmd/stringer@latest -output types_string.go -type=MapType,ProgramType,PinType +//go:generate go tool stringer -output types_string.go -type=MapType,ProgramType,PinType // MapType indicates the type map structure // that will be initialized in the kernel. @@ -144,7 +144,7 @@ func (mt MapType) hasPerCPUValue() bool { // canStoreMapOrProgram returns true if the Map stores references to another Map // or Program. func (mt MapType) canStoreMapOrProgram() bool { - return mt.canStoreMap() || mt.canStoreProgram() + return mt.canStoreMap() || mt.canStoreProgram() || mt == StructOpsMap } // canStoreMap returns true if the map type accepts a map fd @@ -188,6 +188,16 @@ func (mt MapType) mustHaveNoPrealloc() bool { return false } +// mustHaveZeroMaxEntries returns true if the map type requires MaxEntries to be zero. +func (mt MapType) mustHaveZeroMaxEntries() bool { + switch mt { + case CgroupStorage, CGroupStorage, PerCPUCGroupStorage, InodeStorage, TaskStorage, SkStorage: + return true + } + + return false +} + // ProgramType of the eBPF program type ProgramType uint32 @@ -252,7 +262,7 @@ func ProgramTypeForPlatform(plat string, value uint32) (ProgramType, error) { // Will cause invalid argument (EINVAL) at program load time if set incorrectly. type AttachType uint32 -//go:generate go run golang.org/x/tools/cmd/stringer@latest -type AttachType -trimprefix Attach +//go:generate go tool stringer -type AttachType -trimprefix Attach // AttachNone is an alias for AttachCGroupInetIngress for readability reasons. const AttachNone AttachType = 0 diff --git a/vendor/github.com/cilium/ebpf/types_string.go b/vendor/github.com/cilium/ebpf/types_string.go index efcd6a7dd0..94bc2e26cd 100644 --- a/vendor/github.com/cilium/ebpf/types_string.go +++ b/vendor/github.com/cilium/ebpf/types_string.go @@ -162,8 +162,9 @@ const _PinType_name = "PinNonePinByName" var _PinType_index = [...]uint8{0, 7, 16} func (i PinType) String() string { - if i >= PinType(len(_PinType_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_PinType_index)-1 { return "PinType(" + strconv.FormatInt(int64(i), 10) + ")" } - return _PinType_name[_PinType_index[i]:_PinType_index[i+1]] + return _PinType_name[_PinType_index[idx]:_PinType_index[idx+1]] } diff --git a/vendor/github.com/cilium/ebpf/variable.go b/vendor/github.com/cilium/ebpf/variable.go index c6fd55cba1..51691bb4fd 100644 --- a/vendor/github.com/cilium/ebpf/variable.go +++ b/vendor/github.com/cilium/ebpf/variable.go @@ -1,9 +1,11 @@ package ebpf import ( + "encoding/binary" "fmt" "io" "reflect" + "slices" "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal/sysenc" @@ -15,56 +17,52 @@ import ( // All operations on a VariableSpec's underlying MapSpec are performed in the // host's native endianness. type VariableSpec struct { - name string - offset uint64 - size uint64 - - m *MapSpec - t *btf.Var + Name string + // Name of the section this variable was allocated in. + SectionName string + // Offset of the variable within the datasec. + Offset uint32 + // Byte representation of the variable's value. + Value []byte + // Type information of the variable. Optional. + Type *btf.Var } // Set sets the value of the VariableSpec to the provided input using the host's // native endianness. func (s *VariableSpec) Set(in any) error { - buf, err := sysenc.Marshal(in, int(s.size)) - if err != nil { - return fmt.Errorf("marshaling value %s: %w", s.name, err) + size := int(s.Size()) + if size == 0 { + bs := binary.Size(in) + if bs < 0 { + return fmt.Errorf("cannot determine binary size of value %v", in) + } + size = bs } - b, _, err := s.m.dataSection() - if err != nil { - return fmt.Errorf("getting data section of map %s: %w", s.m.Name, err) + if s.Value == nil { + s.Value = make([]byte, size) } - if int(s.offset+s.size) > len(b) { - return fmt.Errorf("offset %d(+%d) for variable %s is out of bounds", s.offset, s.size, s.name) + buf, err := sysenc.Marshal(in, size) + if err != nil { + return fmt.Errorf("marshaling value %s: %w", s.Name, err) } - // MapSpec.Copy() performs a shallow copy. Fully copy the byte slice - // to avoid any changes affecting other copies of the MapSpec. - cpy := make([]byte, len(b)) - copy(cpy, b) - - buf.CopyTo(cpy[s.offset : s.offset+s.size]) - - s.m.Contents[0] = MapKV{Key: uint32(0), Value: cpy} - + buf.CopyTo(s.Value) return nil } // Get writes the value of the VariableSpec to the provided output using the // host's native endianness. +// +// Returns an error if the variable is not initialized or if the unmarshaling fails. func (s *VariableSpec) Get(out any) error { - b, _, err := s.m.dataSection() - if err != nil { - return fmt.Errorf("getting data section of map %s: %w", s.m.Name, err) + if s.Value == nil { + return fmt.Errorf("variable is not initialized") } - if int(s.offset+s.size) > len(b) { - return fmt.Errorf("offset %d(+%d) for variable %s is out of bounds", s.offset, s.size, s.name) - } - - if err := sysenc.Unmarshal(out, b[s.offset:s.offset+s.size]); err != nil { + if err := sysenc.Unmarshal(out, s.Value); err != nil { return fmt.Errorf("unmarshaling value: %w", err) } @@ -72,61 +70,42 @@ func (s *VariableSpec) Get(out any) error { } // Size returns the size of the variable in bytes. -func (s *VariableSpec) Size() uint64 { - return s.size -} +func (s *VariableSpec) Size() uint32 { + if s.Value != nil { + return uint32(len(s.Value)) + } -// MapName returns the name of the underlying MapSpec. -func (s *VariableSpec) MapName() string { - return s.m.Name -} + if s.Type != nil { + size, err := btf.Sizeof(s.Type.Type) + if err != nil { + return 0 + } + return uint32(size) + } -// Offset returns the offset of the variable in the underlying MapSpec. -func (s *VariableSpec) Offset() uint64 { - return s.offset + return 0 } -// Constant returns true if the VariableSpec represents a variable that is -// read-only from the perspective of the BPF program. +// Constant returns true if the variable is located in a data section intended +// for constant values. func (s *VariableSpec) Constant() bool { - return s.m.readOnly() -} - -// Type returns the [btf.Var] representing the variable in its data section. -// This is useful for inspecting the variable's decl tags and the type -// information of the inner type. -// -// Returns nil if the original ELF object did not contain BTF information. -func (s *VariableSpec) Type() *btf.Var { - return s.t + return isConstantDataSection(s.SectionName) } func (s *VariableSpec) String() string { - return fmt.Sprintf("%s (type=%v, map=%s, offset=%d, size=%d)", s.name, s.t, s.m.Name, s.offset, s.size) + return fmt.Sprintf("%s (type=%v, section=%s, offset=%d, size=%d)", s.Name, s.Type, s.SectionName, s.Offset, s.Size()) } -// copy returns a new VariableSpec with the same values as the original, -// but with a different underlying MapSpec. This is useful when copying a -// CollectionSpec. Returns nil if a MapSpec with the same name is not found. -func (s *VariableSpec) copy(cpy *CollectionSpec) *VariableSpec { - out := &VariableSpec{ - name: s.name, - offset: s.offset, - size: s.size, - } - if s.t != nil { - out.t = btf.Copy(s.t).(*btf.Var) - } +// Copy the VariableSpec. +func (s *VariableSpec) Copy() *VariableSpec { + cpy := *s + cpy.Value = slices.Clone(s.Value) - // Attempt to find a MapSpec with the same name in the copied CollectionSpec. - for _, m := range cpy.Maps { - if m.Name == s.m.Name { - out.m = m - return out - } + if s.Type != nil { + cpy.Type = btf.Copy(s.Type).(*btf.Var) } - return nil + return &cpy } // Variable is a convenience wrapper for modifying global variables of a @@ -137,16 +116,16 @@ func (s *VariableSpec) copy(cpy *CollectionSpec) *VariableSpec { // [ErrNotSupported]. type Variable struct { name string - offset uint64 - size uint64 + offset uint32 + size uint32 t *btf.Var mm *Memory } -func newVariable(name string, offset, size uint64, t *btf.Var, mm *Memory) (*Variable, error) { +func newVariable(name string, offset, size uint32, t *btf.Var, mm *Memory) (*Variable, error) { if mm != nil { - if int(offset+size) > mm.Size() { + if !mm.bounds(offset, size) { return nil, fmt.Errorf("offset %d(+%d) is out of bounds", offset, size) } } @@ -161,7 +140,7 @@ func newVariable(name string, offset, size uint64, t *btf.Var, mm *Memory) (*Var } // Size returns the size of the variable. -func (v *Variable) Size() uint64 { +func (v *Variable) Size() uint32 { return v.size } @@ -240,7 +219,6 @@ func checkVariable[T any](v *Variable) error { } t := reflect.TypeFor[T]() - size := uint64(t.Size()) if t.Kind() == reflect.Uintptr && v.size == 8 { // uintptr is 8 bytes on 64-bit and 4 on 32-bit. In BPF/BTF, pointers are // always 8 bytes. For the sake of portability, allow accessing 8-byte BPF @@ -248,8 +226,8 @@ func checkVariable[T any](v *Variable) error { // pointer should be zero anyway. return nil } - if v.size != size { - return fmt.Errorf("can't create %d-byte accessor to %d-byte variable: %w", size, v.size, ErrInvalidType) + if uintptr(v.size) != t.Size() { + return fmt.Errorf("can't create %d-byte accessor to %d-byte variable: %w", t.Size(), v.size, ErrInvalidType) } return nil diff --git a/vendor/github.com/gaissmai/bart/.coderabbit.yaml b/vendor/github.com/gaissmai/bart/.coderabbit.yaml index 5d2e10c366..0832dbda84 100644 --- a/vendor/github.com/gaissmai/bart/.coderabbit.yaml +++ b/vendor/github.com/gaissmai/bart/.coderabbit.yaml @@ -13,7 +13,7 @@ reviews: review_status: true commit_status: true fail_commit_status: false - collapse_walkthrough: false + collapse_walkthrough: true changed_files_summary: true sequence_diagrams: true estimate_code_review_effort: true @@ -24,10 +24,9 @@ reviews: auto_apply_labels: false suggested_reviewers: false auto_assign_reviewers: false - poem: true + poem: false labeling_instructions: [] path_filters: - - '!**/doc/**' - '!**/testdata/**' path_instructions: [] abort_on_close: true diff --git a/vendor/github.com/gaissmai/bart/.golangci.yml b/vendor/github.com/gaissmai/bart/.golangci.yml index fe6a132500..bb47f80859 100644 --- a/vendor/github.com/gaissmai/bart/.golangci.yml +++ b/vendor/github.com/gaissmai/bart/.golangci.yml @@ -23,6 +23,7 @@ linters: - gochecknoglobals - nolintlint - modernize + - gocritic settings: govet: enable: @@ -33,11 +34,13 @@ linters: excludes: - G404 # deterministic RNG in tests/benchmarks only exclusions: + generated: lax rules: - - path: '(.+)_test\.go' - linters: + - linters: - gochecknoglobals - generated: lax + - gosec + - gocyclo + path: _test\.go presets: - comments - common-false-positives diff --git a/vendor/github.com/gaissmai/bart/LICENSE b/vendor/github.com/gaissmai/bart/LICENSE index 6e65745d2d..b4415938ab 100644 --- a/vendor/github.com/gaissmai/bart/LICENSE +++ b/vendor/github.com/gaissmai/bart/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Karl Gaissmaier +Copyright (c) 2026 Karl Gaissmaier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/gaissmai/bart/NODETYPES.md b/vendor/github.com/gaissmai/bart/NODETYPES.md index 56a278f97c..889c5e4d98 100644 --- a/vendor/github.com/gaissmai/bart/NODETYPES.md +++ b/vendor/github.com/gaissmai/bart/NODETYPES.md @@ -1,15 +1,15 @@ # package bart # Node Types Comparison - + BART implements three different node types, each optimized for specific use cases: - + ## Memory Footprint (64-bit systems) - + **Base Components:** - `BitSet256`: `[4]uint64` = **32 bytes** - `sparse.Array256[T]`: `BitSet256 + []T` = **56 bytes + n×sizeof(T)** - + **Child Reference Sizing:** - `childRef`: 8 bytes (pointer) or 16 bytes (interface value storage) - The actual size depends on implementation: 8B for `*node` pointers, 16B for `interface{}` values @@ -17,12 +17,12 @@ ### BartNode[V] - Dynamic Sparse Node ```go type BartNode[V any] struct { - prefixes sparse.Array256[V] // 56 + n×sizeof(V) + prefixes sparse.Array256[V] // 56 + n×sizeof(V) children sparse.Array256[childRef] // 56 + m×sizeof(childRef) } ``` **Memory Usage:** **112 bytes + n×sizeof(V) + m×sizeof(childRef)** - + ### LiteNode - Dynamic Sparse, prefixes Bitset-Only Node ```go type LiteNode struct { @@ -36,29 +36,22 @@ type LiteNode struct { ### FastNode[V] - Fixed Array Node ```go type FastNode[V any] struct { - prefixes struct { - bitset.BitSet256 - items [256]*V - } // 2,048 + 32 bytes BitSet256 - children struct { - bitset.BitSet256 - items [256]*any // pointer-to-interface for 8‑byte nils - } // 2,048 + 32 bytes BitSet256 - pfxCount uint16 - cldCount uint16 // + padding + prefixes sparse.Array256[V] // 56 + n×sizeof(V) + children sparse.Array256[childRef] // 56 + m×sizeof(childRef) + childRankCache [256]uint8 // 256 } ``` -**Memory Usage:** **4,168 bytes** (fixed, regardless of occupancy) - +**Memory Usage:** **256 + 112 bytes + n×sizeof(V) + m×sizeof(childRef)** + ## Real-World Example **Scenario:** Node with 10 prefixes, 5 children - + | Node Type | Base | *Payload | +Children | Total | **Bytes/Prefix** ¹ | |-----------|------|----------|----------|-----------|------------------| | LiteNode | 96 | 0 | 5×16=80 | 176 bytes | **17** | | BartNode[int] | 112 | 10×8=80 | 5×16=80 | 272 bytes | **27** | - | FastNode[int] | 4,168 | 0 | 0 | 4,168 bytes | **417** | - + | FastNode[int] | 368 | 10×8=80 | 5×16=80 | 528 bytes | **53** | + ¹ Values assume childRef = 16 bytes and pointer to payload = 8 bytes ## Extended Memory Example with Path Compression @@ -88,7 +81,7 @@ The actual payload struct referenced by the pointer is **not included** in these |-----------|------|-----------|-----------|----------|-----------|------------|-------------------| | **LiteNode** | 96 B | 0 | 15×16=240 B | 336 B | +160 B | +0 B | **16.8** | | **BartNode[V]** | 112 B | 10×8=80 B | 15×16=240 B | 432 B | +200 B | +40 B | **21.6** | -| **FastNode[V]** | 4,168 B | 0 | 0 | 4,168 B | +200 B | +40 B | **208.4** | +| **FastNode[V]** | 368 B | 10×8=80 B | 15×16=240 B | 688 B | +200 B | +40 B | **34.4** | **Notes:** 1. Prefixes: LiteNode stores no values (0B); BartNode/FastNode store 10×8B pointers to payload structs @@ -114,26 +107,25 @@ The actual payload struct referenced by the pointer is **not included** in these - **Reduced trie depth**: Fewer levels = fewer lookups per route **Important:** The actual payload structs (e.g., routing information, metadata) are stored externally and referenced by the 8-byte pointers. Their sizes are not included in the per-node calculations above, as they are shared or application-specific. - + ## Lookup Performance Deep Dive - + All three node types achieve **O(1) per-level lookup performance**, but must traverse trie levels: - + ### Trie Structure & Performance - **8-bit strides per level**: Each trie level handles 8 bits of the IP address - **IPv4 traversal**: Worst case 4 levels (32÷8), real-world typically 3 levels for /24 routes - **IPv6 traversal**: Worst case 16 levels (128÷8), real-world typically 6 levels for /48 routes - **Performance characteristic**: O(trie_depth) not O(number_of_routes) - **IPv6 vs IPv4**: IPv6 inherently ~2× slower due to deeper tree structure - + ### BartNode[V] & LiteNode - Optimized Level Operations - **Precomputed lookup tables** (`lpm.LookupTbl[idx]`) eliminate search within each level - **BitSet256 intersections** via `IntersectionTop()` for instant prefix matching - **Rank-based indirection**: Bitset-to-slice mapping uses precomputed Rank masks - **Pipeline-friendly**: Only 4 bitset operations (4×uint64) per level, optimized for CPU pipelining - **No backtracking**: Traditional longest-prefix-match backtracking replaced with direct table lookups - -### FastNode[V] - Direct Array Access per Level -- **Zero indirection per level**: Direct array indexing `prefixes[idx]` and `children[idx]` -- **Cache-optimal**: Contiguous memory layout within each level -- **Performance advantage**: Still ~40% faster per level despite sparse optimizations + +### FastNode[V] - Cached Child Access per Level +- **No rank recomputation per level**: Uses `childRankCache[octet]` to index sparse child storage quickly. +- **Performance advantage**: ~25% faster level traversal diff --git a/vendor/github.com/gaissmai/bart/README.md b/vendor/github.com/gaissmai/bart/README.md index 190abb66cd..ab51af4a88 100644 --- a/vendor/github.com/gaissmai/bart/README.md +++ b/vendor/github.com/gaissmai/bart/README.md @@ -3,7 +3,6 @@ [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go) [![CI](https://github.com/gaissmai/bart/actions/workflows/go.yml/badge.svg)](https://github.com/gaissmai/bart/actions/workflows/go.yml) [![Coverage Status](https://coveralls.io/repos/github/gaissmai/bart/badge.svg)](https://coveralls.io/github/gaissmai/bart) -[![Go Report Card](https://goreportcard.com/badge/github.com/gaissmai/bart)](https://goreportcard.com/report/github.com/gaissmai/bart) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) @@ -38,12 +37,8 @@ sparse arrays for **level compression**. Combined with a **novel path and fringe compression**, this design reduces memory consumption by nearly two orders of magnitude compared to classical ART. -For **bart.Fast** this binary tree is represented with fixed arrays -without level compression (classical ART), but combined with the same -novel **path and fringe compression** from BART. This design -reduces memory consumption by more than an order of magnitude compared -to classical ART and thus makes ART usable in the first place for large -routing tables. +For **bart.Fast**, an additional 256 bytes per node are used to +achieve faster level traversal in the multibit trie. **bart.Lite** is a special form of **bart.Table**, but without a payload, and therefore has the lowest memory overhead while maintaining the same lookup times. @@ -110,12 +105,13 @@ func main() { | Aspect | Table | Lite | Fast | |--------|-------------|-------------|-------------| - | **Per-level Speed** | ⚡ **O(1)** | ⚡ **O(1)** | 🚀 **O(1), 50-100% faster** | + | **Per-level Speed** | ⚡ **O(1)** | ⚡ **O(1)** | 🚀 **O(1), ~25% faster** | | **Overall Lookup** | O(trie_depth) | O(trie_depth) | O(trie_depth) | | **IPv4 Performance** | ~3 level traversals | ~3 level traversals | ~3 level traversals | | **IPv6 Performance** | ~6 level traversals | ~6 level traversals | ~6 level traversals | | **IPv6 vs IPv4** | ~2× slower | ~2× slower | ~2× slower | - | **Memory** | efficient | very efficient | inefficient | + | **Memory** | efficient | very efficient | less efficient | + | **Update** | very fast | best | fast | A more detailed description can be found in [NODETYPES.md](NODETYPES.md). @@ -124,15 +120,18 @@ A more detailed description can be found in [NODETYPES.md](NODETYPES.md). ### 🎯 **bart.Table[V]** - The Balanced Choice - **Recommended** for most routing table use cases - Near-optimal per-level performance with excellent memory efficiency +- Very fast updates (insert/delete) - Perfect balance for both IPv4 and IPv6 routing tables (use it for RIB) ### 🪶 **bart.Lite** - The Minimalist - **Specialized** for prefix-only operations, no payload -- Same per-level performance as *bart.Table[V]* but 35% less memory +- Same per-level performance as *bart.Table[V]* but ~35% less memory +- Optimal updates (insert/delete), ~25% faster than *bart.Table[V]* - Ideal for IPv4/IPv6 allowlists and set-based operations (use it for ACL) -### 🚀 **bart.Fast[V]** - The Performance Champion -- **50-100% faster** when memory constraints allow +### 🚀 **bart.Fast[V]** - The Lookup Performance Champion +- **~25% faster** when memory constraints allow +- Fast updates (insert/delete), ~50% slower than *bart.Table[V]* - Best choice for lookup-intensive applications (use it for FIB) ## Bitset Efficiency @@ -268,11 +267,13 @@ $ GOAMD64=v3 go test -run=xxx -bench=FullFastM/Lookup$ -cpu=1 goos: linux goarch: amd64 pkg: github.com/gaissmai/bart -cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz -BenchmarkFullFastMatch4/Lookup 124476082 9.63 ns/op -BenchmarkFullFastMatch6/Lookup 91032597 13.20 ns/op -BenchmarkFullFastMiss4/Lookup 134171480 8.93 ns/op -BenchmarkFullFastMiss6/Lookup 75634396 15.79 ns/op +cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics +BenchmarkFullFastMatch4/Lookup 129484557 9.274 ns/op +BenchmarkFullFastMatch6/Lookup 65185531 17.99 ns/op +BenchmarkFullFastMiss4/Lookup 92448910 12.81 ns/op +BenchmarkFullFastMiss6/Lookup 64323817 18.66 ns/op +PASS +ok github.com/gaissmai/bart 6.647s ``` ## Compatibility Guarantees diff --git a/vendor/github.com/gaissmai/bart/bart.go b/vendor/github.com/gaissmai/bart/bart.go index 6f2c17e6f7..f7e860a018 100644 --- a/vendor/github.com/gaissmai/bart/bart.go +++ b/vendor/github.com/gaissmai/bart/bart.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package bart @@ -16,13 +16,14 @@ import ( // // The zero value is ready to use. // +// A Table must not be copied by value; always pass by pointer. +// Nil pointers as receivers or arguments are forbidden and will panic. +// // The Table is safe for concurrent reads, but concurrent reads and writes // must be externally synchronized. Mutation via Insert/Delete requires locks, // or alternatively, use ...Persist methods which return a modified copy // without altering the original table (copy-on-write). // -// A Table must not be copied by value; always pass by pointer. -// // Performance note: Do not pass IPv4-in-IPv6 addresses (e.g., ::ffff:192.0.2.1) // as input. The methods do not perform automatic unmapping to avoid unnecessary // overhead for the common case where native addresses are used. @@ -191,7 +192,7 @@ func (t *Table[V]) Lookup(ip netip.Addr) (val V, ok bool) { LOOP: // find leaf node for depth, octet = range octets { - depth = depth & nodes.DepthMask // BCE, Lookup must be fast + depth &= nodes.DepthMask // BCE, Lookup must be fast // push current node on stack for fast backtracking stack[depth] = n @@ -224,7 +225,7 @@ LOOP: // start backtracking, unwind the stack, bounds check eliminated for ; depth >= 0; depth-- { - depth = depth & nodes.DepthMask // BCE + depth &= nodes.DepthMask // BCE n = stack[depth] @@ -260,7 +261,7 @@ func (t *Table[V]) LookupPrefix(pfx netip.Prefix) (val V, ok bool) { // match any address in the provided prefix range. // // This is functionally identical to LookupPrefix but additionally returns the -// matching prefix (lpmPfx) itself along with the value. +// matching LPM prefix itself along with the value. // // This method is slower than LookupPrefix and should only be used if the // matching lpm entry is also required for other reasons. @@ -280,10 +281,10 @@ func (t *Table[V]) lookupPrefixLPM(pfx netip.Prefix, withLPM bool) (lpmPfx netip pfx = pfx.Masked() ip := pfx.Addr() - bits := pfx.Bits() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := nodes.LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := nodes.DivMod8(pfxLen) n := t.rootNodeByVersion(is4) @@ -296,10 +297,10 @@ func (t *Table[V]) lookupPrefixLPM(pfx netip.Prefix, withLPM bool) (lpmPfx netip LOOP: // find the last node on the octets path in the trie, for depth, octet = range octets { - depth = depth & nodes.DepthMask // BCE + depth &= nodes.DepthMask // BCE // stepped one past the last stride of interest; back up to last and break - if depth > lastOctetPlusOne { + if depth > strideCount { depth-- break } @@ -320,7 +321,7 @@ LOOP: case *nodes.LeafNode[V]: // reached a path compressed prefix, stop traversing - if kid.Prefix.Bits() > bits || !kid.Prefix.Contains(ip) { + if kid.Prefix.Bits() > pfxLen || !kid.Prefix.Contains(ip) { break LOOP } return kid.Prefix, kid.Value, true @@ -329,7 +330,7 @@ LOOP: // the bits of the fringe are defined by the depth // maybe the LPM isn't needed, saves some cycles fringeBits := (depth + 1) << 3 - if fringeBits > bits { + if fringeBits > pfxLen { break LOOP } @@ -347,7 +348,7 @@ LOOP: // start backtracking, unwind the stack for ; depth >= 0; depth-- { - depth = depth & nodes.DepthMask // BCE + depth &= nodes.DepthMask // BCE n = stack[depth] @@ -356,15 +357,13 @@ LOOP: continue } - // only the lastOctet may have a different prefix len - // all others are just host routes var idx uint8 octet = octets[depth] - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4 or 16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx = art.PfxToIdx(octet, lastBits) + + // only the final stride may have a different prefix len + // all others are just host routes + if depth == strideCount { + idx = art.PfxToIdx(octet, modBits) } else { idx = art.OctetToIdx(octet) } diff --git a/vendor/github.com/gaissmai/bart/bartmethodsgenerated.go b/vendor/github.com/gaissmai/bart/bartmethodsgenerated.go index d7e67abc3f..a75cba6660 100644 --- a/vendor/github.com/gaissmai/bart/bartmethodsgenerated.go +++ b/vendor/github.com/gaissmai/bart/bartmethodsgenerated.go @@ -1,6 +1,6 @@ // Code generated from file "commonmethods_tmpl.go"; DO NOT EDIT. -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package bart @@ -266,9 +266,6 @@ func (t *Table[V]) ModifyPersist(pfx netip.Prefix, cb func(_ V, ok bool) (_ V, d // Returns an empty iterator if the prefix is invalid. func (t *Table[V]) Supernets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } if !pfx.IsValid() { return } @@ -298,9 +295,6 @@ func (t *Table[V]) Supernets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] { // Returns an empty iterator if the prefix is invalid. func (t *Table[V]) Subnets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } if !pfx.IsValid() { return } @@ -351,15 +345,12 @@ func (t *Table[V]) OverlapsPrefix(pfx netip.Prefix) bool { // This is useful for conflict detection, policy enforcement, // or validating mutually exclusive routing domains. func (t *Table[V]) Overlaps(o *Table[V]) bool { - if o == nil { - return false - } return t.Overlaps4(o) || t.Overlaps6(o) } // Overlaps4 is like [Table.Overlaps] but for the v4 routing table only. func (t *Table[V]) Overlaps4(o *Table[V]) bool { - if o == nil || t.size4 == 0 || o.size4 == 0 { + if t.size4 == 0 || o.size4 == 0 { return false } return t.root4.Overlaps(&o.root4, 0) @@ -367,7 +358,7 @@ func (t *Table[V]) Overlaps4(o *Table[V]) bool { // Overlaps6 is like [Table.Overlaps] but for the v6 routing table only. func (t *Table[V]) Overlaps6(o *Table[V]) bool { - if o == nil || t.size6 == 0 || o.size6 == 0 { + if t.size6 == 0 || o.size6 == 0 { return false } return t.root6.Overlaps(&o.root6, 0) @@ -380,7 +371,15 @@ func (t *Table[V]) Overlaps6(o *Table[V]) bool { // This duplicate is shallow-copied by default, but if the value type V implements the // Clone method, the value is deeply cloned before insertion. See also Table.Clone. func (t *Table[V]) Union(o *Table[V]) { - if o == nil || o == t || (o.size4 == 0 && o.size6 == 0) { + // panics on nil receiver + _ = t.size4 + + // panics on nil argument + if o.size4 == 0 && o.size6 == 0 { + return + } + // t is unchanged + if o == t { return } @@ -398,10 +397,17 @@ func (t *Table[V]) Union(o *Table[V]) { // UnionPersist is similar to [Union] but the receiver isn't modified. // // All nodes touched during union are cloned and a new *Table is returned. -// If o is nil or empty, no nodes are touched and the receiver may be +// If o is empty, no nodes are touched and the receiver may be // returned unchanged. func (t *Table[V]) UnionPersist(o *Table[V]) *Table[V] { - if o == nil || o == t || (o.size4 == 0 && o.size6 == 0) { + // panics on nil receiver + _ = t.size4 + + // panics on nil argument + if o.size4 == 0 && o.size6 == 0 { + return t + } + if o == t { return t } @@ -439,21 +445,16 @@ func (t *Table[V]) UnionPersist(o *Table[V]) *Table[V] { // It ensures both trees (IPv4-based and IPv6-based) have the same sizes and // recursively compares their root nodes. // -// Value comparisons use reflect.DeepEqual by default. To avoid the potentially -// expensive reflect.DeepEqual, the payload type V can provide custom equality -// by implementing the following method: +// If V implements an `Equal(V) bool` method, its custom equality logic is used. +// Otherwise, values are compared directly using the == operator. // -// Equal(other V) bool +// Note: If V implements `Equal(V) bool` with a pointer receiver, the Equal +// method should handle nil receivers gracefully. // -// Example: -// -// type MyValue struct { ID int } -// func (v MyValue) Equal(other MyValue) bool { return v.ID == other.ID } -// -// The bart package will automatically detect and use this method via Go's -// structural typing. +// ATTENTION: If V is not comparable at runtime (such as a slice or map without an `Equal` +// method), a runtime panic will occur. func (t *Table[V]) Equal(o *Table[V]) bool { - if o == nil || t.size4 != o.size4 || t.size6 != o.size6 { + if t.size4 != o.size4 || t.size6 != o.size6 { return false } if o == t { @@ -478,11 +479,10 @@ func (t *Table[V]) Equal(o *Table[V]) bool { // // The bart package will automatically detect and use this method via Go's // structural typing. +// +// Note: If V implements Clone() V with a pointer receiver, the Clone +// method should handle nil receivers gracefully. func (t *Table[V]) Clone() *Table[V] { - if t == nil { - return nil - } - c := new(Table[V]) cloneFn := value.CloneFnFactory[V]() @@ -513,35 +513,14 @@ func (t *Table[V]) Size6() int { // All returns an iterator over all prefix–value pairs in the table. // -// The entries from both IPv4 and IPv6 subtries are yielded using an internal recursive traversal. -// The iteration order is unspecified and may vary between calls; for a stable order, use AllSorted. -// -// You can use All directly in a for-range loop without providing a yield function. -// The Go compiler automatically synthesizes the yield callback for you: -// -// for prefix, value := range t.All() { -// fmt.Println(prefix, value) -// } +// The iteration order is unspecified and may vary between calls; for a stable order, +// use [Table.AllSorted]. // -// Under the hood, the loop body is passed as a yield function to the iterator. -// If you break or return from the loop, iteration stops early as expected. -// -// IMPORTANT: Modifying or deleting entries during iteration is not allowed, +// IMPORTANT: Modifying the table during iteration is not allowed, // as this would interfere with the internal traversal and may corrupt or -// prematurely terminate the iteration. If mutation of the table during -// traversal is required use persistent table methods, e.g. -// -// pt := t // shallow copy of t -// for pfx, val := range t.All() { -// if cond(pfx, val) { -// pt = pt.DeletePersist(pfx) -// } -// } +// prematurely terminate the iteration. func (t *Table[V]) All() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRec(stridePath{}, 0, true, yield) && t.root6.AllRec(stridePath{}, 0, false, yield) } } @@ -549,9 +528,6 @@ func (t *Table[V]) All() iter.Seq2[netip.Prefix, V] { // All4 is like [Table.All] but only for the v4 routing table. func (t *Table[V]) All4() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRec(stridePath{}, 0, true, yield) } } @@ -559,35 +535,14 @@ func (t *Table[V]) All4() iter.Seq2[netip.Prefix, V] { // All6 is like [Table.All] but only for the v6 routing table. func (t *Table[V]) All6() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root6.AllRec(stridePath{}, 0, false, yield) } } -// AllSorted returns an iterator over all prefix–value pairs in the table, -// ordered in canonical CIDR prefix sort order. -// -// This can be used directly with a for-range loop; -// the Go compiler provides the yield function implicitly: -// -// for prefix, value := range t.AllSorted() { -// fmt.Println(prefix, value) -// } -// -// The traversal is stable and predictable across calls. -// Iteration stops early if you break out of the loop. -// -// IMPORTANT: Deleting entries during iteration is not allowed, -// as this would interfere with the internal traversal and may corrupt or -// prematurely terminate the iteration. If mutation of the table during -// traversal is required use persistent table methods. +// AllSorted is like [Table.All] but the iteration is ordered in canonical +// CIDR prefix sort order. func (t *Table[V]) AllSorted() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRecSorted(stridePath{}, 0, true, yield) && t.root6.AllRecSorted(stridePath{}, 0, false, yield) } @@ -596,9 +551,6 @@ func (t *Table[V]) AllSorted() iter.Seq2[netip.Prefix, V] { // AllSorted4 is like [Table.AllSorted] but only for the v4 routing table. func (t *Table[V]) AllSorted4() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRecSorted(stridePath{}, 0, true, yield) } } @@ -606,9 +558,6 @@ func (t *Table[V]) AllSorted4() iter.Seq2[netip.Prefix, V] { // AllSorted6 is like [Table.AllSorted] but only for the v6 routing table. func (t *Table[V]) AllSorted6() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root6.AllRecSorted(stridePath{}, 0, false, yield) } } @@ -636,12 +585,9 @@ func (t *Table[V]) AllSorted6() iter.Seq2[netip.Prefix, V] { // │ └─ 2001:db8::/32 (V) // └─ fe80::/10 (V) func (t *Table[V]) Fprint(w io.Writer) error { - if w == nil { + if w == nil && t != nil { return fmt.Errorf("nil writer") } - if t == nil { - return nil - } // v4 if err := t.fprint(w, true); err != nil { @@ -691,10 +637,6 @@ func (t *Table[V]) MarshalText() ([]byte, error) { // MarshalJSON dumps the table into two sorted lists: for ipv4 and ipv6. // Every root and subnet is an array, not a map, because the order matters. func (t *Table[V]) MarshalJSON() ([]byte, error) { - if t == nil { - return []byte("null"), nil - } - result := struct { Ipv4 []DumpListNode[V] `json:"ipv4,omitempty"` Ipv6 []DumpListNode[V] `json:"ipv6,omitempty"` @@ -714,18 +656,12 @@ func (t *Table[V]) MarshalJSON() ([]byte, error) { // DumpList4 dumps the ipv4 tree into a list of roots and their subnets. // It can be used to analyze the tree or build the text or JSON serialization. func (t *Table[V]) DumpList4() []DumpListNode[V] { - if t == nil { - return nil - } return t.dumpListRec(&t.root4, 0, stridePath{}, 0, true) } // DumpList6 dumps the ipv6 tree into a list of roots and their subnets. // It can be used to analyze the tree or build custom JSON representation. func (t *Table[V]) DumpList6() []DumpListNode[V] { - if t == nil { - return nil - } return t.dumpListRec(&t.root6, 0, stridePath{}, 0, false) } @@ -775,10 +711,6 @@ func (t *Table[V]) dumpString() string { // dump the table structure and all the nodes to w. func (t *Table[V]) dump(w io.Writer) { - if t == nil { - return - } - if t.size4 > 0 { stats := t.root4.StatsRec() fmt.Fprintln(w) diff --git a/vendor/github.com/gaissmai/bart/common.go b/vendor/github.com/gaissmai/bart/common.go index e079b9db26..0b8d6ef640 100644 --- a/vendor/github.com/gaissmai/bart/common.go +++ b/vendor/github.com/gaissmai/bart/common.go @@ -1,20 +1,22 @@ -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT // Package bart provides high-performance Balanced Routing Tables (BART) -// for fastest IP-to-CIDR lookups on IPv4 and IPv6 addresses. +// for fast IP-to-CIDR lookups on IPv4 and IPv6 addresses. // // BART offers three table variants optimized for different use cases: // // - Lite: Memory-optimized with popcount-compressed sparse arrays // - Table: Full-featured with popcount-compressed sparse arrays -// - Fast: Speed-optimized with fixed-size 256-element arrays +// - Fast: Speed-optimized with additional rank caching // // The implementation is based on Knuth's ART algorithm with novel // optimizations for memory efficiency and lookup speed. // // `Table` and `Lite` use popcount compression for memory efficiency, while -// `Fast` trades memory for maximum lookup speed with uncompressed arrays. +// `Fast` trades additional memory and slower updates for maximum lookup +// speed: a child-rank cache is maintained on every insert/delete, so +// lookups avoid popcount but updates pay the caching cost. // // BART excels at efficient set operations on routing tables including Union, // Overlaps, Equal, Subnets, and Supernets with optimal complexity, making it diff --git a/vendor/github.com/gaissmai/bart/commonmethods_tmpl.go b/vendor/github.com/gaissmai/bart/commonmethods_tmpl.go index 710760b38e..2ed9ab0835 100644 --- a/vendor/github.com/gaissmai/bart/commonmethods_tmpl.go +++ b/vendor/github.com/gaissmai/bart/commonmethods_tmpl.go @@ -1,6 +1,6 @@ //go:build generate -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT //go:generate ./scripts/generate-table-methods.sh @@ -327,9 +327,6 @@ func (t *_TABLE_TYPE[V]) ModifyPersist(pfx netip.Prefix, cb func(_ V, ok bool) ( // Returns an empty iterator if the prefix is invalid. func (t *_TABLE_TYPE[V]) Supernets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } if !pfx.IsValid() { return } @@ -359,9 +356,6 @@ func (t *_TABLE_TYPE[V]) Supernets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] // Returns an empty iterator if the prefix is invalid. func (t *_TABLE_TYPE[V]) Subnets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } if !pfx.IsValid() { return } @@ -412,15 +406,12 @@ func (t *_TABLE_TYPE[V]) OverlapsPrefix(pfx netip.Prefix) bool { // This is useful for conflict detection, policy enforcement, // or validating mutually exclusive routing domains. func (t *_TABLE_TYPE[V]) Overlaps(o *_TABLE_TYPE[V]) bool { - if o == nil { - return false - } return t.Overlaps4(o) || t.Overlaps6(o) } // Overlaps4 is like [_TABLE_TYPE.Overlaps] but for the v4 routing table only. func (t *_TABLE_TYPE[V]) Overlaps4(o *_TABLE_TYPE[V]) bool { - if o == nil || t.size4 == 0 || o.size4 == 0 { + if t.size4 == 0 || o.size4 == 0 { return false } return t.root4.Overlaps(&o.root4, 0) @@ -428,7 +419,7 @@ func (t *_TABLE_TYPE[V]) Overlaps4(o *_TABLE_TYPE[V]) bool { // Overlaps6 is like [_TABLE_TYPE.Overlaps] but for the v6 routing table only. func (t *_TABLE_TYPE[V]) Overlaps6(o *_TABLE_TYPE[V]) bool { - if o == nil || t.size6 == 0 || o.size6 == 0 { + if t.size6 == 0 || o.size6 == 0 { return false } return t.root6.Overlaps(&o.root6, 0) @@ -441,7 +432,15 @@ func (t *_TABLE_TYPE[V]) Overlaps6(o *_TABLE_TYPE[V]) bool { // This duplicate is shallow-copied by default, but if the value type V implements the // Clone method, the value is deeply cloned before insertion. See also _TABLE_TYPE.Clone. func (t *_TABLE_TYPE[V]) Union(o *_TABLE_TYPE[V]) { - if o == nil || o == t || (o.size4 == 0 && o.size6 == 0) { + // panics on nil receiver + _ = t.size4 + + // panics on nil argument + if o.size4 == 0 && o.size6 == 0 { + return + } + // t is unchanged + if o == t { return } @@ -459,10 +458,17 @@ func (t *_TABLE_TYPE[V]) Union(o *_TABLE_TYPE[V]) { // UnionPersist is similar to [Union] but the receiver isn't modified. // // All nodes touched during union are cloned and a new *_TABLE_TYPE is returned. -// If o is nil or empty, no nodes are touched and the receiver may be +// If o is empty, no nodes are touched and the receiver may be // returned unchanged. func (t *_TABLE_TYPE[V]) UnionPersist(o *_TABLE_TYPE[V]) *_TABLE_TYPE[V] { - if o == nil || o == t || (o.size4 == 0 && o.size6 == 0) { + // panics on nil receiver + _ = t.size4 + + // panics on nil argument + if o.size4 == 0 && o.size6 == 0 { + return t + } + if o == t { return t } @@ -500,21 +506,16 @@ func (t *_TABLE_TYPE[V]) UnionPersist(o *_TABLE_TYPE[V]) *_TABLE_TYPE[V] { // It ensures both trees (IPv4-based and IPv6-based) have the same sizes and // recursively compares their root nodes. // -// Value comparisons use reflect.DeepEqual by default. To avoid the potentially -// expensive reflect.DeepEqual, the payload type V can provide custom equality -// by implementing the following method: +// If V implements an `Equal(V) bool` method, its custom equality logic is used. +// Otherwise, values are compared directly using the == operator. // -// Equal(other V) bool +// Note: If V implements `Equal(V) bool` with a pointer receiver, the Equal +// method should handle nil receivers gracefully. // -// Example: -// -// type MyValue struct { ID int } -// func (v MyValue) Equal(other MyValue) bool { return v.ID == other.ID } -// -// The bart package will automatically detect and use this method via Go's -// structural typing. +// ATTENTION: If V is not comparable at runtime (such as a slice or map without an `Equal` +// method), a runtime panic will occur. func (t *_TABLE_TYPE[V]) Equal(o *_TABLE_TYPE[V]) bool { - if o == nil || t.size4 != o.size4 || t.size6 != o.size6 { + if t.size4 != o.size4 || t.size6 != o.size6 { return false } if o == t { @@ -539,11 +540,10 @@ func (t *_TABLE_TYPE[V]) Equal(o *_TABLE_TYPE[V]) bool { // // The bart package will automatically detect and use this method via Go's // structural typing. +// +// Note: If V implements Clone() V with a pointer receiver, the Clone +// method should handle nil receivers gracefully. func (t *_TABLE_TYPE[V]) Clone() *_TABLE_TYPE[V] { - if t == nil { - return nil - } - c := new(_TABLE_TYPE[V]) cloneFn := value.CloneFnFactory[V]() @@ -574,35 +574,14 @@ func (t *_TABLE_TYPE[V]) Size6() int { // All returns an iterator over all prefix–value pairs in the table. // -// The entries from both IPv4 and IPv6 subtries are yielded using an internal recursive traversal. -// The iteration order is unspecified and may vary between calls; for a stable order, use AllSorted. -// -// You can use All directly in a for-range loop without providing a yield function. -// The Go compiler automatically synthesizes the yield callback for you: -// -// for prefix, value := range t.All() { -// fmt.Println(prefix, value) -// } +// The iteration order is unspecified and may vary between calls; for a stable order, +// use [_TABLE_TYPE.AllSorted]. // -// Under the hood, the loop body is passed as a yield function to the iterator. -// If you break or return from the loop, iteration stops early as expected. -// -// IMPORTANT: Modifying or deleting entries during iteration is not allowed, +// IMPORTANT: Modifying the table during iteration is not allowed, // as this would interfere with the internal traversal and may corrupt or -// prematurely terminate the iteration. If mutation of the table during -// traversal is required use persistent table methods, e.g. -// -// pt := t // shallow copy of t -// for pfx, val := range t.All() { -// if cond(pfx, val) { -// pt = pt.DeletePersist(pfx) -// } -// } +// prematurely terminate the iteration. func (t *_TABLE_TYPE[V]) All() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRec(stridePath{}, 0, true, yield) && t.root6.AllRec(stridePath{}, 0, false, yield) } } @@ -610,9 +589,6 @@ func (t *_TABLE_TYPE[V]) All() iter.Seq2[netip.Prefix, V] { // All4 is like [_TABLE_TYPE.All] but only for the v4 routing table. func (t *_TABLE_TYPE[V]) All4() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRec(stridePath{}, 0, true, yield) } } @@ -620,35 +596,14 @@ func (t *_TABLE_TYPE[V]) All4() iter.Seq2[netip.Prefix, V] { // All6 is like [_TABLE_TYPE.All] but only for the v6 routing table. func (t *_TABLE_TYPE[V]) All6() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root6.AllRec(stridePath{}, 0, false, yield) } } -// AllSorted returns an iterator over all prefix–value pairs in the table, -// ordered in canonical CIDR prefix sort order. -// -// This can be used directly with a for-range loop; -// the Go compiler provides the yield function implicitly: -// -// for prefix, value := range t.AllSorted() { -// fmt.Println(prefix, value) -// } -// -// The traversal is stable and predictable across calls. -// Iteration stops early if you break out of the loop. -// -// IMPORTANT: Deleting entries during iteration is not allowed, -// as this would interfere with the internal traversal and may corrupt or -// prematurely terminate the iteration. If mutation of the table during -// traversal is required use persistent table methods. +// AllSorted is like [_TABLE_TYPE.All] but the iteration is ordered in canonical +// CIDR prefix sort order. func (t *_TABLE_TYPE[V]) AllSorted() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRecSorted(stridePath{}, 0, true, yield) && t.root6.AllRecSorted(stridePath{}, 0, false, yield) } @@ -657,9 +612,6 @@ func (t *_TABLE_TYPE[V]) AllSorted() iter.Seq2[netip.Prefix, V] { // AllSorted4 is like [_TABLE_TYPE.AllSorted] but only for the v4 routing table. func (t *_TABLE_TYPE[V]) AllSorted4() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRecSorted(stridePath{}, 0, true, yield) } } @@ -667,9 +619,6 @@ func (t *_TABLE_TYPE[V]) AllSorted4() iter.Seq2[netip.Prefix, V] { // AllSorted6 is like [_TABLE_TYPE.AllSorted] but only for the v6 routing table. func (t *_TABLE_TYPE[V]) AllSorted6() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root6.AllRecSorted(stridePath{}, 0, false, yield) } } @@ -697,12 +646,9 @@ func (t *_TABLE_TYPE[V]) AllSorted6() iter.Seq2[netip.Prefix, V] { // │ └─ 2001:db8::/32 (V) // └─ fe80::/10 (V) func (t *_TABLE_TYPE[V]) Fprint(w io.Writer) error { - if w == nil { + if w == nil && t != nil { return fmt.Errorf("nil writer") } - if t == nil { - return nil - } // v4 if err := t.fprint(w, true); err != nil { @@ -752,10 +698,6 @@ func (t *_TABLE_TYPE[V]) MarshalText() ([]byte, error) { // MarshalJSON dumps the table into two sorted lists: for ipv4 and ipv6. // Every root and subnet is an array, not a map, because the order matters. func (t *_TABLE_TYPE[V]) MarshalJSON() ([]byte, error) { - if t == nil { - return []byte("null"), nil - } - result := struct { Ipv4 []DumpListNode[V] `json:"ipv4,omitempty"` Ipv6 []DumpListNode[V] `json:"ipv6,omitempty"` @@ -775,18 +717,12 @@ func (t *_TABLE_TYPE[V]) MarshalJSON() ([]byte, error) { // DumpList4 dumps the ipv4 tree into a list of roots and their subnets. // It can be used to analyze the tree or build the text or JSON serialization. func (t *_TABLE_TYPE[V]) DumpList4() []DumpListNode[V] { - if t == nil { - return nil - } return t.dumpListRec(&t.root4, 0, stridePath{}, 0, true) } // DumpList6 dumps the ipv6 tree into a list of roots and their subnets. // It can be used to analyze the tree or build custom JSON representation. func (t *_TABLE_TYPE[V]) DumpList6() []DumpListNode[V] { - if t == nil { - return nil - } return t.dumpListRec(&t.root6, 0, stridePath{}, 0, false) } @@ -836,10 +772,6 @@ func (t *_TABLE_TYPE[V]) dumpString() string { // dump the table structure and all the nodes to w. func (t *_TABLE_TYPE[V]) dump(w io.Writer) { - if t == nil { - return - } - if t.size4 > 0 { stats := t.root4.StatsRec() fmt.Fprintln(w) diff --git a/vendor/github.com/gaissmai/bart/commontests_tmpl.go b/vendor/github.com/gaissmai/bart/commontests_tmpl.go index f910d52a64..ef73ab08e4 100644 --- a/vendor/github.com/gaissmai/bart/commontests_tmpl.go +++ b/vendor/github.com/gaissmai/bart/commontests_tmpl.go @@ -1,6 +1,6 @@ //go:build generate -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT //go:generate ./scripts/generate-table-tests.sh @@ -100,9 +100,11 @@ func (t *_TABLE_TYPE[V]) flatSorted() golden.Table[V] { return flat } -func TestTableNil__TABLE_TYPE(t *testing.T) { +func TestTableNilReceiver__TABLE_TYPE(t *testing.T) { t.Parallel() + var tbl1 *_TABLE_TYPE[any] = nil + ip4 := mpa("127.0.0.1") ip6 := mpa("::1") @@ -110,21 +112,12 @@ func TestTableNil__TABLE_TYPE(t *testing.T) { pfx6 := mpp("::1/128") tbl2 := new(_TABLE_TYPE[any]) - tbl2.Insert(pfx4, nil) - tbl2.Insert(pfx6, nil) + tbl2.Insert(pfx4, true) + tbl2.Insert(pfx6, true) - var tbl1 *_TABLE_TYPE[any] = nil - - t.Run("mustPanic", func(t *testing.T) { + t.Run("mustPanicNilReceiver", func(t *testing.T) { t.Parallel() - mustPanic(t, "sizeUpdate", func() { tbl1.sizeUpdate(true, 1) }) - mustPanic(t, "sizeUpdate", func() { tbl1.sizeUpdate(false, 1) }) - mustPanic(t, "rootNodeByVersion", func() { tbl1.rootNodeByVersion(true) }) - mustPanic(t, "rootNodeByVersion", func() { tbl1.rootNodeByVersion(false) }) - mustPanic(t, "fprint", func() { tbl1.fprint(nil, true) }) - mustPanic(t, "fprint", func() { tbl1.fprint(nil, false) }) - mustPanic(t, "Size", func() { tbl1.Size() }) mustPanic(t, "Size4", func() { tbl1.Size4() }) mustPanic(t, "Size6", func() { tbl1.Size6() }) @@ -140,52 +133,48 @@ func TestTableNil__TABLE_TYPE(t *testing.T) { mustPanic(t, "Lookup", func() { tbl1.Lookup(ip6) }) mustPanic(t, "LookupPrefix", func() { tbl1.LookupPrefix(pfx4) }) mustPanic(t, "LookupPrefixLPM", func() { tbl1.LookupPrefixLPM(pfx4) }) + mustPanic(t, "Clone", func() { tbl1.Clone() }) mustPanic(t, "Union", func() { tbl1.Union(tbl2) }) mustPanic(t, "UnionPersist", func() { tbl1.UnionPersist(tbl2) }) - }) - - t.Run("noPanic", func(t *testing.T) { - t.Parallel() - - noPanic(t, "Overlaps", func() { tbl1.Overlaps(nil) }) - noPanic(t, "Overlaps4", func() { tbl1.Overlaps4(nil) }) - noPanic(t, "Overlaps6", func() { tbl1.Overlaps6(nil) }) - - noPanic(t, "Overlaps", func() { tbl2.Overlaps(tbl2) }) - noPanic(t, "Overlaps4", func() { tbl2.Overlaps4(tbl2) }) - noPanic(t, "Overlaps6", func() { tbl2.Overlaps6(tbl2) }) - mustPanic(t, "Overlaps", func() { tbl1.Overlaps(tbl2) }) mustPanic(t, "Overlaps4", func() { tbl1.Overlaps4(tbl2) }) mustPanic(t, "Overlaps6", func() { tbl1.Overlaps6(tbl2) }) mustPanic(t, "OverlapsPrefix", func() { tbl1.OverlapsPrefix(pfx4) }) mustPanic(t, "OverlapsPrefix", func() { tbl1.OverlapsPrefix(pfx6) }) - mustPanic(t, "Equal", func() { tbl1.Equal(tbl2) }) - noPanic(t, "Equal", func() { tbl1.Equal(tbl1) }) - noPanic(t, "Equal", func() { tbl2.Equal(tbl2) }) - - noPanic(t, "dump", func() { tbl1.dump(nil) }) - noPanic(t, "dumpString", func() { tbl1.dumpString() }) - noPanic(t, "Clone", func() { tbl1.Clone() }) - noPanic(t, "DumpList4", func() { tbl1.DumpList4() }) - noPanic(t, "DumpList6", func() { tbl1.DumpList6() }) - noPanic(t, "Fprint", func() { tbl1.Fprint(nil) }) - noPanic(t, "MarshalJSON", func() { _, _ = tbl1.MarshalJSON() }) - noPanic(t, "MarshalText", func() { _, _ = tbl1.MarshalText() }) + mustPanic(t, "DumpList4", func() { tbl1.DumpList4() }) + mustPanic(t, "DumpList6", func() { tbl1.DumpList6() }) + mustPanic(t, "Fprint", func() { tbl1.Fprint(nil) }) + mustPanic(t, "MarshalJSON", func() { _, _ = tbl1.MarshalJSON() }) + mustPanic(t, "MarshalText", func() { _, _ = tbl1.MarshalText() }) + + mustPanicRangeOverFunc[any](t, "All", tbl1.All) + mustPanicRangeOverFunc[any](t, "All4", tbl1.All4) + mustPanicRangeOverFunc[any](t, "All6", tbl1.All6) + mustPanicRangeOverFunc[any](t, "AllSorted", tbl1.AllSorted) + mustPanicRangeOverFunc[any](t, "AllSorted4", tbl1.AllSorted4) + mustPanicRangeOverFunc[any](t, "AllSorted6", tbl1.AllSorted6) + mustPanicRangeOverFunc[any](t, "Subnets", tbl1.Subnets) + mustPanicRangeOverFunc[any](t, "Supernets", tbl1.Supernets) }) +} + +func TestTableNilArguments__TABLE_TYPE(t *testing.T) { + t.Parallel() - t.Run("noPanicRangeOverFunc", func(t *testing.T) { + t.Run("mustPanicNilArgs", func(t *testing.T) { t.Parallel() + tbl := new(_TABLE_TYPE[bool]) - noPanicRangeOverFunc[any](t, "All", tbl1.All) - noPanicRangeOverFunc[any](t, "All4", tbl1.All4) - noPanicRangeOverFunc[any](t, "All6", tbl1.All6) - noPanicRangeOverFunc[any](t, "AllSorted", tbl1.AllSorted) - noPanicRangeOverFunc[any](t, "AllSorted4", tbl1.AllSorted4) - noPanicRangeOverFunc[any](t, "AllSorted6", tbl1.AllSorted6) - noPanicRangeOverFunc[any](t, "Subnets", tbl1.Subnets) - noPanicRangeOverFunc[any](t, "Supernets", tbl1.Supernets) + tbl.Insert(mpp("127.0.0.1/32"), true) + tbl.Insert(mpp("::1/128"), true) + + mustPanic(t, "Union(nil)", func() { tbl.Union(nil) }) + mustPanic(t, "UnionPersist(nil)", func() { tbl.UnionPersist(nil) }) + mustPanic(t, "Overlaps(nil)", func() { tbl.Overlaps(nil) }) + mustPanic(t, "Overlaps4(nil)", func() { tbl.Overlaps4(nil) }) + mustPanic(t, "Overlaps6(nil)", func() { tbl.Overlaps6(nil) }) + mustPanic(t, "Equal(nil)", func() { tbl.Equal(nil) }) }) } @@ -1372,12 +1361,7 @@ func TestTableClone__TABLE_TYPE(t *testing.T) { prng := rand.New(rand.NewPCG(42, 42)) pfxs := random.RealWorldPrefixes(prng, n) - var tbl *_TABLE_TYPE[int] - if tbl.Clone() != nil { - t.Fatal("expected nil") - } - - tbl = new(_TABLE_TYPE[int]) + tbl := new(_TABLE_TYPE[int]) for i, pfx := range pfxs { tbl.Insert(pfx, i) } @@ -2263,3 +2247,53 @@ func TestTableEqual__TABLE_TYPE(t *testing.T) { } }) } + +func TestTableExtra__TABLE_TYPE(t *testing.T) { + t.Parallel() + + // 1. Equal: checking self equality + t1 := new(_TABLE_TYPE[int]) + t1.Insert(mpp("10.0.0.0/8"), 42) + t1.Insert(mpp("2001:db8::/32"), 42) + + if !t1.Equal(t1) { + t.Error("expected table to be equal to itself") + } + + // 2. Equal: checking different size4 / size6 + t2 := new(_TABLE_TYPE[int]) + t2.Insert(mpp("10.0.0.0/8"), 42) + if t1.Equal(t2) { + t.Error("expected false for different size6") + } + + t3 := new(_TABLE_TYPE[int]) + t3.Insert(mpp("2001:db8::/32"), 42) + if t1.Equal(t3) { + t.Error("expected false for different size4") + } + + // 3. Overlaps6: checking when size6 is 0 + emptyT := new(_TABLE_TYPE[int]) + if t1.Overlaps6(emptyT) { + t.Error("expected false when other table has no IPv6 prefixes") + } + if emptyT.Overlaps6(t1) { + t.Error("expected false when this table has no IPv6 prefixes") + } + + // 4. Fprint: nil writer check + var nilWriter io.Writer = nil + if err := t1.Fprint(nilWriter); err == nil { + t.Error("expected error when writing to nil writer") + } + + // 5. ModifyPersist: no-op deletion of non-existing key + t4 := new(_TABLE_TYPE[int]) + t5 := t4.ModifyPersist(mpp("10.0.0.0/8"), func(val int, ok bool) (int, bool) { + return 0, true + }) + if t5 != t4 { + t.Error("expected same table pointer for no-op ModifyPersist") + } +} diff --git a/vendor/github.com/gaissmai/bart/fast.go b/vendor/github.com/gaissmai/bart/fast.go index 3a8ba35009..db1c710939 100644 --- a/vendor/github.com/gaissmai/bart/fast.go +++ b/vendor/github.com/gaissmai/bart/fast.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package bart @@ -8,29 +8,25 @@ import ( "sync" "github.com/gaissmai/bart/internal/art" + "github.com/gaissmai/bart/internal/lpm" "github.com/gaissmai/bart/internal/nodes" - "github.com/gaissmai/bart/internal/value" ) -// Fast follows the ART design by Knuth in using fixed arrays at each level -// combined with the same path and fringe compression invented for BART. +// Fast is similar to Table but uses additional 256 bytes in each node +// for faster level traversing in the multibit trie. // -// As a result Fast sacrifices memory efficiency to achieve 50-100% higher -// speed. +// As a result Fast sacrifices memory efficiency to achieve 25% better +// speed in lookup and contains. // // The zero value is ready to use. -// A Fast table must not be copied by value; always pass by pointer. // -// The payload type MUST NOT be a zero-sized value (for example: struct{} -// or [0]byte). According to the Go specification: +// A Fast must not be copied by value; always pass by pointer. +// Nil pointers as receivers or arguments are forbidden and will panic. // -// "Pointers to distinct zero-size variables may or may not be equal." -// -// However, the ART algorithm requires that different prefixes, even with -// identical payload V, result in distinct value pointers. This is not -// guaranteed for zero-sized values; thus, zero-sized types as payload V -// would result in undefined behavior so the input methods panic if they -// detect it. +// The Fast is safe for concurrent reads, but concurrent reads and writes +// must be externally synchronized. Mutation via Insert/Delete requires locks, +// or alternatively, use ...Persist methods which return a modified copy +// without altering the original table (copy-on-write). // // Performance note: Do not pass IPv4-in-IPv6 addresses (e.g., ::ffff:192.0.2.1) // as input. The methods do not perform automatic unmapping to avoid unnecessary @@ -38,10 +34,10 @@ import ( // Users should unmap IPv4-in-IPv6 addresses to their native IPv4 form // (e.g., 192.0.2.1) before calling these methods. type Fast[V any] struct { - // used for zero-sized type checks during insert - once sync.Once + // used by -copylocks checker from `go vet`. + _ [0]sync.Mutex - // the root nodes are fast nodes with fixed size arrays + // the root nodes, implemented as popcount compressed multibit tries root4 nodes.FastNode[V] root6 nodes.FastNode[V] @@ -50,7 +46,7 @@ type Fast[V any] struct { size6 int } -// rootNodeByVersion, root node getter for ip version and trie levels. +// rootNodeByVersion, root node getter for ip version. func (f *Fast[V]) rootNodeByVersion(is4 bool) *nodes.FastNode[V] { if is4 { return &f.root4 @@ -65,7 +61,6 @@ func (f *Fast[V]) rootNodeByVersion(is4 bool) *nodes.FastNode[V] { // The prefix is automatically canonicalized using pfx.Masked() to ensure // consistent behavior regardless of host bits in the input. func (f *Fast[V]) Insert(pfx netip.Prefix, val V) { - f.once.Do(value.PanicOnZST[V]) f.insert(pfx, val) } @@ -73,7 +68,7 @@ func (f *Fast[V]) Insert(pfx netip.Prefix, val V) { // // All nodes touched during insert are cloned and a new Fast is returned. // This is not a full [Fast.Clone], all untouched nodes are still referenced -// from both Tables. +// from both Fasts. // // If the payload type V contains pointers or needs deep copying, // it must implement the Clone method to support correct cloning. @@ -81,7 +76,6 @@ func (f *Fast[V]) Insert(pfx netip.Prefix, val V) { // Due to cloning overhead this is significantly slower than Insert, // typically taking μsec instead of nsec. func (f *Fast[V]) InsertPersist(pfx netip.Prefix, val V) *Fast[V] { - f.once.Do(value.PanicOnZST[V]) return f.insertPersist(pfx, val) } @@ -92,6 +86,8 @@ func (f *Fast[V]) InsertPersist(pfx netip.Prefix, val V) *Fast[V] { // return a new value and a delete flag: del == false inserts or updates, // del == true deletes the entry if it exists (otherwise no-op). // +// The callback is invoked at most once per call. +// // The operation is determined by the callback function, which is called with: // // val: the current value (or zero value if not found) @@ -105,14 +101,13 @@ func (f *Fast[V]) InsertPersist(pfx netip.Prefix, val V) *Fast[V] { // Summary of callback semantics: // // | cb-input | cb-return | Ops | -// ------------------------------------- -------- +// |-----------------|-----------------|--------| // | (zero, false) | (_, true) | no-op | // | (zero, false) | (newVal, false) | insert | // | (oldVal, true) | (newVal, false) | update | // | (oldVal, true) | (_, true) | delete | // ------------------------------------- -------- func (f *Fast[V]) Modify(pfx netip.Prefix, cb func(_ V, ok bool) (_ V, del bool)) { - f.once.Do(value.PanicOnZST[V]) if !pfx.IsValid() { return } @@ -134,9 +129,9 @@ func (f *Fast[V]) Modify(pfx netip.Prefix, cb func(_ V, ok bool) (_ V, del bool) // This performs longest-prefix matching and returns true if any prefix // in the routing table contains the IP address, regardless of the associated value. // -// It does not return the value nor the prefix of the matching item, +// It does not return the value or the prefix of the matching item, // but as a test against an allow-/deny-list it's often sufficient -// and even few nanoseconds faster than [Table.Lookup]. +// and even few nanoseconds faster than [Fast.Lookup]. func (f *Fast[V]) Contains(ip netip.Addr) bool { // speed is top priority: no explicit test for ip.IsValid // if ip is invalid, AsSlice() returns nil, Contains returns false. @@ -144,28 +139,27 @@ func (f *Fast[V]) Contains(ip netip.Addr) bool { n := f.rootNodeByVersion(is4) for _, octet := range ip.AsSlice() { - if n.Contains(art.OctetToIdx(octet)) { + // for contains, any lpm match is good enough, no backtracking needed + if n.PrefixCount() != 0 && n.Contains(art.OctetToIdx(octet)) { return true } - kidAny, exists := n.GetChild(octet) - if !exists { - // no next node + // stop traversing? + if !n.Children.Test(octet) { return false } + kid := n.MustGetChild(octet) // kid is node or leaf or fringe at octet - switch kid := kidAny.(type) { + switch kid := kid.(type) { case *nodes.FastNode[V]: - n = kid // continue + n = kid // descend down to next trie level case *nodes.FringeNode[V]: // fringe is the default-route for all possible octets below return true case *nodes.LeafNode[V]: - // due to path compression, the octet path between - // leaf and prefix may diverge return kid.Prefix.Contains(ip) } } @@ -173,56 +167,83 @@ func (f *Fast[V]) Contains(ip netip.Addr) bool { return false } -// Lookup performs longest-prefix matching for the given IP address and returns -// the associated value of the most specific matching prefix. -// Returns the zero value of V and false if no prefix matches. -// Returns false for invalid IP addresses. +// Lookup performs a longest prefix match (LPM) lookup for the given address. +// It finds the most specific (longest) prefix in the routing table that +// contains the given address and returns its associated value. // -// This is the core routing table operation used for packet forwarding decisions. +// This is the fundamental operation for IP routing decisions, finding the +// best matching route for a destination address. // -// Its semantics are identical to [Table.Lookup]. +// Returns the associated value and true if a matching prefix is found. +// Returns zero value and false if no prefix contains the address. func (f *Fast[V]) Lookup(ip netip.Addr) (val V, ok bool) { if !ip.IsValid() { return val, ok } is4 := ip.Is4() + octets := ip.AsSlice() + n := f.rootNodeByVersion(is4) - for _, octet := range ip.AsSlice() { - // save the current best LPM val, lookup is cheap for nodes.FastNode - if bestLPM, ok2 := n.Lookup(art.OctetToIdx(octet)); ok2 { - val = bestLPM - ok = ok2 - } + // stack of the traversed nodes for fast backtracking, if needed + stack := [nodes.MaxTreeDepth]*nodes.FastNode[V]{} + + // run variable, used after for loop + var depth int + var octet byte + +LOOP: + // find leaf node + for depth, octet = range octets { + depth &= nodes.DepthMask // BCE, Lookup must be fast - kidAny, exists := n.GetChild(octet) - if !exists { - // no next node - return val, ok + // push current node on stack for fast backtracking + stack[depth] = n + + // go down in tight loop to last octet + if !n.Children.Test(octet) { + // no more nodes below octet + break LOOP } + kid := n.MustGetChild(octet) - // next kid is fast, fringe or leaf node. - switch kid := kidAny.(type) { + // kid is node or leaf or fringe at octet + switch kid := kid.(type) { case *nodes.FastNode[V]: n = kid + continue LOOP // descend down to next trie level case *nodes.FringeNode[V]: - // fringe is the default-route for all possible nodes below + // fringe is the default-route for all possible octets below return kid.Value, true case *nodes.LeafNode[V]: - // due to path compression, the octet path between - // leaf and prefix may diverge if kid.Prefix.Contains(ip) { return kid.Value, true } - // maybe there is a current best value from upper levels - return val, ok + // reached a path compressed prefix, stop traversing + break LOOP + } + } + + // start backtracking, unwind the stack, bounds check eliminated + for ; depth >= 0; depth-- { + depth &= nodes.DepthMask // BCE + + n = stack[depth] + + // longest prefix match, skip if node has no prefixes + if n.PrefixCount() != 0 { + idx := art.OctetToIdx(octets[depth]) + // lookupIdx() manually inlined + if lpmIdx, ok2 := n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx]); ok2 { + return n.MustGetPrefix(lpmIdx), ok2 + } } } - panic("unreachable") + return val, ok } // LookupPrefix performs a longest prefix match lookup for any address within @@ -244,7 +265,7 @@ func (f *Fast[V]) LookupPrefix(pfx netip.Prefix) (val V, ok bool) { // match any address in the provided prefix range. // // This is functionally identical to LookupPrefix but additionally returns the -// matching prefix (lpmPfx) itself along with the value. +// matching LPM prefix itself along with the value. // // This method is slower than LookupPrefix and should only be used if the // matching lpm entry is also required for other reasons. @@ -264,10 +285,10 @@ func (f *Fast[V]) lookupPrefixLPM(pfx netip.Prefix, withLPM bool) (lpmPfx netip. pfx = pfx.Masked() ip := pfx.Addr() - bits := pfx.Bits() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := nodes.LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := nodes.DivMod8(pfxLen) n := f.rootNodeByVersion(is4) @@ -280,10 +301,10 @@ func (f *Fast[V]) lookupPrefixLPM(pfx netip.Prefix, withLPM bool) (lpmPfx netip. LOOP: // find the last node on the octets path in the trie, for depth, octet = range octets { - depth = depth & nodes.DepthMask // BCE + depth &= nodes.DepthMask // BCE // stepped one past the last stride of interest; back up to last and break - if depth > lastOctetPlusOne { + if depth > strideCount { depth-- break } @@ -291,20 +312,20 @@ LOOP: stack[depth] = n // go down in tight loop to leaf node - kidAny, exists := n.GetChild(octet) - if !exists { + if !n.Children.Test(octet) { break LOOP } + kid := n.MustGetChild(octet) // kid is node or leaf or fringe at octet - switch kid := kidAny.(type) { + switch kid := kid.(type) { case *nodes.FastNode[V]: n = kid continue LOOP // descend down to next trie level case *nodes.LeafNode[V]: // reached a path compressed prefix, stop traversing - if kid.Prefix.Bits() > bits || !kid.Prefix.Contains(ip) { + if kid.Prefix.Bits() > pfxLen || !kid.Prefix.Contains(ip) { break LOOP } return kid.Prefix, kid.Value, true @@ -313,7 +334,7 @@ LOOP: // the bits of the fringe are defined by the depth // maybe the LPM isn't needed, saves some cycles fringeBits := (depth + 1) << 3 - if fringeBits > bits { + if fringeBits > pfxLen { break LOOP } @@ -331,47 +352,47 @@ LOOP: // start backtracking, unwind the stack for ; depth >= 0; depth-- { - depth = depth & nodes.DepthMask // BCE + depth &= nodes.DepthMask // BCE n = stack[depth] // longest prefix match, skip if node has no prefixes - if n.PfxCount == 0 { + if n.Prefixes.Len() == 0 { continue } - // only the lastOctet may have a different prefix len - // all others are just host routes var idx uint8 octet = octets[depth] - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4 or 16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx = art.PfxToIdx(octet, lastBits) + + // only the final stride may have a different prefix len + // all others are just host routes + if depth == strideCount { + idx = art.PfxToIdx(octet, modBits) } else { idx = art.OctetToIdx(octet) } - switch withLPM { - case false: // LookupPrefix - if val, ok = n.Lookup(idx); ok { + // manually inlined: lookupIdx(idx) + var topIdx uint8 + if topIdx, ok = n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx]); ok { + val = n.MustGetPrefix(topIdx) + + // called from LookupPrefix + if !withLPM { return netip.Prefix{}, val, ok } - case true: // LookupPrefixLPM - if lpmIdx, val2, ok2 := n.LookupIdx(idx); ok2 { - // get the bits from depth and lpmIdx - pfxBits := int(art.PfxBits(depth, lpmIdx)) + // called from LookupPrefixLPM - // calculate the lpmPfx from incoming ip and new mask - // netip.Addr.Prefix canonicalizes. Invariant: art.PfxBits(depth, topIdx) - // yields a valid mask (v4: 0..32, v6: 0..128), so error is impossible. - lpmPfx, _ = ip.Prefix(pfxBits) - return lpmPfx, val2, ok2 - } + // get the bits from depth and top idx + pfxBits := int(art.PfxBits(depth, topIdx)) + + // calculate the lpmPfx from incoming ip and new mask + // netip.Addr.Prefix canonicalizes. Invariant: art.PfxBits(depth, topIdx) + // yields a valid mask (v4: 0..32, v6: 0..128), so error is impossible. + lpmPfx, _ = ip.Prefix(pfxBits) + return lpmPfx, val, ok } - // continue rewinding the stack } return lpmPfx, val, ok diff --git a/vendor/github.com/gaissmai/bart/fastmethodsgenerated.go b/vendor/github.com/gaissmai/bart/fastmethodsgenerated.go index 5c72ef4c55..c6521b0617 100644 --- a/vendor/github.com/gaissmai/bart/fastmethodsgenerated.go +++ b/vendor/github.com/gaissmai/bart/fastmethodsgenerated.go @@ -1,6 +1,6 @@ // Code generated from file "commonmethods_tmpl.go"; DO NOT EDIT. -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package bart @@ -266,9 +266,6 @@ func (t *Fast[V]) ModifyPersist(pfx netip.Prefix, cb func(_ V, ok bool) (_ V, de // Returns an empty iterator if the prefix is invalid. func (t *Fast[V]) Supernets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } if !pfx.IsValid() { return } @@ -298,9 +295,6 @@ func (t *Fast[V]) Supernets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] { // Returns an empty iterator if the prefix is invalid. func (t *Fast[V]) Subnets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } if !pfx.IsValid() { return } @@ -351,15 +345,12 @@ func (t *Fast[V]) OverlapsPrefix(pfx netip.Prefix) bool { // This is useful for conflict detection, policy enforcement, // or validating mutually exclusive routing domains. func (t *Fast[V]) Overlaps(o *Fast[V]) bool { - if o == nil { - return false - } return t.Overlaps4(o) || t.Overlaps6(o) } // Overlaps4 is like [Fast.Overlaps] but for the v4 routing table only. func (t *Fast[V]) Overlaps4(o *Fast[V]) bool { - if o == nil || t.size4 == 0 || o.size4 == 0 { + if t.size4 == 0 || o.size4 == 0 { return false } return t.root4.Overlaps(&o.root4, 0) @@ -367,7 +358,7 @@ func (t *Fast[V]) Overlaps4(o *Fast[V]) bool { // Overlaps6 is like [Fast.Overlaps] but for the v6 routing table only. func (t *Fast[V]) Overlaps6(o *Fast[V]) bool { - if o == nil || t.size6 == 0 || o.size6 == 0 { + if t.size6 == 0 || o.size6 == 0 { return false } return t.root6.Overlaps(&o.root6, 0) @@ -380,7 +371,15 @@ func (t *Fast[V]) Overlaps6(o *Fast[V]) bool { // This duplicate is shallow-copied by default, but if the value type V implements the // Clone method, the value is deeply cloned before insertion. See also Fast.Clone. func (t *Fast[V]) Union(o *Fast[V]) { - if o == nil || o == t || (o.size4 == 0 && o.size6 == 0) { + // panics on nil receiver + _ = t.size4 + + // panics on nil argument + if o.size4 == 0 && o.size6 == 0 { + return + } + // t is unchanged + if o == t { return } @@ -398,10 +397,17 @@ func (t *Fast[V]) Union(o *Fast[V]) { // UnionPersist is similar to [Union] but the receiver isn't modified. // // All nodes touched during union are cloned and a new *Fast is returned. -// If o is nil or empty, no nodes are touched and the receiver may be +// If o is empty, no nodes are touched and the receiver may be // returned unchanged. func (t *Fast[V]) UnionPersist(o *Fast[V]) *Fast[V] { - if o == nil || o == t || (o.size4 == 0 && o.size6 == 0) { + // panics on nil receiver + _ = t.size4 + + // panics on nil argument + if o.size4 == 0 && o.size6 == 0 { + return t + } + if o == t { return t } @@ -439,21 +445,16 @@ func (t *Fast[V]) UnionPersist(o *Fast[V]) *Fast[V] { // It ensures both trees (IPv4-based and IPv6-based) have the same sizes and // recursively compares their root nodes. // -// Value comparisons use reflect.DeepEqual by default. To avoid the potentially -// expensive reflect.DeepEqual, the payload type V can provide custom equality -// by implementing the following method: +// If V implements an `Equal(V) bool` method, its custom equality logic is used. +// Otherwise, values are compared directly using the == operator. // -// Equal(other V) bool +// Note: If V implements `Equal(V) bool` with a pointer receiver, the Equal +// method should handle nil receivers gracefully. // -// Example: -// -// type MyValue struct { ID int } -// func (v MyValue) Equal(other MyValue) bool { return v.ID == other.ID } -// -// The bart package will automatically detect and use this method via Go's -// structural typing. +// ATTENTION: If V is not comparable at runtime (such as a slice or map without an `Equal` +// method), a runtime panic will occur. func (t *Fast[V]) Equal(o *Fast[V]) bool { - if o == nil || t.size4 != o.size4 || t.size6 != o.size6 { + if t.size4 != o.size4 || t.size6 != o.size6 { return false } if o == t { @@ -478,11 +479,10 @@ func (t *Fast[V]) Equal(o *Fast[V]) bool { // // The bart package will automatically detect and use this method via Go's // structural typing. +// +// Note: If V implements Clone() V with a pointer receiver, the Clone +// method should handle nil receivers gracefully. func (t *Fast[V]) Clone() *Fast[V] { - if t == nil { - return nil - } - c := new(Fast[V]) cloneFn := value.CloneFnFactory[V]() @@ -513,35 +513,14 @@ func (t *Fast[V]) Size6() int { // All returns an iterator over all prefix–value pairs in the table. // -// The entries from both IPv4 and IPv6 subtries are yielded using an internal recursive traversal. -// The iteration order is unspecified and may vary between calls; for a stable order, use AllSorted. -// -// You can use All directly in a for-range loop without providing a yield function. -// The Go compiler automatically synthesizes the yield callback for you: -// -// for prefix, value := range t.All() { -// fmt.Println(prefix, value) -// } +// The iteration order is unspecified and may vary between calls; for a stable order, +// use [Fast.AllSorted]. // -// Under the hood, the loop body is passed as a yield function to the iterator. -// If you break or return from the loop, iteration stops early as expected. -// -// IMPORTANT: Modifying or deleting entries during iteration is not allowed, +// IMPORTANT: Modifying the table during iteration is not allowed, // as this would interfere with the internal traversal and may corrupt or -// prematurely terminate the iteration. If mutation of the table during -// traversal is required use persistent table methods, e.g. -// -// pt := t // shallow copy of t -// for pfx, val := range t.All() { -// if cond(pfx, val) { -// pt = pt.DeletePersist(pfx) -// } -// } +// prematurely terminate the iteration. func (t *Fast[V]) All() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRec(stridePath{}, 0, true, yield) && t.root6.AllRec(stridePath{}, 0, false, yield) } } @@ -549,9 +528,6 @@ func (t *Fast[V]) All() iter.Seq2[netip.Prefix, V] { // All4 is like [Fast.All] but only for the v4 routing table. func (t *Fast[V]) All4() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRec(stridePath{}, 0, true, yield) } } @@ -559,35 +535,14 @@ func (t *Fast[V]) All4() iter.Seq2[netip.Prefix, V] { // All6 is like [Fast.All] but only for the v6 routing table. func (t *Fast[V]) All6() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root6.AllRec(stridePath{}, 0, false, yield) } } -// AllSorted returns an iterator over all prefix–value pairs in the table, -// ordered in canonical CIDR prefix sort order. -// -// This can be used directly with a for-range loop; -// the Go compiler provides the yield function implicitly: -// -// for prefix, value := range t.AllSorted() { -// fmt.Println(prefix, value) -// } -// -// The traversal is stable and predictable across calls. -// Iteration stops early if you break out of the loop. -// -// IMPORTANT: Deleting entries during iteration is not allowed, -// as this would interfere with the internal traversal and may corrupt or -// prematurely terminate the iteration. If mutation of the table during -// traversal is required use persistent table methods. +// AllSorted is like [Fast.All] but the iteration is ordered in canonical +// CIDR prefix sort order. func (t *Fast[V]) AllSorted() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRecSorted(stridePath{}, 0, true, yield) && t.root6.AllRecSorted(stridePath{}, 0, false, yield) } @@ -596,9 +551,6 @@ func (t *Fast[V]) AllSorted() iter.Seq2[netip.Prefix, V] { // AllSorted4 is like [Fast.AllSorted] but only for the v4 routing table. func (t *Fast[V]) AllSorted4() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRecSorted(stridePath{}, 0, true, yield) } } @@ -606,9 +558,6 @@ func (t *Fast[V]) AllSorted4() iter.Seq2[netip.Prefix, V] { // AllSorted6 is like [Fast.AllSorted] but only for the v6 routing table. func (t *Fast[V]) AllSorted6() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root6.AllRecSorted(stridePath{}, 0, false, yield) } } @@ -636,12 +585,9 @@ func (t *Fast[V]) AllSorted6() iter.Seq2[netip.Prefix, V] { // │ └─ 2001:db8::/32 (V) // └─ fe80::/10 (V) func (t *Fast[V]) Fprint(w io.Writer) error { - if w == nil { + if w == nil && t != nil { return fmt.Errorf("nil writer") } - if t == nil { - return nil - } // v4 if err := t.fprint(w, true); err != nil { @@ -691,10 +637,6 @@ func (t *Fast[V]) MarshalText() ([]byte, error) { // MarshalJSON dumps the table into two sorted lists: for ipv4 and ipv6. // Every root and subnet is an array, not a map, because the order matters. func (t *Fast[V]) MarshalJSON() ([]byte, error) { - if t == nil { - return []byte("null"), nil - } - result := struct { Ipv4 []DumpListNode[V] `json:"ipv4,omitempty"` Ipv6 []DumpListNode[V] `json:"ipv6,omitempty"` @@ -714,18 +656,12 @@ func (t *Fast[V]) MarshalJSON() ([]byte, error) { // DumpList4 dumps the ipv4 tree into a list of roots and their subnets. // It can be used to analyze the tree or build the text or JSON serialization. func (t *Fast[V]) DumpList4() []DumpListNode[V] { - if t == nil { - return nil - } return t.dumpListRec(&t.root4, 0, stridePath{}, 0, true) } // DumpList6 dumps the ipv6 tree into a list of roots and their subnets. // It can be used to analyze the tree or build custom JSON representation. func (t *Fast[V]) DumpList6() []DumpListNode[V] { - if t == nil { - return nil - } return t.dumpListRec(&t.root6, 0, stridePath{}, 0, false) } @@ -775,10 +711,6 @@ func (t *Fast[V]) dumpString() string { // dump the table structure and all the nodes to w. func (t *Fast[V]) dump(w io.Writer) { - if t == nil { - return - } - if t.size4 > 0 { stats := t.root4.StatsRec() fmt.Fprintln(w) diff --git a/vendor/github.com/gaissmai/bart/internal/allot/allotroutesgenerated.go b/vendor/github.com/gaissmai/bart/internal/allot/allotroutesgenerated.go index 9c36649155..3ae92858d8 100644 --- a/vendor/github.com/gaissmai/bart/internal/allot/allotroutesgenerated.go +++ b/vendor/github.com/gaissmai/bart/internal/allot/allotroutesgenerated.go @@ -1,6 +1,6 @@ // Code generated by generate_allottbl.go; DO NOT EDIT. -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package allot diff --git a/vendor/github.com/gaissmai/bart/internal/allot/generate_allottbl.go b/vendor/github.com/gaissmai/bart/internal/allot/generate_allottbl.go index ba555aebb8..467e4843cd 100644 --- a/vendor/github.com/gaissmai/bart/internal/allot/generate_allottbl.go +++ b/vendor/github.com/gaissmai/bart/internal/allot/generate_allottbl.go @@ -2,7 +2,7 @@ //go:generate go run $GOFILE -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT // Package main generates lookup tables for route allocation in the BART routing table. @@ -202,7 +202,7 @@ func asString(tbl [256]bitset.BitSet256) string { // their structure and usage. const codeTemplate = `// Code generated by {{.File}}; DO NOT EDIT. -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package allot diff --git a/vendor/github.com/gaissmai/bart/internal/art/base_index.go b/vendor/github.com/gaissmai/bart/internal/art/base_index.go index 06bcaed714..8323304c4a 100644 --- a/vendor/github.com/gaissmai/bart/internal/art/base_index.go +++ b/vendor/github.com/gaissmai/bart/internal/art/base_index.go @@ -1,15 +1,15 @@ -// Copyright (c) 2024 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT // Package art summarizes the functions and inverse functions // for mapping between a prefix and a baseIndex. // -// can inline IdxToPfx with cost 37 -// can inline IdxToRange with cost 68 -// can inline NetMask with cost 14 -// can inline OctetToIdx with cost 5 +// can inline OctetToIdx with cost 6 +// can inline PfxToIdx with cost 10 +// can inline NetMask with cost 13 // can inline PfxBits with cost 21 -// can inline PfxToIdx with cost 17 +// can inline IdxToPfx with cost 39 +// can inline IdxToRange with cost 71 package art import "math/bits" @@ -29,17 +29,26 @@ import "math/bits" // + ----------------------- // 0b0000_1101 = 13 // -// Panics if `pfxLen > 7`. +// ATTENTION: For pfxLen values > 7 the result is undefined! func PfxToIdx(octet, pfxLen uint8) uint8 { - if pfxLen > 7 { - panic("PfxToIdx: invalid pfxLen > 7") - } - return octet>>(8-pfxLen) + 1<>(8-pfxLen) | 1< [128..255] to start +// with the parent (>>1) in the complete binary tree. +// +// Same formula as PfxToIdx, but for octet/8 and a shift (>>1). +// +// octet>>(8-pfxLen) + 1<>(8-8) + 1<<8 +// octet + 256 +// +// got to parent idx: +// (octet+256)>>1 == octet>>1 + 128 func OctetToIdx(octet uint8) uint8 { - return 128 + octet>>1 + // use '|' instead of '+', maybe a little bit faster + return octet>>1 | 128 } // IdxToPfx returns the octet and prefix len of baseIdx. diff --git a/vendor/github.com/gaissmai/bart/internal/bitset/bitset256.go b/vendor/github.com/gaissmai/bart/internal/bitset/bitset256.go index 1fc0568f63..5e4f70f9ff 100644 --- a/vendor/github.com/gaissmai/bart/internal/bitset/bitset256.go +++ b/vendor/github.com/gaissmai/bart/internal/bitset/bitset256.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT // Package bitset provides a compact and efficient implementation of a fixed-length @@ -24,9 +24,9 @@ package bitset // can inline (*BitSet256).FirstSet with cost 79 // can inline (*BitSet256).Intersects with cost 48 // can inline (*BitSet256).Intersection with cost 53 -// can inline (*BitSet256).IntersectionTop with cost 42 +// can inline (*BitSet256).IntersectionTop with cost 67 // can inline (*BitSet256).IsEmpty with cost 22 -// can inline (*BitSet256).LastSet with cost 37 +// can inline (*BitSet256).LastSet with cost 75 // can inline (*BitSet256).NextSet with cost 65 // can inline (*BitSet256).Rank with cost 57 // can inline (*BitSet256).Set with cost 12 @@ -55,20 +55,21 @@ import ( // not factored out as functions to make most of the methods // inlineable with minimal costs. -// BitSet256 represents a fixed size bitset from [0..255] +// BitSet256 represents a fixed-size bitset for the range [0..255], +// stored as four uint64 words (256 bits total). type BitSet256 [4]uint64 -// Set sets the bit. +// Set sets the bit at position bit (0..255). func (b *BitSet256) Set(bit uint8) { b[bit>>6] |= 1 << (bit & 63) } -// Clear clears the bit. +// Clear clears the bit at position bit (0..255). func (b *BitSet256) Clear(bit uint8) { b[bit>>6] &^= 1 << (bit & 63) } -// Test if the bit is set. +// Test reports whether the bit at position bit (0..255) is set. func (b *BitSet256) Test(bit uint8) (ok bool) { return b[bit>>6]&(1<<(bit&63)) != 0 } @@ -160,7 +161,7 @@ func (b *BitSet256) NextSet(bit uint8) (next uint8, ok bool) { // // It searches the bitset in descending order and returns the position of the // first bit (top bit) with value 1. If at least one bit is set, ok is true. -// If no bits are set, ok is false and last is undefined. +// If no bits are set, ok is false and last is 0. // // Example: // @@ -170,43 +171,40 @@ func (b *BitSet256) NextSet(bit uint8) (next uint8, ok bool) { // bs.Set(214) // index, ok := bs.LastSet() // index == 214, ok == true func (b *BitSet256) LastSet() (last uint8, ok bool) { - // optimized for pipelining, sorry, can't inline, cost 81>80 - // try it again when Go supports SIMD intrinsics - // - // ### b3 := bits.Len64(b[3]) - // ### b2 := bits.Len64(b[2]) - // ### b1 := bits.Len64(b[1]) - // ### b0 := bits.Len64(b[0]) - - // ### if b3 != 0 { - // ### return uint8(b3 + 191), true - // ### } - // ### if b2 != 0 { - // ### return uint8(b2 + 127), true - // ### } - // ### if b1 != 0 { - // ### return uint8(b1 + 63), true - // ### } - // ### if b0 != 0 { - // ### return uint8(b0 - 1), true - // ### } - // ### return - - for wIdx := 3; wIdx >= 0; wIdx-- { - if word := b[wIdx]; word != 0 { - //nolint:gosec // G115: integer overflow conversion int -> uint - return uint8(wIdx<<6 + bits.Len64(word) - 1), true - } + // optimized by unrolling the loop. + // This enables compiler inlining and is ~20% faster. + if b[3] != 0 { + //nolint:gosec // G115: integer overflow conversion int -> uint + return uint8(bits.Len64(b[3]) + 191), true } - return + if b[2] != 0 { + //nolint:gosec // G115: integer overflow conversion int -> uint + return uint8(bits.Len64(b[2]) + 127), true + } + if b[1] != 0 { + //nolint:gosec // G115: integer overflow conversion int -> uint + return uint8(bits.Len64(b[1]) + 63), true + } + if b[0] != 0 { + //nolint:gosec // G115: integer overflow conversion int -> uint + return uint8(bits.Len64(b[0]) - 1), true + } + return 0, false } -// AsSlice returns a slice containing all set bits in the BitSet256. +// AsSlice extracts the indices of all set bits in the BitSet256, returning them +// as uint8 values in strictly ascending order. // -// The bits are returned in ascending order as uint8 values. The provided buf -// must be a pointer to an array of 256 uint8s; it is used as backing -// storage for the result to avoid heap allocations. The returned slice shares -// its backing array with buf and is only valid until buf is modified or reused. +// Performance Considerations: +// To guarantee zero heap allocations and enable compiler inlining, the caller must +// provide a pointer to a 256-byte array (`buf`) as backing storage. The method +// populates this array in-place and returns a sliced view (`[]uint8`) tailored to +// the actual number of set bits. +// +// Safety and Lifecycle: +// The returned slice directly shares the underlying storage of `buf` and is only +// valid until `buf` is modified or reused. This pattern is highly recommended for +// hot paths and performance-critical loops where heap churn must be avoided. func (b *BitSet256) AsSlice(buf *[256]uint8) []uint8 { size := 0 for wIdx, word := range b { @@ -217,30 +215,41 @@ func (b *BitSet256) AsSlice(buf *[256]uint8) []uint8 { } } + // tailor to the actual number of set bits return buf[:size] } -// Bits returns a slice containing all set bits in the BitSet256. -// -// The bits are returned in ascending order as uint8 values. Bits allocates -// a new slice on the heap for the result. For allocation-free collection, -// use [AsSlice] with a pre-allocated buffer. +// Bits returns a slice containing the indices of all set bits in strictly +// ascending order as uint8 values. // -// Example usage: +// Performance Considerations: +// Unlike [AsSlice], this method dynamically allocates a new slice on the +// heap to store the result. It is designed for convenience and APIs where the lifecycle +// of the returned slice needs to outlive the immediate caller's stack frame. // -// bits := b.Bits() -// // bits now contains the indices of all set bits in b +// Usage Guidance: +// Use Bits when convenience is preferred over raw performance, or when the result +// must be returned across boundaries where stack-allocated buffers cannot safely escape. +// For high-throughput or allocation-free processing, prefer [AsSlice]. func (b *BitSet256) Bits() []uint8 { return b.AsSlice(&[256]uint8{}) } -// IntersectionTop computes the intersection of base set with the compare set. -// If the result set isn't empty, it returns the top most set bit and true. +// IntersectionTop computes the intersection of the receiver with c +// and returns the highest (top-most) set bit of the result. +// If the intersection is non-empty, it returns the top bit index and true. +// If the intersection is empty, ok is false and top is 0. func (b *BitSet256) IntersectionTop(c *BitSet256) (top uint8, ok bool) { - for wIdx := 3; wIdx >= 0; wIdx-- { - if word := b[wIdx] & c[wIdx]; word != 0 { + // optimized by unrolling the first word check. + // This enables compiler inlining and is ~15% faster. + if w := b[3] & c[3]; w != 0 { + //nolint:gosec // G115: integer overflow conversion int -> uint8 + return uint8(191 + bits.Len64(w)), true + } + for wIdx := 2; wIdx >= 0; wIdx-- { + if w := b[wIdx] & c[wIdx]; w != 0 { //nolint:gosec // G115: integer overflow conversion int -> uint8 - return uint8(wIdx<<6 + bits.Len64(word) - 1), true + return uint8(wIdx<<6 + bits.Len64(w) - 1), true } } return @@ -276,13 +285,12 @@ func (b *BitSet256) Rank(idx uint8) (rnk int) { return } -// IsEmpty returns true if no bit is set. +// IsEmpty reports whether all 256 bits are zero. func (b *BitSet256) IsEmpty() bool { return b[0]|b[1]|b[2]|b[3] == 0 } -// Intersects returns true if the intersection of base set with the compare set -// is not the empty set. +// Intersects reports whether the receiver and c have at least one bit in common. func (b *BitSet256) Intersects(c *BitSet256) bool { return b[0]&c[0] != 0 || b[1]&c[1] != 0 || @@ -290,8 +298,8 @@ func (b *BitSet256) Intersects(c *BitSet256) bool { b[3]&c[3] != 0 } -// Intersection computes the intersection of base set with the compare set. -// This is the BitSet equivalent of & (and). +// Intersection returns a new BitSet256 containing only the bits +// that are set in both the receiver and c (bitwise AND). func (b *BitSet256) Intersection(c *BitSet256) (bs BitSet256) { bs[0] = b[0] & c[0] bs[1] = b[1] & c[1] @@ -300,8 +308,7 @@ func (b *BitSet256) Intersection(c *BitSet256) (bs BitSet256) { return } -// Union performs an in-place union of the receiver with c. -// It is the BitSet equivalent of | (OR). +// Union sets all bits in the receiver that are set in c (in-place bitwise OR). func (b *BitSet256) Union(c *BitSet256) { b[0] |= c[0] b[1] |= c[1] @@ -309,7 +316,7 @@ func (b *BitSet256) Union(c *BitSet256) { b[3] |= c[3] } -// Size is the number of set bits. +// Size returns the population count, i.e. the number of set bits. func (b *BitSet256) Size() (cnt int) { cnt += bits.OnesCount64(b[0]) cnt += bits.OnesCount64(b[1]) diff --git a/vendor/github.com/gaissmai/bart/internal/lpm/generate_lookuptbl.go b/vendor/github.com/gaissmai/bart/internal/lpm/generate_lookuptbl.go index 5585a3cc63..44d34a0792 100644 --- a/vendor/github.com/gaissmai/bart/internal/lpm/generate_lookuptbl.go +++ b/vendor/github.com/gaissmai/bart/internal/lpm/generate_lookuptbl.go @@ -2,7 +2,7 @@ //go:generate go run $GOFILE -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT // Package main generates a lookup table for longest-prefix-match (LPM) operations @@ -203,7 +203,7 @@ func genLookupTbl() string { // - Warnings about not mutating the read-only table const codeTemplate = `// Code generated by {{.File}}; DO NOT EDIT. -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT // Package lpm (longest-prefix-match) contains the lookup table with which diff --git a/vendor/github.com/gaissmai/bart/internal/lpm/lookuptblgenerated.go b/vendor/github.com/gaissmai/bart/internal/lpm/lookuptblgenerated.go index 5c82dbc7fc..8d89b79fd5 100644 --- a/vendor/github.com/gaissmai/bart/internal/lpm/lookuptblgenerated.go +++ b/vendor/github.com/gaissmai/bart/internal/lpm/lookuptblgenerated.go @@ -1,6 +1,6 @@ // Code generated by generate_lookuptbl.go; DO NOT EDIT. -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT // Package lpm (longest-prefix-match) contains the lookup table with which diff --git a/vendor/github.com/gaissmai/bart/internal/nodes/bart.go b/vendor/github.com/gaissmai/bart/internal/nodes/bart.go index f5ab169211..0e2d1e70bf 100644 --- a/vendor/github.com/gaissmai/bart/internal/nodes/bart.go +++ b/vendor/github.com/gaissmai/bart/internal/nodes/bart.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package nodes @@ -8,32 +8,33 @@ import ( "github.com/gaissmai/bart/internal/lpm" "github.com/gaissmai/bart/internal/sparse" - "github.com/gaissmai/bart/internal/value" ) -// BartNode is a trie level node in the multibit routing table. +// BartNode represents a single trie level in the multibit routing table. // -// Each BartNode contains two conceptually different arrays: -// - Prefixes stores routing entries (prefix -> value), -// laid out as a complete binary tree using the baseIndex() -// function from the ART algorithm. -// - Children: holding subtries or path-compressed leaves/fringes with -// a branching factor of 256 (8 bits per stride). -// - Children holds subnodes for the 256 possible next-hop paths -// at this trie level (8-bit stride). +// Unlike the original ART algorithm, this implementation uses popcount-compressed +// sparse arrays instead of fixed-size allocations. Insertions and lookups rely on +// fast bitset operations and precomputed lookup tables to maximize CPU pipelining +// and cache efficiency. // -// Entries in Children may be: -// - *BartNode[V] -> internal child node for further traversal -// - *LeafNode[V] -> path-comp. node (depth < maxDepth - 1) -// - *FringeNode[V] -> path-comp. node (depth == maxDepth - 1, stride-aligned: /8, /16, ... /128) +// Each BartNode maintains two distinct sparse arrays: // -// Note: Both *LeafNode and *FringeNode entries are only created by path compression. -// Prefixes that match exactly at the maximum trie depth (depth == maxDepth) are -// never stored as Children, but always directly in the prefixes array at that level. +// 1. Prefixes: Stores routing entries (prefix -> value) for the current stride. +// These are laid out as a complete binary tree using the baseIndex() +// function mapping from the ART algorithm. Prefixes that match exactly at +// the maximum trie depth are always stored here. // -// Unlike the original ART, this implementation uses popcount-compressed sparse arrays -// instead of fixed-size arrays. Array slots are not pre-allocated; insertion -// and lookup rely on fast bitset operations and precomputed rank indexes. +// 2. Children: Holds pointers to the next logical levels with a branching +// factor of 256 (8 bits per stride). +// +// A slot in the Children array may contain one of three types: +// - *BartNode[V]: An internal intermediate node for further trie traversal. +// - *FringeNode[V]: A path-compressed node if it qualifies as fringe [IsFringe] +// - *LeafNode[V]: A path-compressed node otherwise. +// +// Note: LeafNode and FringeNode are created through path compression and +// are automatically split into regular BartNodes when a more specific prefix +// is inserted that requires further branching. type BartNode[V any] struct { Prefixes sparse.Array256[V] Children sparse.Array256[any] @@ -63,7 +64,8 @@ func (n *BartNode[V]) ChildCount() int { // It returns true if a prefix already existed at that index (indicating an update), // false if this is a new insertion. func (n *BartNode[V]) InsertPrefix(idx uint8, val V) (exists bool) { - return n.Prefixes.InsertAt(idx, val) + _, exists = n.Prefixes.InsertAt(idx, val) + return } // GetPrefix retrieves the value associated with the prefix at the given index. @@ -83,9 +85,8 @@ func (n *BartNode[V]) MustGetPrefix(idx uint8) (val V) { func (n *BartNode[V]) AllIndices() iter.Seq2[uint8, V] { return func(yield func(uint8, V) bool) { var buf [256]uint8 - for _, idx := range n.Prefixes.AsSlice(&buf) { - val := n.MustGetPrefix(idx) - if !yield(idx, val) { + for i, idx := range n.Prefixes.AsSlice(&buf) { + if !yield(idx, n.Prefixes.Items[i]) { return } } @@ -103,7 +104,8 @@ func (n *BartNode[V]) DeletePrefix(idx uint8) (exists bool) { // The child can be a *BartNode[V], *LeafNode[V], or *FringeNode[V]. // Returns true if a child already existed at that address. func (n *BartNode[V]) InsertChild(addr uint8, child any) (exists bool) { - return n.Children.InsertAt(addr, child) + _, exists = n.Children.InsertAt(addr, child) + return } // GetChild retrieves the child node at the specified address. @@ -125,8 +127,7 @@ func (n *BartNode[V]) AllChildren() iter.Seq2[uint8, any] { var buf [256]uint8 addrs := n.Children.AsSlice(&buf) for i, addr := range addrs { - child := n.Children.Items[i] - if !yield(addr, child) { + if !yield(addr, n.Children.Items[i]) { return } } @@ -172,7 +173,7 @@ func (n *BartNode[V]) LookupIdx(idx uint8) (top uint8, val V, ok bool) { return top, val, ok } -// Lookup is just a simple wrapper for lookupIdx. +// Lookup is just a simple wrapper for LookupIdx. func (n *BartNode[V]) Lookup(idx uint8) (val V, ok bool) { _, val, ok = n.LookupIdx(idx) return val, ok @@ -188,7 +189,7 @@ func (n *BartNode[V]) Lookup(idx uint8) (val V, ok bool) { // // Note: The returned node is a new instance with copied slices but only shallow copies of nested nodes, // except for LeafNode and FringeNode children which are cloned according to cloneFn. -func (n *BartNode[V]) CloneFlat(cloneFn value.CloneFunc[V]) *BartNode[V] { +func (n *BartNode[V]) CloneFlat(cloneFn func(V) V) *BartNode[V] { if n == nil { return nil } @@ -245,7 +246,7 @@ func (n *BartNode[V]) CloneFlat(cloneFn value.CloneFunc[V]) *BartNode[V] { // // Returns a new instance of BartNode[V] which is a complete deep clone of the // receiver node with all descendants. -func (n *BartNode[V]) CloneRec(cloneFn value.CloneFunc[V]) *BartNode[V] { +func (n *BartNode[V]) CloneRec(cloneFn func(V) V) *BartNode[V] { if n == nil { return nil } diff --git a/vendor/github.com/gaissmai/bart/internal/nodes/bartmethodsgenerated.go b/vendor/github.com/gaissmai/bart/internal/nodes/bartmethodsgenerated.go index 69c0e8a1c3..8b92f78f86 100644 --- a/vendor/github.com/gaissmai/bart/internal/nodes/bartmethodsgenerated.go +++ b/vendor/github.com/gaissmai/bart/internal/nodes/bartmethodsgenerated.go @@ -1,6 +1,6 @@ // Code generated from file "commonmethods_tmpl.go"; DO NOT EDIT. -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package nodes @@ -17,65 +17,76 @@ import ( "github.com/gaissmai/bart/internal/value" ) -// Insert inserts a network prefix and its associated value into the -// trie starting at the specified byte depth. +// Insert adds or updates a network prefix and its associated value in the trie. +// Traversal begins at the specified byte depth. // -// The function traverses the prefix address from the given depth and inserts -// the value either directly into the node's prefix table or as a compressed -// leaf or fringe node. If a conflicting leaf or fringe exists, it creates -// a new intermediate node to accommodate both entries. +// The trie utilizes path compression to conserve memory. A prefix is inserted: +// - Uncompressed into a node's prefix table at depth == strideCount. +// - As a path-compressed FringeNode if it qualifies as fringe [IsFringe]. +// - As a path-compressed LeafNode otherwise. +// +// When a new prefix collides with an existing compressed node (Leaf or Fringe), +// Insert resolves the collision by creating a new intermediate node, pushing +// the existing entry down to the next level, and continuing traversal. // // Parameters: -// - pfx: The network prefix to insert (must be in canonical form) -// - val: The value to associate with the prefix -// - depth: The current depth in the trie (0-based byte index) +// - pfx: The network prefix to insert (must be in canonical/masked form). +// - val: The value to associate with the prefix. +// - depth: The current depth in the trie (0-based byte index). // -// Returns true if a prefix already existed and was updated, false for new insertions. +// Returns true if an existing prefix was updated, false if a new insertion occurred. func (n *BartNode[V]) Insert(pfx netip.Prefix, val V, depth int) (exists bool) { ip := pfx.Addr() // the pfx must be in canonical form + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // find the proper trie node to insert prefix - // start with prefix octet at depth + // Traverse the prefix's octets. Each depth corresponds to an 8-bit stride. + // We descend through the trie until we either reach the final stride (depth == strideCount) + // or find an empty child slot where we can path-compress the remaining strides. for ; depth < len(octets); depth++ { octet := octets[depth] - // last masked octet: insert/override prefix/val into node - if depth == lastOctetPlusOne { - return n.InsertPrefix(art.PfxToIdx(octet, lastBits), val) + // The current depth matches the prefix's stride count, meaning this is the final + // node for this prefix. We insert it directly into this node's prefix table. + if depth == strideCount { + return n.InsertPrefix(art.PfxToIdx(octet, modBits), val) } - // reached end of trie path ... + // No child exists at this octet path. Instead of creating intermediate nodes + // for the remaining strides, we path-compress the rest of the prefix into a single child slot. if !n.Children.Test(octet) { - // insert prefix path compressed as leaf or fringe - if IsFringe(depth, pfx) { + // If the prefix is perfectly aligned with the next stride boundary (e.g., /16 at depth 1), + // it acts as a default route for everything below it. We store it as a FringeNode. + // Otherwise, it has trailing bits or crosses boundaries, so we store it as a LeafNode. + if IsFringe(depth, pfxLen) { return n.InsertChild(octet, NewFringeNode(val)) } return n.InsertChild(octet, NewLeafNode(pfx, val)) } - // ... or descend down the trie + // A child already exists at this octet path. Retrieve it to either continue + // our descent along the strides or resolve a structural collision with a compressed node. kid := n.MustGetChild(octet) - // kid is node or leaf at addr switch kid := kid.(type) { case *BartNode[V]: - n = kid // descend down to next trie level + // Standard intermediate node: descend to the next trie level. + n = kid case *LeafNode[V]: - // reached a path compressed prefix - // override value in slot if prefixes are equal + // Collision with an existing path-compressed LeafNode. + // If it's the exact same prefix, simply update the value. if kid.Prefix == pfx { kid.Value = val - // exists return true } - // create new node - // push the leaf down - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: the paths diverge. + // 1. Create a new intermediate node. + // 2. Push the existing leaf down into this new node. + // 3. Replace the current child slot with the new node. + // 4. Descend into the new node to continue inserting 'pfx'. newNode := new(BartNode[V]) newNode.Insert(kid.Prefix, kid.Value, depth+1) @@ -83,18 +94,17 @@ func (n *BartNode[V]) Insert(pfx netip.Prefix, val V, depth int) (exists bool) { n = newNode case *FringeNode[V]: - // reached a path compressed fringe - // override value in slot if pfx is a fringe - if IsFringe(depth, pfx) { + // Collision with an existing path-compressed FringeNode. + // If the incoming prefix is also a fringe at this depth, update the value. + if IsFringe(depth, pfxLen) { kid.Value = val - // exists return true } - // create new node - // push the fringe down, it becomes a default route (idx=1) - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: + // The existing FringeNode acts as a catch-all (default route) for this sub-trie. + // To allow the incoming prefix to branch further, we expand the FringeNode + // into a full intermediate node and place its value at the default route index (1). newNode := new(BartNode[V]) newNode.InsertPrefix(1, kid.Value) @@ -108,63 +118,74 @@ func (n *BartNode[V]) Insert(pfx netip.Prefix, val V, depth int) (exists bool) { panic("unreachable") } -// InsertPersist is similar to insert but the receiver isn't modified. -// Assumes the caller has pre-cloned the root (COW). It clones the -// internal nodes along the descent path before mutating them. -func (n *BartNode[V]) InsertPersist(cloneFn value.CloneFunc[V], pfx netip.Prefix, val V, depth int) (exists bool) { +// InsertPersist adds or updates a network prefix and its associated value in the trie +// using Copy-On-Write (COW) semantics. Traversal begins at the specified byte depth. +// +// Unlike [Insert], InsertPersist ensures structural integrity of the existing tree +// by cloning internal nodes along the descent path (Copy-On-Write) before mutation. +// +// Parameters: +// - cloneFn: The function used to clone values (V). +// - pfx: The network prefix to insert (must be in canonical/masked form). +// - val: The value to associate with the prefix. +// - depth: The current depth in the trie (0-based byte index). +// +// Returns true if an existing prefix was updated, false if a new insertion occurred. +func (n *BartNode[V]) InsertPersist(cloneFn func(V) V, pfx netip.Prefix, val V, depth int) (exists bool) { ip := pfx.Addr() // the pfx must be in canonical form + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // find the proper trie node to insert prefix - // start with prefix octet at depth + // Traverse the prefix's octets. Each depth corresponds to an 8-bit stride. + // We descend through the trie until we either reach the final stride (depth == strideCount) + // or find an empty child slot where we can path-compress the remaining strides. for ; depth < len(octets); depth++ { octet := octets[depth] - // last masked octet: insert/override prefix/val into node - if depth == lastOctetPlusOne { - return n.InsertPrefix(art.PfxToIdx(octet, lastBits), val) + // The current depth matches the prefix's stride count, meaning this is the final + // node for this prefix. We insert it directly into this node's prefix table. + if depth == strideCount { + return n.InsertPrefix(art.PfxToIdx(octet, modBits), val) } - // reached end of trie path ... + // No child exists at this octet path. Instead of creating intermediate nodes + // for the remaining strides, we path-compress the rest of the prefix into a single child slot. if !n.Children.Test(octet) { - // insert prefix path compressed as leaf or fringe - if IsFringe(depth, pfx) { + // If the prefix is perfectly aligned with the next stride boundary (e.g., /16 at depth 1), + // it acts as a default route for everything below it. We store it as a FringeNode. + // Otherwise, it has trailing bits or crosses boundaries, so we store it as a LeafNode. + if IsFringe(depth, pfxLen) { return n.InsertChild(octet, NewFringeNode(val)) } return n.InsertChild(octet, NewLeafNode(pfx, val)) } - // ... or descend down the trie + // A child already exists at this octet path. Retrieve it to either continue + // our descent along the strides or resolve a structural collision with a compressed node. kid := n.MustGetChild(octet) - // kid is node or leaf at addr switch kid := kid.(type) { case *BartNode[V]: - // clone the traversed path - - // kid points now to cloned kid + // Standard intermediate node: Clone the traversed path to maintain persistence (COW). + // We clone the child node before modifying it, then replace the current child slot. kid = kid.CloneFlat(cloneFn) - - // replace kid with clone n.InsertChild(octet, kid) - n = kid - continue // descend down to next trie level case *LeafNode[V]: - // reached a path compressed prefix - // override value in slot if prefixes are equal + // Collision with an existing path-compressed LeafNode. + // If it's the exact same prefix, simply update the value. if kid.Prefix == pfx { kid.Value = val - // exists return true } - // create new node - // push the leaf down - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: the paths diverge. + // 1. Create a new intermediate node. + // 2. Push the existing leaf down into this new node. + // 3. Replace the current child slot with the new node. + // 4. Descend into the new node to continue inserting 'pfx'. newNode := new(BartNode[V]) newNode.Insert(kid.Prefix, kid.Value, depth+1) @@ -172,18 +193,17 @@ func (n *BartNode[V]) InsertPersist(cloneFn value.CloneFunc[V], pfx netip.Prefix n = newNode case *FringeNode[V]: - // reached a path compressed fringe - // override value in slot if pfx is a fringe - if IsFringe(depth, pfx) { + // Collision with an existing path-compressed FringeNode. + // If the incoming prefix is also a fringe at this depth, update the value. + if IsFringe(depth, pfxLen) { kid.Value = val - // exists return true } - // create new node - // push the fringe down, it becomes a default route (idx=1) - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: + // The existing FringeNode acts as a catch-all (default route) for this sub-trie. + // To allow the incoming prefix to branch further, we expand the FringeNode + // into a full intermediate node and place its value at the default route index (1). newNode := new(BartNode[V]) newNode.InsertPrefix(1, kid.Value) @@ -193,162 +213,170 @@ func (n *BartNode[V]) InsertPersist(cloneFn value.CloneFunc[V], pfx netip.Prefix default: panic("logic error, wrong node type") } - } - panic("unreachable") } -// PurgeAndCompress performs bottom-up compression of the trie. +// PurgeAndCompress performs bottom-up trie maintenance to restore path compression +// after a deletion. It unwinds the provided stack of parent nodes, identifying +// nodes that have become sparse (i.e., containing only a single prefix or a single +// child node) and prunes them by promoting the underlying entries to the parent level. // -// The function unwinds the provided stack of parent nodes, checking each level -// for compression opportunities based on child and prefix count. -// It may convert: -// - Nodes with a single prefix into leaf one level above. -// - Nodes with a single leaf or fringe into leaf one level above. +// This ensures the trie remains memory-efficient by collapsing redundant intermediate +// nodes back into path-compressed LeafNodes or FringeNodes whenever possible. // // Parameters: -// - stack: Array of parent nodes to process during unwinding -// - octets: The path of octets taken to reach the current position -// - is4: True for IPv4 processing, false for IPv6 +// - stack: Array of parent nodes to process during bottom-up unwinding. +// - octets: The full path of octets leading to the current node. +// - is4: True for IPv4 processing, false for IPv6. func (n *BartNode[V]) PurgeAndCompress(stack []*BartNode[V], octets []uint8, is4 bool) { - // unwind the stack + // Iterate backwards through the ancestor stack to prune nodes from the bottom up. for depth := len(stack) - 1; depth >= 0; depth-- { parent := stack[depth] octet := octets[depth] + // Check if the current node is redundant. + // A node may be redundant if it contains exactly one entry (either a prefix or + // a compressed child node). pfxCount := n.PrefixCount() childCount := n.ChildCount() + // If it contains more than one entry, it is always structurally significant and cannot be pruned. if pfxCount+childCount > 1 { return } switch { case childCount == 1: - singleAddr, _ := n.Children.FirstSet() // single addr must be first bit set + // The node has exactly one child. We determine if it is a path node, + // a compressed LeafNode, or a compressed FringeNode. + singleAddr, _ := n.Children.FirstSet() anyKid := n.MustGetChild(singleAddr) switch kid := anyKid.(type) { case *BartNode[V]: - // fast exit, we are at an intermediate path node - // no further delete/compress upwards the stack is possible + // The child is an intermediate path node; the tree structure is required + // at this level. Compression cannot proceed further up. return case *LeafNode[V]: - // just one leaf, delete this node and reinsert the leaf above + // The child is a compressed LeafNode. Prune the current node + // and re-insert the leaf into the parent to elevate it. parent.DeleteChild(octet) - - // ... (re)insert the leaf at parents depth parent.Insert(kid.Prefix, kid.Value, depth) case *FringeNode[V]: - // just one fringe, delete this node and reinsert the fringe as leaf above + // The child is a compressed FringeNode. Prune the current node + // and re-insert the fringe as a leaf into the parent. parent.DeleteChild(octet) - // rebuild the prefix with octets, depth, ip version and addr - // depth is the parent's depth, so add +1 here for the kid - // lastOctet in cidrForFringe is the only addr (singleAddr) + // Reconstruct the full prefix for the fringe, as path compression + // requires the entire CIDR path, not just the remainder. + // depth is the parent's depth, so we offset by 1 for the kid's position. fringePfx := CidrForFringe(octets, depth+1, is4, singleAddr) - // ... (re)reinsert prefix/value at parents depth parent.Insert(fringePfx, kid.Value, depth) } case pfxCount == 1: - // just one prefix, delete this node and reinsert the idx as leaf above + // The node has exactly one prefix. Prune the node and elevate the + // prefix to the parent level as a leaf/fringe. parent.DeleteChild(octet) - // get prefix back from idx ... - idx, _ := n.Prefixes.FirstSet() // single idx must be first bit set + // Retrieve the single prefix stored in this node. + idx, _ := n.Prefixes.FirstSet() val := n.MustGetPrefix(idx) - // ... and octet path + // Reconstruct the prefix from the path for re-insertion. path := StridePath{} copy(path[:], octets) - - // depth is the parent's depth, so add +1 here for the kid pfx := CidrFromPath(path, depth+1, is4, idx) - // ... (re)insert prefix/value at parents depth parent.Insert(pfx, val, depth) + default: + panic("unreachable") } - // climb up the stack + // Move up to the next parent in the stack to continue pruning. n = parent } } -// Delete deletes the prefix and returns true if the prefix existed, -// or false otherwise. The prefix must be in canonical form. +// Delete removes the prefix from the trie rooted at n and returns true if the +// prefix existed, false if it was not found. The prefix must be in canonical +// (masked) form. +// +// The trie uses path compression, so a prefix may be stored in one of three ways: +// - In the current node's prefix table when the prefix length aligns exactly +// with the stride boundary at this depth (depth == strideCount). +// - As a path-compressed FringeNode in a child slot for stride-aligned prefixes +// (e.g. /8, /16, /24); occurs at depth == strideCount-1. +// - As a path-compressed LeafNode in a child slot for prefixes otherwise. +// +// After a successful deletion, PurgeAndCompress walks the ancestor stack to prune +// now-empty nodes and restore path compression upward. func (n *BartNode[V]) Delete(pfx netip.Prefix) (exists bool) { - // invariant, prefix must be masked - - // values derived from pfx - ip := pfx.Addr() + ip := pfx.Addr() // pfx must be in canonical (masked) form + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // record the nodes on the path to the deleted node, needed to purge - // and/or path compress nodes after the deletion of a prefix + // Record ancestor nodes as we descend; PurgeAndCompress uses this stack to + // walk back up and clean up empty or re-compressible nodes after deletion. stack := [MaxTreeDepth]*BartNode[V]{} - // find the trie node for depth, octet := range octets { - depth = depth & DepthMask // BCE, Delete must be fast + depth &= DepthMask // BCE hint; keep Delete on the fast path - // push current node on stack for path recording - stack[depth] = n + stack[depth] = n // record current node before descending - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - // try to delete prefix in trie node - if exists = n.DeletePrefix(art.PfxToIdx(octet, lastBits)); !exists { + // At the stride boundary, the prefix is stored directly in this node's + // prefix table. + if depth == strideCount { + if exists = n.DeletePrefix(art.PfxToIdx(octet, modBits)); !exists { return false } - // remove now-empty nodes and re-path-compress upwards + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) return true } + // no child node exists at this octet; the prefix is not in the trie if !n.Children.Test(octet) { return false } + + // A child node exists at this octet path, retrieve it. kid := n.MustGetChild(octet) - // kid is node or leaf or fringe at octet switch kid := kid.(type) { case *BartNode[V]: - n = kid // descend down to next trie level + n = kid // descend to the next trie level case *FringeNode[V]: - // if pfx is no fringe at this depth, fast exit - if !IsFringe(depth, pfx) { + // A FringeNode holds a single stride-aligned prefix (/8, /16, ...). + // If pfx does not qualify as a fringe at this depth, it cannot be here. + if !IsFringe(depth, pfxLen) { return false } - // pfx is fringe at depth, delete fringe n.DeleteChild(octet) - // remove now-empty nodes and re-path-compress upwards + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true case *LeafNode[V]: - // Attention: pfx must be masked to be comparable! + // A LeafNode holds exactly one path-compressed prefix. + // Compare using the canonical (masked) form for an exact match. if kid.Prefix != pfx { return false } - // prefix is equal leaf, delete leaf n.DeleteChild(octet) - // remove now-empty nodes and re-path-compress upwards + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true default: @@ -359,142 +387,150 @@ func (n *BartNode[V]) Delete(pfx netip.Prefix) (exists bool) { panic("unreachable") } -// DeletePersist is similar to delete but does not mutate the original trie. -// Assumes the caller has pre-cloned the root (COW). It clones the -// internal nodes along the descent path before mutating them. -func (n *BartNode[V]) DeletePersist(cloneFn value.CloneFunc[V], pfx netip.Prefix) (exists bool) { - ip := pfx.Addr() // the pfx must be in canonical form +// DeletePersist removes the prefix from the trie rooted at n using Copy-On-Write (COW) semantics. +// It returns true if the prefix existed, false if it was not found. The prefix must be in +// canonical (masked) form. +// +// Like [Delete], this method uses path compression. However, DeletePersist ensures +// the structural integrity of the existing tree by cloning internal nodes along the +// descent path (COW) before mutation. +// +// After a successful deletion, PurgeAndCompress walks the ancestor stack to prune +// now-empty nodes and restore path compression upward. +func (n *BartNode[V]) DeletePersist(cloneFn func(V) V, pfx netip.Prefix) (exists bool) { + ip := pfx.Addr() // pfx must be in canonical (masked) form + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // Stack to keep track of cloned nodes along the path, - // needed for purge and path compression after delete. + // Record ancestor nodes as we descend; PurgeAndCompress uses this stack to + // walk back up and clean up empty or re-compressible nodes after deletion. + // Since this is a COW operation, we store the cloned nodes here. stack := [MaxTreeDepth]*BartNode[V]{} - // Traverse the trie to locate the prefix to delete. for depth, octet := range octets { - // Keep track of the cloned node at current depth. - stack[depth] = n + depth &= DepthMask // BCE hint; keep DeletePersist on the fast path + stack[depth] = n // record current node before descending - if depth == lastOctetPlusOne { - // Attempt to delete the prefix from the node's prefixes. - if exists = n.DeletePrefix(art.PfxToIdx(octet, lastBits)); !exists { - // Prefix not found, nothing deleted. + // At the stride boundary, the prefix is stored directly in this node's + // prefix table. + if depth == strideCount { + if exists = n.DeletePrefix(art.PfxToIdx(octet, modBits)); !exists { return false } - // After deletion, purge nodes and compress the path if needed. + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true } - addr := octet - - // If child node doesn't exist, no prefix to delete. - if !n.Children.Test(addr) { + // no child node exists at this octet; the prefix is not in the trie + if !n.Children.Test(octet) { return false } - // Fetch child node at current address. - kid := n.MustGetChild(addr) + // A child node exists at this octet path, retrieve it to either continue + // our descent or perform a persistent delete on a compressed node. + kid := n.MustGetChild(octet) switch kid := kid.(type) { case *BartNode[V]: - // Clone the internal node for copy-on-write. + // Standard intermediate node: Clone the traversed path to maintain + // persistence (COW). We clone the child node before modifying it, + // then replace the current child slot. kid = kid.CloneFlat(cloneFn) - - // Replace child with cloned node. - n.InsertChild(addr, kid) - - // Descend to cloned child node. + n.InsertChild(octet, kid) n = kid continue case *FringeNode[V]: - // Reached a path compressed fringe. - if !IsFringe(depth, pfx) { - // Prefix to delete not found here. + // A FringeNode holds a single stride-aligned prefix (/8, /16, ...). + // If pfx does not qualify as a fringe at this depth, it cannot be here. + if !IsFringe(depth, pfxLen) { return false } - // Delete the fringe node. - n.DeleteChild(addr) + n.DeleteChild(octet) - // Purge and compress affected path. + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true case *LeafNode[V]: - // Reached a path compressed leaf node. + // A LeafNode holds exactly one path-compressed prefix. + // Compare using the canonical (masked) form for an exact match. if kid.Prefix != pfx { - // Leaf prefix does not match; nothing to delete. return false } - // Delete leaf node. - n.DeleteChild(addr) + n.DeleteChild(octet) - // Purge and compress affected path. + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true default: - // Unexpected node type indicates a logic error. panic("logic error, wrong node type") } } - // Should never happen: traversal always returns or panics inside loop. panic("unreachable") } // Get retrieves the value associated with the given network prefix. -// Returns the stored value and true if the prefix exists in this node, -// zero value and false if the prefix is not found. +// Traversal descends through the trie using the prefix's octets. +// +// The lookup handles path compression transparently: +// - If the path matches an internal node at the target stride, the value is retrieved +// from the node's prefix table. +// - If the path leads to a compressed LeafNode or FringeNode, the function verifies +// the prefix match before returning the value. // // Parameters: -// - pfx: The network prefix to look up (must be in canonical form) +// - pfx: The network prefix to look up (must be in canonical form). // // Returns: -// - val: The value associated with the prefix (zero value if not found) -// - exists: True if the prefix was found, false otherwise +// - val: The value associated with the prefix (zero value if not found). +// - exists: True if the prefix was found, false otherwise. func (n *BartNode[V]) Get(pfx netip.Prefix) (val V, exists bool) { - // invariant, prefix must be masked - - // values derived from pfx + // The prefix must be provided in canonical (masked) form for correct trie traversal. ip := pfx.Addr() + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // find the trie node + // Traverse the trie octet by octet based on the prefix path. for depth, octet := range octets { - if depth == lastOctetPlusOne { - return n.GetPrefix(art.PfxToIdx(octet, lastBits)) + // At the target stride boundary, the prefix is expected in this node's + // prefix table. + if depth == strideCount { + return n.GetPrefix(art.PfxToIdx(octet, modBits)) } + // If no child exists at this path, the prefix is not in the trie. kidAny, ok := n.GetChild(octet) if !ok { return val, false } - // kid is node or leaf or fringe at octet + // Identify the node type at this path segment. switch kid := kidAny.(type) { case *BartNode[V]: - n = kid // descend down to next trie level + // Standard intermediate node: descend to the next level. + n = kid case *FringeNode[V]: - // reached a path compressed fringe, stop traversing - if IsFringe(depth, pfx) { + // Reached a path-compressed FringeNode. + // Verify if the prefix qualifies as a fringe at this depth to return a match. + if IsFringe(depth, pfxLen) { return kid.Value, true } return val, false case *LeafNode[V]: - // reached a path compressed prefix, stop traversing + // Reached a path-compressed LeafNode. + // Check if the stored prefix matches the lookup prefix exactly. if kid.Prefix == pfx { return kid.Value, true } @@ -512,7 +548,7 @@ func (n *BartNode[V]) Get(pfx netip.Prefix) (val V, exists bool) { // The callback receives the current value (if found) and existence flag, and returns // a new value and deletion flag. // -// modify returns the size delta (-1, 0, +1). +// Modify returns the size delta (-1, 0, +1). // This method handles path traversal, node creation for new paths, and automatic // purge/compress operations after deletions. // @@ -526,9 +562,10 @@ func (n *BartNode[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V, var zero V ip := pfx.Addr() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) // record the nodes on the path to the deleted node, needed to purge // and/or path compress nodes after the deletion of a prefix @@ -536,16 +573,15 @@ func (n *BartNode[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V, // find the proper trie node to update prefix for depth, octet := range octets { - depth = depth & DepthMask // BCE + depth &= DepthMask // BCE // push current node on stack for path recording stack[depth] = n - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx := art.PfxToIdx(octet, lastBits) + // The current depth matches the prefix's stride count, meaning this is the final + // stride for this prefix. Insert or update it directly in this node's prefix table. + if depth == strideCount { + idx := art.PfxToIdx(octet, modBits) oldVal, existed := n.GetPrefix(idx) newVal, del := cb(oldVal, existed) @@ -585,7 +621,7 @@ func (n *BartNode[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V, } // insert - if IsFringe(depth, pfx) { + if IsFringe(depth, pfxLen) { n.InsertChild(octet, NewFringeNode(newVal)) } else { n.InsertChild(octet, NewLeafNode(pfx, newVal)) @@ -644,7 +680,7 @@ func (n *BartNode[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V, case *FringeNode[V]: // update existing value if prefix is fringe - if IsFringe(depth, pfx) { + if IsFringe(depth, pfxLen) { newVal, del := cb(kid.Value, true) if !del { kid.Value = newVal @@ -774,8 +810,8 @@ func (n *BartNode[V]) EqualRec(o *BartNode[V]) bool { // // It returns immediately if n is nil or empty. For each visited internal node // it calls dump to write the node's representation, then iterates its child -// addresses and recurses into children that implement nodeDumper[V] (internal -// subnodes). The path slice and depth together represent the byte-wise path +// addresses and recurses into children of type *BartNode[V] (internal subnodes). +// The path slice and depth together represent the byte-wise path // from the root to the current node; depth is incremented for each recursion. // The is4 flag controls IPv4/IPv6 formatting used by dump. func (n *BartNode[V]) DumpRec(w io.Writer, path StridePath, depth int, is4 bool) { @@ -803,8 +839,8 @@ func (n *BartNode[V]) dump(w io.Writer, path StridePath, depth int, is4 bool) { bits := depth * strideLen indent := strings.Repeat(".", depth) - // printing values if V is not zero-sized - printValues := !value.IsZST[V]() + // printing values if V is not the empty struct{} + printValues := !value.IsEmptyStruct[V]() // node type with depth and octet path and bits. fmt.Fprintf(w, "\n%s[%s] depth: %d path: [%s] / %d\n", @@ -967,7 +1003,7 @@ func (n *BartNode[V]) DumpString(octets []uint8, depth int, is4 bool) string { // - stopNode: has children but no subnodes (nodes == 0) // - halfNode: contains at least one leaf or fringe and also has subnodes, but // no prefixes -// - fullNode: has prefixes or leaves/fringes and also has subnodes +// - fullNode: has prefixes and also has subnodes // - pathNode: has subnodes only (no prefixes, leaves, or fringes) // // The order of these checks is significant to ensure the correct classification. @@ -1024,7 +1060,7 @@ func (n *BartNode[V]) Stats() (s StatsT) { // It walks the node tree recursively and sums immediate counts (prefixes and // child slots) plus the number of nodes, leaves, and fringe nodes in the // subtree. If n is nil or empty, a zeroed stats is returned. The returned -// stats.nodes includes the current node. The function will panic if a child +// SubNodes count includes the current node. The function will panic if a child // has an unexpected concrete type. func (n *BartNode[V]) StatsRec() (s StatsT) { if n == nil || n.IsEmpty() { @@ -1080,8 +1116,8 @@ func (n *BartNode[V]) FprintRec(w io.Writer, parent TrieItem[V], pad string) err return CmpPrefix(a.Cidr, b.Cidr) }) - // printing values if V is not zero-sized - printValues := !value.IsZST[V]() + // printing values if V is not the empty struct{} + printValues := !value.IsEmptyStruct[V]() // for all direct item under this node ... for i, item := range directItems { @@ -1178,7 +1214,7 @@ func (n *BartNode[V]) DirectItemsRec(parentIdx uint8, path StridePath, depth int path[depth] = addr directItems = append(directItems, kid.DirectItemsRec(0, path, depth+1, is4)...) - case *LeafNode[V]: // path-compressed child, stop's recursion for this child + case *LeafNode[V]: // path-compressed child, stops recursion for this child item := TrieItem[V]{ Node: nil, Is4: is4, @@ -1187,7 +1223,7 @@ func (n *BartNode[V]) DirectItemsRec(parentIdx uint8, path StridePath, depth int } directItems = append(directItems, item) - case *FringeNode[V]: // path-compressed fringe, stop's recursion for this child + case *FringeNode[V]: // path-compressed fringe, stops recursion for this child item := TrieItem[V]{ Node: nil, Is4: is4, @@ -1221,9 +1257,9 @@ func (n *BartNode[V]) DirectItemsRec(parentIdx uint8, path StridePath, depth int // The merge operation is destructive on the receiver n, but leaves the source node o unchanged. // // Returns the number of duplicate prefixes that were overwritten during merging. -func (n *BartNode[V]) UnionRec(cloneFn value.CloneFunc[V], o *BartNode[V], depth int) (duplicates int) { +func (n *BartNode[V]) UnionRec(cloneFn func(V) V, o *BartNode[V], depth int) (duplicates int) { if cloneFn == nil { - cloneFn = value.CopyVal + cloneFn = func(v V) V { return v } } buf := [256]uint8{} @@ -1254,9 +1290,9 @@ func (n *BartNode[V]) UnionRec(cloneFn value.CloneFunc[V], o *BartNode[V], depth } // UnionRecPersist is similar to unionRec but performs an immutable union of nodes. -func (n *BartNode[V]) UnionRecPersist(cloneFn value.CloneFunc[V], o *BartNode[V], depth int) (duplicates int) { +func (n *BartNode[V]) UnionRecPersist(cloneFn func(V) V, o *BartNode[V], depth int) (duplicates int) { if cloneFn == nil { - cloneFn = value.CopyVal + cloneFn = func(v V) V { return v } } buf := [256]uint8{} @@ -1305,7 +1341,7 @@ func (n *BartNode[V]) UnionRecPersist(cloneFn value.CloneFunc[V], o *BartNode[V] // fringe, node <-- insert new node, push this fringe down, union rec-descent // fringe, leaf <-- insert new node, push this fringe down, insert other leaf at depth+1 // fringe, fringe <-- just overwrite value -func (n *BartNode[V]) handleMatrix(cloneFn value.CloneFunc[V], thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { +func (n *BartNode[V]) handleMatrix(cloneFn func(V) V, thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { // Do ALL type assertions upfront - reduces line noise var ( thisNode, thisIsNode = thisChild.(*BartNode[V]) @@ -1422,7 +1458,7 @@ func (n *BartNode[V]) handleMatrix(cloneFn value.CloneFunc[V], thisExists bool, // fringe, node <-- insert new node, push this fringe down, union rec-descent // fringe, leaf <-- insert new node, push this fringe down, insert other leaf at depth+1 // fringe, fringe <-- just overwrite value -func (n *BartNode[V]) handleMatrixPersist(cloneFn value.CloneFunc[V], thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { +func (n *BartNode[V]) handleMatrixPersist(cloneFn func(V) V, thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { // Do ALL type assertions upfront - reduces line noise var ( thisNode, thisIsNode = thisChild.(*BartNode[V]) @@ -1654,9 +1690,7 @@ func (n *BartNode[V]) AllRecSorted(path StridePath, depth int, is4 bool, yield f } case *FringeNode[V]: fringePfx := CidrForFringe(path[:], depth, is4, childAddr) - // callback for this fringe if !yield(fringePfx, kid.Value) { - // early exit return false } @@ -1691,9 +1725,7 @@ func (n *BartNode[V]) AllRecSorted(path StridePath, depth int, is4 bool, yield f } case *FringeNode[V]: fringePfx := CidrForFringe(path[:], depth, is4, addr) - // callback for this fringe if !yield(fringePfx, kid.Value) { - // early exit return false } @@ -1879,9 +1911,10 @@ func (n *BartNode[V]) EachSubnet(octets []byte, depth int, is4 bool, pfxIdx uint // the iteration early. func (n *BartNode[V]) Supernets(pfx netip.Prefix, yield func(netip.Prefix, V) bool) { ip := pfx.Addr() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) // stack of the traversed nodes for reverse ordering of supernets stack := [MaxTreeDepth]*BartNode[V]{} @@ -1894,7 +1927,7 @@ func (n *BartNode[V]) Supernets(pfx netip.Prefix, yield func(netip.Prefix, V) bo LOOP: for depth, octet = range octets { // stepped one past the last stride of interest; back up to last and exit - if depth > lastOctetPlusOne { + if depth > strideCount { depth-- break } @@ -1955,11 +1988,10 @@ LOOP: // all others are just host routes var idx uint8 octet = octets[depth] - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx = art.PfxToIdx(octet, lastBits) + // Last “octet” from prefix + // Note: For /32 and /128, depth never reaches strideCount (4/16), + if depth == strideCount { + idx = art.PfxToIdx(octet, modBits) } else { idx = art.OctetToIdx(octet) } @@ -1996,17 +2028,18 @@ LOOP: func (n *BartNode[V]) Subnets(pfx netip.Prefix, yield func(netip.Prefix, V) bool) { // values derived from pfx ip := pfx.Addr() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) // find the trie node for depth, octet := range octets { - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), + // Last “octet” from prefix + // Note: For /32 and /128, depth never reaches strideCount (4/16), // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx := art.PfxToIdx(octet, lastBits) + if depth == strideCount { + idx := art.PfxToIdx(octet, modBits) n.EachSubnet(octets, depth, is4, idx, yield) return } @@ -2124,7 +2157,7 @@ func (n *BartNode[V]) Overlaps(o *BartNode[V], depth int) bool { // OverlapsRoutes compares the prefix sets of two nodes (n and o). // // It first checks for direct bitset intersection (identical indices), -// then walks both prefix sets using lpmTest to detect if any +// then walks both prefix sets using the Contains method to detect if any // of the n-prefixes is contained in o, or vice versa. func (n *BartNode[V]) OverlapsRoutes(o *BartNode[V]) bool { // some prefixes are identical, trivial overlap @@ -2267,19 +2300,20 @@ func (n *BartNode[V]) OverlapsSameChildren(o *BartNode[V], depth int) bool { // trie traversal across varying prefix lengths and compression levels. func (n *BartNode[V]) OverlapsPrefixAtDepth(pfx netip.Prefix, depth int) bool { ip := pfx.Addr() + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) for ; depth < len(octets); depth++ { - if depth > lastOctetPlusOne { + if depth > strideCount { break } octet := octets[depth] // full octet path in node trie, check overlap with last prefix octet - if depth == lastOctetPlusOne { - return n.OverlapsIdx(art.PfxToIdx(octet, lastBits)) + if depth == strideCount { + return n.OverlapsIdx(art.PfxToIdx(octet, modBits)) } // test if any route overlaps prefix´ so far diff --git a/vendor/github.com/gaissmai/bart/internal/nodes/commonmethods_tmpl.go b/vendor/github.com/gaissmai/bart/internal/nodes/commonmethods_tmpl.go index bdcee61cb7..6767152e53 100644 --- a/vendor/github.com/gaissmai/bart/internal/nodes/commonmethods_tmpl.go +++ b/vendor/github.com/gaissmai/bart/internal/nodes/commonmethods_tmpl.go @@ -1,6 +1,6 @@ //go:build generate -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT //go:generate ../../scripts/generate-node-methods.sh @@ -31,85 +31,96 @@ type _NODE_TYPE[V any] struct { Children struct{ bitset.BitSet256 } } -func (n *_NODE_TYPE[V]) IsEmpty() (_ bool) { return } -func (n *_NODE_TYPE[V]) PrefixCount() (_ int) { return } -func (n *_NODE_TYPE[V]) ChildCount() (_ int) { return } -func (n *_NODE_TYPE[V]) MustGetPrefix(uint8) (_ V) { return } -func (n *_NODE_TYPE[V]) MustGetChild(uint8) (_ any) { return } -func (n *_NODE_TYPE[V]) InsertPrefix(uint8, V) (_ bool) { return } -func (n *_NODE_TYPE[V]) DeletePrefix(uint8) (_ bool) { return } -func (n *_NODE_TYPE[V]) GetChild(uint8) (_ any, _ bool) { return } -func (n *_NODE_TYPE[V]) GetPrefix(uint8) (_ V, _ bool) { return } -func (n *_NODE_TYPE[V]) InsertChild(uint8, any) (_ bool) { return } -func (n *_NODE_TYPE[V]) DeleteChild(uint8) (_ bool) { return } -func (n *_NODE_TYPE[V]) CloneRec(value.CloneFunc[V]) (_ *_NODE_TYPE[V]) { return } -func (n *_NODE_TYPE[V]) CloneFlat(value.CloneFunc[V]) (_ *_NODE_TYPE[V]) { return } -func (n *_NODE_TYPE[V]) AllIndices() (seq2 iter.Seq2[uint8, V]) { return } -func (n *_NODE_TYPE[V]) AllChildren() (seq2 iter.Seq2[uint8, any]) { return } -func (n *_NODE_TYPE[V]) Contains(uint8) (_ bool) { return } -func (n *_NODE_TYPE[V]) LookupIdx(uint8) (_ uint8, _ V, _ bool) { return } +func (n *_NODE_TYPE[V]) IsEmpty() (_ bool) { return } +func (n *_NODE_TYPE[V]) PrefixCount() (_ int) { return } +func (n *_NODE_TYPE[V]) ChildCount() (_ int) { return } +func (n *_NODE_TYPE[V]) MustGetPrefix(uint8) (_ V) { return } +func (n *_NODE_TYPE[V]) MustGetChild(uint8) (_ any) { return } +func (n *_NODE_TYPE[V]) InsertPrefix(uint8, V) (_ bool) { return } +func (n *_NODE_TYPE[V]) DeletePrefix(uint8) (_ bool) { return } +func (n *_NODE_TYPE[V]) GetChild(uint8) (_ any, _ bool) { return } +func (n *_NODE_TYPE[V]) GetPrefix(uint8) (_ V, _ bool) { return } +func (n *_NODE_TYPE[V]) InsertChild(uint8, any) (_ bool) { return } +func (n *_NODE_TYPE[V]) DeleteChild(uint8) (_ bool) { return } +func (n *_NODE_TYPE[V]) CloneRec(func(V) V) (_ *_NODE_TYPE[V]) { return } +func (n *_NODE_TYPE[V]) CloneFlat(func(V) V) (_ *_NODE_TYPE[V]) { return } +func (n *_NODE_TYPE[V]) AllIndices() (seq2 iter.Seq2[uint8, V]) { return } +func (n *_NODE_TYPE[V]) AllChildren() (seq2 iter.Seq2[uint8, any]) { return } +func (n *_NODE_TYPE[V]) Contains(uint8) (_ bool) { return } +func (n *_NODE_TYPE[V]) LookupIdx(uint8) (_ uint8, _ V, _ bool) { return } // ### GENERATE DELETE END ### -// Insert inserts a network prefix and its associated value into the -// trie starting at the specified byte depth. +// Insert adds or updates a network prefix and its associated value in the trie. +// Traversal begins at the specified byte depth. // -// The function traverses the prefix address from the given depth and inserts -// the value either directly into the node's prefix table or as a compressed -// leaf or fringe node. If a conflicting leaf or fringe exists, it creates -// a new intermediate node to accommodate both entries. +// The trie utilizes path compression to conserve memory. A prefix is inserted: +// - Uncompressed into a node's prefix table at depth == strideCount. +// - As a path-compressed FringeNode if it qualifies as fringe [IsFringe]. +// - As a path-compressed LeafNode otherwise. +// +// When a new prefix collides with an existing compressed node (Leaf or Fringe), +// Insert resolves the collision by creating a new intermediate node, pushing +// the existing entry down to the next level, and continuing traversal. // // Parameters: -// - pfx: The network prefix to insert (must be in canonical form) -// - val: The value to associate with the prefix -// - depth: The current depth in the trie (0-based byte index) +// - pfx: The network prefix to insert (must be in canonical/masked form). +// - val: The value to associate with the prefix. +// - depth: The current depth in the trie (0-based byte index). // -// Returns true if a prefix already existed and was updated, false for new insertions. +// Returns true if an existing prefix was updated, false if a new insertion occurred. func (n *_NODE_TYPE[V]) Insert(pfx netip.Prefix, val V, depth int) (exists bool) { ip := pfx.Addr() // the pfx must be in canonical form + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // find the proper trie node to insert prefix - // start with prefix octet at depth + // Traverse the prefix's octets. Each depth corresponds to an 8-bit stride. + // We descend through the trie until we either reach the final stride (depth == strideCount) + // or find an empty child slot where we can path-compress the remaining strides. for ; depth < len(octets); depth++ { octet := octets[depth] - // last masked octet: insert/override prefix/val into node - if depth == lastOctetPlusOne { - return n.InsertPrefix(art.PfxToIdx(octet, lastBits), val) + // The current depth matches the prefix's stride count, meaning this is the final + // node for this prefix. We insert it directly into this node's prefix table. + if depth == strideCount { + return n.InsertPrefix(art.PfxToIdx(octet, modBits), val) } - // reached end of trie path ... + // No child exists at this octet path. Instead of creating intermediate nodes + // for the remaining strides, we path-compress the rest of the prefix into a single child slot. if !n.Children.Test(octet) { - // insert prefix path compressed as leaf or fringe - if IsFringe(depth, pfx) { + // If the prefix is perfectly aligned with the next stride boundary (e.g., /16 at depth 1), + // it acts as a default route for everything below it. We store it as a FringeNode. + // Otherwise, it has trailing bits or crosses boundaries, so we store it as a LeafNode. + if IsFringe(depth, pfxLen) { return n.InsertChild(octet, NewFringeNode(val)) } return n.InsertChild(octet, NewLeafNode(pfx, val)) } - // ... or descend down the trie + // A child already exists at this octet path. Retrieve it to either continue + // our descent along the strides or resolve a structural collision with a compressed node. kid := n.MustGetChild(octet) - // kid is node or leaf at addr switch kid := kid.(type) { case *_NODE_TYPE[V]: - n = kid // descend down to next trie level + // Standard intermediate node: descend to the next trie level. + n = kid case *LeafNode[V]: - // reached a path compressed prefix - // override value in slot if prefixes are equal + // Collision with an existing path-compressed LeafNode. + // If it's the exact same prefix, simply update the value. if kid.Prefix == pfx { kid.Value = val - // exists return true } - // create new node - // push the leaf down - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: the paths diverge. + // 1. Create a new intermediate node. + // 2. Push the existing leaf down into this new node. + // 3. Replace the current child slot with the new node. + // 4. Descend into the new node to continue inserting 'pfx'. newNode := new(_NODE_TYPE[V]) newNode.Insert(kid.Prefix, kid.Value, depth+1) @@ -117,18 +128,17 @@ func (n *_NODE_TYPE[V]) Insert(pfx netip.Prefix, val V, depth int) (exists bool) n = newNode case *FringeNode[V]: - // reached a path compressed fringe - // override value in slot if pfx is a fringe - if IsFringe(depth, pfx) { + // Collision with an existing path-compressed FringeNode. + // If the incoming prefix is also a fringe at this depth, update the value. + if IsFringe(depth, pfxLen) { kid.Value = val - // exists return true } - // create new node - // push the fringe down, it becomes a default route (idx=1) - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: + // The existing FringeNode acts as a catch-all (default route) for this sub-trie. + // To allow the incoming prefix to branch further, we expand the FringeNode + // into a full intermediate node and place its value at the default route index (1). newNode := new(_NODE_TYPE[V]) newNode.InsertPrefix(1, kid.Value) @@ -142,63 +152,74 @@ func (n *_NODE_TYPE[V]) Insert(pfx netip.Prefix, val V, depth int) (exists bool) panic("unreachable") } -// InsertPersist is similar to insert but the receiver isn't modified. -// Assumes the caller has pre-cloned the root (COW). It clones the -// internal nodes along the descent path before mutating them. -func (n *_NODE_TYPE[V]) InsertPersist(cloneFn value.CloneFunc[V], pfx netip.Prefix, val V, depth int) (exists bool) { +// InsertPersist adds or updates a network prefix and its associated value in the trie +// using Copy-On-Write (COW) semantics. Traversal begins at the specified byte depth. +// +// Unlike [Insert], InsertPersist ensures structural integrity of the existing tree +// by cloning internal nodes along the descent path (Copy-On-Write) before mutation. +// +// Parameters: +// - cloneFn: The function used to clone values (V). +// - pfx: The network prefix to insert (must be in canonical/masked form). +// - val: The value to associate with the prefix. +// - depth: The current depth in the trie (0-based byte index). +// +// Returns true if an existing prefix was updated, false if a new insertion occurred. +func (n *_NODE_TYPE[V]) InsertPersist(cloneFn func(V) V, pfx netip.Prefix, val V, depth int) (exists bool) { ip := pfx.Addr() // the pfx must be in canonical form + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // find the proper trie node to insert prefix - // start with prefix octet at depth + // Traverse the prefix's octets. Each depth corresponds to an 8-bit stride. + // We descend through the trie until we either reach the final stride (depth == strideCount) + // or find an empty child slot where we can path-compress the remaining strides. for ; depth < len(octets); depth++ { octet := octets[depth] - // last masked octet: insert/override prefix/val into node - if depth == lastOctetPlusOne { - return n.InsertPrefix(art.PfxToIdx(octet, lastBits), val) + // The current depth matches the prefix's stride count, meaning this is the final + // node for this prefix. We insert it directly into this node's prefix table. + if depth == strideCount { + return n.InsertPrefix(art.PfxToIdx(octet, modBits), val) } - // reached end of trie path ... + // No child exists at this octet path. Instead of creating intermediate nodes + // for the remaining strides, we path-compress the rest of the prefix into a single child slot. if !n.Children.Test(octet) { - // insert prefix path compressed as leaf or fringe - if IsFringe(depth, pfx) { + // If the prefix is perfectly aligned with the next stride boundary (e.g., /16 at depth 1), + // it acts as a default route for everything below it. We store it as a FringeNode. + // Otherwise, it has trailing bits or crosses boundaries, so we store it as a LeafNode. + if IsFringe(depth, pfxLen) { return n.InsertChild(octet, NewFringeNode(val)) } return n.InsertChild(octet, NewLeafNode(pfx, val)) } - // ... or descend down the trie + // A child already exists at this octet path. Retrieve it to either continue + // our descent along the strides or resolve a structural collision with a compressed node. kid := n.MustGetChild(octet) - // kid is node or leaf at addr switch kid := kid.(type) { case *_NODE_TYPE[V]: - // clone the traversed path - - // kid points now to cloned kid + // Standard intermediate node: Clone the traversed path to maintain persistence (COW). + // We clone the child node before modifying it, then replace the current child slot. kid = kid.CloneFlat(cloneFn) - - // replace kid with clone n.InsertChild(octet, kid) - n = kid - continue // descend down to next trie level case *LeafNode[V]: - // reached a path compressed prefix - // override value in slot if prefixes are equal + // Collision with an existing path-compressed LeafNode. + // If it's the exact same prefix, simply update the value. if kid.Prefix == pfx { kid.Value = val - // exists return true } - // create new node - // push the leaf down - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: the paths diverge. + // 1. Create a new intermediate node. + // 2. Push the existing leaf down into this new node. + // 3. Replace the current child slot with the new node. + // 4. Descend into the new node to continue inserting 'pfx'. newNode := new(_NODE_TYPE[V]) newNode.Insert(kid.Prefix, kid.Value, depth+1) @@ -206,18 +227,17 @@ func (n *_NODE_TYPE[V]) InsertPersist(cloneFn value.CloneFunc[V], pfx netip.Pref n = newNode case *FringeNode[V]: - // reached a path compressed fringe - // override value in slot if pfx is a fringe - if IsFringe(depth, pfx) { + // Collision with an existing path-compressed FringeNode. + // If the incoming prefix is also a fringe at this depth, update the value. + if IsFringe(depth, pfxLen) { kid.Value = val - // exists return true } - // create new node - // push the fringe down, it becomes a default route (idx=1) - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: + // The existing FringeNode acts as a catch-all (default route) for this sub-trie. + // To allow the incoming prefix to branch further, we expand the FringeNode + // into a full intermediate node and place its value at the default route index (1). newNode := new(_NODE_TYPE[V]) newNode.InsertPrefix(1, kid.Value) @@ -227,162 +247,170 @@ func (n *_NODE_TYPE[V]) InsertPersist(cloneFn value.CloneFunc[V], pfx netip.Pref default: panic("logic error, wrong node type") } - } - panic("unreachable") } -// PurgeAndCompress performs bottom-up compression of the trie. +// PurgeAndCompress performs bottom-up trie maintenance to restore path compression +// after a deletion. It unwinds the provided stack of parent nodes, identifying +// nodes that have become sparse (i.e., containing only a single prefix or a single +// child node) and prunes them by promoting the underlying entries to the parent level. // -// The function unwinds the provided stack of parent nodes, checking each level -// for compression opportunities based on child and prefix count. -// It may convert: -// - Nodes with a single prefix into leaf one level above. -// - Nodes with a single leaf or fringe into leaf one level above. +// This ensures the trie remains memory-efficient by collapsing redundant intermediate +// nodes back into path-compressed LeafNodes or FringeNodes whenever possible. // // Parameters: -// - stack: Array of parent nodes to process during unwinding -// - octets: The path of octets taken to reach the current position -// - is4: True for IPv4 processing, false for IPv6 +// - stack: Array of parent nodes to process during bottom-up unwinding. +// - octets: The full path of octets leading to the current node. +// - is4: True for IPv4 processing, false for IPv6. func (n *_NODE_TYPE[V]) PurgeAndCompress(stack []*_NODE_TYPE[V], octets []uint8, is4 bool) { - // unwind the stack + // Iterate backwards through the ancestor stack to prune nodes from the bottom up. for depth := len(stack) - 1; depth >= 0; depth-- { parent := stack[depth] octet := octets[depth] + // Check if the current node is redundant. + // A node may be redundant if it contains exactly one entry (either a prefix or + // a compressed child node). pfxCount := n.PrefixCount() childCount := n.ChildCount() + // If it contains more than one entry, it is always structurally significant and cannot be pruned. if pfxCount+childCount > 1 { return } switch { case childCount == 1: - singleAddr, _ := n.Children.FirstSet() // single addr must be first bit set + // The node has exactly one child. We determine if it is a path node, + // a compressed LeafNode, or a compressed FringeNode. + singleAddr, _ := n.Children.FirstSet() anyKid := n.MustGetChild(singleAddr) switch kid := anyKid.(type) { case *_NODE_TYPE[V]: - // fast exit, we are at an intermediate path node - // no further delete/compress upwards the stack is possible + // The child is an intermediate path node; the tree structure is required + // at this level. Compression cannot proceed further up. return case *LeafNode[V]: - // just one leaf, delete this node and reinsert the leaf above + // The child is a compressed LeafNode. Prune the current node + // and re-insert the leaf into the parent to elevate it. parent.DeleteChild(octet) - - // ... (re)insert the leaf at parents depth parent.Insert(kid.Prefix, kid.Value, depth) case *FringeNode[V]: - // just one fringe, delete this node and reinsert the fringe as leaf above + // The child is a compressed FringeNode. Prune the current node + // and re-insert the fringe as a leaf into the parent. parent.DeleteChild(octet) - // rebuild the prefix with octets, depth, ip version and addr - // depth is the parent's depth, so add +1 here for the kid - // lastOctet in cidrForFringe is the only addr (singleAddr) + // Reconstruct the full prefix for the fringe, as path compression + // requires the entire CIDR path, not just the remainder. + // depth is the parent's depth, so we offset by 1 for the kid's position. fringePfx := CidrForFringe(octets, depth+1, is4, singleAddr) - // ... (re)reinsert prefix/value at parents depth parent.Insert(fringePfx, kid.Value, depth) } case pfxCount == 1: - // just one prefix, delete this node and reinsert the idx as leaf above + // The node has exactly one prefix. Prune the node and elevate the + // prefix to the parent level as a leaf/fringe. parent.DeleteChild(octet) - // get prefix back from idx ... - idx, _ := n.Prefixes.FirstSet() // single idx must be first bit set + // Retrieve the single prefix stored in this node. + idx, _ := n.Prefixes.FirstSet() val := n.MustGetPrefix(idx) - // ... and octet path + // Reconstruct the prefix from the path for re-insertion. path := StridePath{} copy(path[:], octets) - - // depth is the parent's depth, so add +1 here for the kid pfx := CidrFromPath(path, depth+1, is4, idx) - // ... (re)insert prefix/value at parents depth parent.Insert(pfx, val, depth) + default: + panic("unreachable") } - // climb up the stack + // Move up to the next parent in the stack to continue pruning. n = parent } } -// Delete deletes the prefix and returns true if the prefix existed, -// or false otherwise. The prefix must be in canonical form. +// Delete removes the prefix from the trie rooted at n and returns true if the +// prefix existed, false if it was not found. The prefix must be in canonical +// (masked) form. +// +// The trie uses path compression, so a prefix may be stored in one of three ways: +// - In the current node's prefix table when the prefix length aligns exactly +// with the stride boundary at this depth (depth == strideCount). +// - As a path-compressed FringeNode in a child slot for stride-aligned prefixes +// (e.g. /8, /16, /24); occurs at depth == strideCount-1. +// - As a path-compressed LeafNode in a child slot for prefixes otherwise. +// +// After a successful deletion, PurgeAndCompress walks the ancestor stack to prune +// now-empty nodes and restore path compression upward. func (n *_NODE_TYPE[V]) Delete(pfx netip.Prefix) (exists bool) { - // invariant, prefix must be masked - - // values derived from pfx - ip := pfx.Addr() + ip := pfx.Addr() // pfx must be in canonical (masked) form + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // record the nodes on the path to the deleted node, needed to purge - // and/or path compress nodes after the deletion of a prefix + // Record ancestor nodes as we descend; PurgeAndCompress uses this stack to + // walk back up and clean up empty or re-compressible nodes after deletion. stack := [MaxTreeDepth]*_NODE_TYPE[V]{} - // find the trie node for depth, octet := range octets { - depth = depth & DepthMask // BCE, Delete must be fast + depth &= DepthMask // BCE hint; keep Delete on the fast path - // push current node on stack for path recording - stack[depth] = n + stack[depth] = n // record current node before descending - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - // try to delete prefix in trie node - if exists = n.DeletePrefix(art.PfxToIdx(octet, lastBits)); !exists { + // At the stride boundary, the prefix is stored directly in this node's + // prefix table. + if depth == strideCount { + if exists = n.DeletePrefix(art.PfxToIdx(octet, modBits)); !exists { return false } - // remove now-empty nodes and re-path-compress upwards + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) return true } + // no child node exists at this octet; the prefix is not in the trie if !n.Children.Test(octet) { return false } + + // A child node exists at this octet path, retrieve it. kid := n.MustGetChild(octet) - // kid is node or leaf or fringe at octet switch kid := kid.(type) { case *_NODE_TYPE[V]: - n = kid // descend down to next trie level + n = kid // descend to the next trie level case *FringeNode[V]: - // if pfx is no fringe at this depth, fast exit - if !IsFringe(depth, pfx) { + // A FringeNode holds a single stride-aligned prefix (/8, /16, ...). + // If pfx does not qualify as a fringe at this depth, it cannot be here. + if !IsFringe(depth, pfxLen) { return false } - // pfx is fringe at depth, delete fringe n.DeleteChild(octet) - // remove now-empty nodes and re-path-compress upwards + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true case *LeafNode[V]: - // Attention: pfx must be masked to be comparable! + // A LeafNode holds exactly one path-compressed prefix. + // Compare using the canonical (masked) form for an exact match. if kid.Prefix != pfx { return false } - // prefix is equal leaf, delete leaf n.DeleteChild(octet) - // remove now-empty nodes and re-path-compress upwards + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true default: @@ -393,142 +421,150 @@ func (n *_NODE_TYPE[V]) Delete(pfx netip.Prefix) (exists bool) { panic("unreachable") } -// DeletePersist is similar to delete but does not mutate the original trie. -// Assumes the caller has pre-cloned the root (COW). It clones the -// internal nodes along the descent path before mutating them. -func (n *_NODE_TYPE[V]) DeletePersist(cloneFn value.CloneFunc[V], pfx netip.Prefix) (exists bool) { - ip := pfx.Addr() // the pfx must be in canonical form +// DeletePersist removes the prefix from the trie rooted at n using Copy-On-Write (COW) semantics. +// It returns true if the prefix existed, false if it was not found. The prefix must be in +// canonical (masked) form. +// +// Like [Delete], this method uses path compression. However, DeletePersist ensures +// the structural integrity of the existing tree by cloning internal nodes along the +// descent path (COW) before mutation. +// +// After a successful deletion, PurgeAndCompress walks the ancestor stack to prune +// now-empty nodes and restore path compression upward. +func (n *_NODE_TYPE[V]) DeletePersist(cloneFn func(V) V, pfx netip.Prefix) (exists bool) { + ip := pfx.Addr() // pfx must be in canonical (masked) form + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // Stack to keep track of cloned nodes along the path, - // needed for purge and path compression after delete. + // Record ancestor nodes as we descend; PurgeAndCompress uses this stack to + // walk back up and clean up empty or re-compressible nodes after deletion. + // Since this is a COW operation, we store the cloned nodes here. stack := [MaxTreeDepth]*_NODE_TYPE[V]{} - // Traverse the trie to locate the prefix to delete. for depth, octet := range octets { - // Keep track of the cloned node at current depth. - stack[depth] = n + depth &= DepthMask // BCE hint; keep DeletePersist on the fast path + stack[depth] = n // record current node before descending - if depth == lastOctetPlusOne { - // Attempt to delete the prefix from the node's prefixes. - if exists = n.DeletePrefix(art.PfxToIdx(octet, lastBits)); !exists { - // Prefix not found, nothing deleted. + // At the stride boundary, the prefix is stored directly in this node's + // prefix table. + if depth == strideCount { + if exists = n.DeletePrefix(art.PfxToIdx(octet, modBits)); !exists { return false } - // After deletion, purge nodes and compress the path if needed. + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true } - addr := octet - - // If child node doesn't exist, no prefix to delete. - if !n.Children.Test(addr) { + // no child node exists at this octet; the prefix is not in the trie + if !n.Children.Test(octet) { return false } - // Fetch child node at current address. - kid := n.MustGetChild(addr) + // A child node exists at this octet path, retrieve it to either continue + // our descent or perform a persistent delete on a compressed node. + kid := n.MustGetChild(octet) switch kid := kid.(type) { case *_NODE_TYPE[V]: - // Clone the internal node for copy-on-write. + // Standard intermediate node: Clone the traversed path to maintain + // persistence (COW). We clone the child node before modifying it, + // then replace the current child slot. kid = kid.CloneFlat(cloneFn) - - // Replace child with cloned node. - n.InsertChild(addr, kid) - - // Descend to cloned child node. + n.InsertChild(octet, kid) n = kid continue case *FringeNode[V]: - // Reached a path compressed fringe. - if !IsFringe(depth, pfx) { - // Prefix to delete not found here. + // A FringeNode holds a single stride-aligned prefix (/8, /16, ...). + // If pfx does not qualify as a fringe at this depth, it cannot be here. + if !IsFringe(depth, pfxLen) { return false } - // Delete the fringe node. - n.DeleteChild(addr) + n.DeleteChild(octet) - // Purge and compress affected path. + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true case *LeafNode[V]: - // Reached a path compressed leaf node. + // A LeafNode holds exactly one path-compressed prefix. + // Compare using the canonical (masked) form for an exact match. if kid.Prefix != pfx { - // Leaf prefix does not match; nothing to delete. return false } - // Delete leaf node. - n.DeleteChild(addr) + n.DeleteChild(octet) - // Purge and compress affected path. + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true default: - // Unexpected node type indicates a logic error. panic("logic error, wrong node type") } } - // Should never happen: traversal always returns or panics inside loop. panic("unreachable") } // Get retrieves the value associated with the given network prefix. -// Returns the stored value and true if the prefix exists in this node, -// zero value and false if the prefix is not found. +// Traversal descends through the trie using the prefix's octets. +// +// The lookup handles path compression transparently: +// - If the path matches an internal node at the target stride, the value is retrieved +// from the node's prefix table. +// - If the path leads to a compressed LeafNode or FringeNode, the function verifies +// the prefix match before returning the value. // // Parameters: -// - pfx: The network prefix to look up (must be in canonical form) +// - pfx: The network prefix to look up (must be in canonical form). // // Returns: -// - val: The value associated with the prefix (zero value if not found) -// - exists: True if the prefix was found, false otherwise +// - val: The value associated with the prefix (zero value if not found). +// - exists: True if the prefix was found, false otherwise. func (n *_NODE_TYPE[V]) Get(pfx netip.Prefix) (val V, exists bool) { - // invariant, prefix must be masked - - // values derived from pfx + // The prefix must be provided in canonical (masked) form for correct trie traversal. ip := pfx.Addr() + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // find the trie node + // Traverse the trie octet by octet based on the prefix path. for depth, octet := range octets { - if depth == lastOctetPlusOne { - return n.GetPrefix(art.PfxToIdx(octet, lastBits)) + // At the target stride boundary, the prefix is expected in this node's + // prefix table. + if depth == strideCount { + return n.GetPrefix(art.PfxToIdx(octet, modBits)) } + // If no child exists at this path, the prefix is not in the trie. kidAny, ok := n.GetChild(octet) if !ok { return val, false } - // kid is node or leaf or fringe at octet + // Identify the node type at this path segment. switch kid := kidAny.(type) { case *_NODE_TYPE[V]: - n = kid // descend down to next trie level + // Standard intermediate node: descend to the next level. + n = kid case *FringeNode[V]: - // reached a path compressed fringe, stop traversing - if IsFringe(depth, pfx) { + // Reached a path-compressed FringeNode. + // Verify if the prefix qualifies as a fringe at this depth to return a match. + if IsFringe(depth, pfxLen) { return kid.Value, true } return val, false case *LeafNode[V]: - // reached a path compressed prefix, stop traversing + // Reached a path-compressed LeafNode. + // Check if the stored prefix matches the lookup prefix exactly. if kid.Prefix == pfx { return kid.Value, true } @@ -546,7 +582,7 @@ func (n *_NODE_TYPE[V]) Get(pfx netip.Prefix) (val V, exists bool) { // The callback receives the current value (if found) and existence flag, and returns // a new value and deletion flag. // -// modify returns the size delta (-1, 0, +1). +// Modify returns the size delta (-1, 0, +1). // This method handles path traversal, node creation for new paths, and automatic // purge/compress operations after deletions. // @@ -560,9 +596,10 @@ func (n *_NODE_TYPE[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V var zero V ip := pfx.Addr() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) // record the nodes on the path to the deleted node, needed to purge // and/or path compress nodes after the deletion of a prefix @@ -570,16 +607,15 @@ func (n *_NODE_TYPE[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V // find the proper trie node to update prefix for depth, octet := range octets { - depth = depth & DepthMask // BCE + depth &= DepthMask // BCE // push current node on stack for path recording stack[depth] = n - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx := art.PfxToIdx(octet, lastBits) + // The current depth matches the prefix's stride count, meaning this is the final + // stride for this prefix. Insert or update it directly in this node's prefix table. + if depth == strideCount { + idx := art.PfxToIdx(octet, modBits) oldVal, existed := n.GetPrefix(idx) newVal, del := cb(oldVal, existed) @@ -619,7 +655,7 @@ func (n *_NODE_TYPE[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V } // insert - if IsFringe(depth, pfx) { + if IsFringe(depth, pfxLen) { n.InsertChild(octet, NewFringeNode(newVal)) } else { n.InsertChild(octet, NewLeafNode(pfx, newVal)) @@ -678,7 +714,7 @@ func (n *_NODE_TYPE[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V case *FringeNode[V]: // update existing value if prefix is fringe - if IsFringe(depth, pfx) { + if IsFringe(depth, pfxLen) { newVal, del := cb(kid.Value, true) if !del { kid.Value = newVal @@ -808,8 +844,8 @@ func (n *_NODE_TYPE[V]) EqualRec(o *_NODE_TYPE[V]) bool { // // It returns immediately if n is nil or empty. For each visited internal node // it calls dump to write the node's representation, then iterates its child -// addresses and recurses into children that implement nodeDumper[V] (internal -// subnodes). The path slice and depth together represent the byte-wise path +// addresses and recurses into children of type *_NODE_TYPE[V] (internal subnodes). +// The path slice and depth together represent the byte-wise path // from the root to the current node; depth is incremented for each recursion. // The is4 flag controls IPv4/IPv6 formatting used by dump. func (n *_NODE_TYPE[V]) DumpRec(w io.Writer, path StridePath, depth int, is4 bool) { @@ -837,8 +873,8 @@ func (n *_NODE_TYPE[V]) dump(w io.Writer, path StridePath, depth int, is4 bool) bits := depth * strideLen indent := strings.Repeat(".", depth) - // printing values if V is not zero-sized - printValues := !value.IsZST[V]() + // printing values if V is not the empty struct{} + printValues := !value.IsEmptyStruct[V]() // node type with depth and octet path and bits. fmt.Fprintf(w, "\n%s[%s] depth: %d path: [%s] / %d\n", @@ -1001,7 +1037,7 @@ func (n *_NODE_TYPE[V]) DumpString(octets []uint8, depth int, is4 bool) string { // - stopNode: has children but no subnodes (nodes == 0) // - halfNode: contains at least one leaf or fringe and also has subnodes, but // no prefixes -// - fullNode: has prefixes or leaves/fringes and also has subnodes +// - fullNode: has prefixes and also has subnodes // - pathNode: has subnodes only (no prefixes, leaves, or fringes) // // The order of these checks is significant to ensure the correct classification. @@ -1058,7 +1094,7 @@ func (n *_NODE_TYPE[V]) Stats() (s StatsT) { // It walks the node tree recursively and sums immediate counts (prefixes and // child slots) plus the number of nodes, leaves, and fringe nodes in the // subtree. If n is nil or empty, a zeroed stats is returned. The returned -// stats.nodes includes the current node. The function will panic if a child +// SubNodes count includes the current node. The function will panic if a child // has an unexpected concrete type. func (n *_NODE_TYPE[V]) StatsRec() (s StatsT) { if n == nil || n.IsEmpty() { @@ -1114,8 +1150,8 @@ func (n *_NODE_TYPE[V]) FprintRec(w io.Writer, parent TrieItem[V], pad string) e return CmpPrefix(a.Cidr, b.Cidr) }) - // printing values if V is not zero-sized - printValues := !value.IsZST[V]() + // printing values if V is not the empty struct{} + printValues := !value.IsEmptyStruct[V]() // for all direct item under this node ... for i, item := range directItems { @@ -1212,7 +1248,7 @@ func (n *_NODE_TYPE[V]) DirectItemsRec(parentIdx uint8, path StridePath, depth i path[depth] = addr directItems = append(directItems, kid.DirectItemsRec(0, path, depth+1, is4)...) - case *LeafNode[V]: // path-compressed child, stop's recursion for this child + case *LeafNode[V]: // path-compressed child, stops recursion for this child item := TrieItem[V]{ Node: nil, Is4: is4, @@ -1221,7 +1257,7 @@ func (n *_NODE_TYPE[V]) DirectItemsRec(parentIdx uint8, path StridePath, depth i } directItems = append(directItems, item) - case *FringeNode[V]: // path-compressed fringe, stop's recursion for this child + case *FringeNode[V]: // path-compressed fringe, stops recursion for this child item := TrieItem[V]{ Node: nil, Is4: is4, @@ -1255,9 +1291,9 @@ func (n *_NODE_TYPE[V]) DirectItemsRec(parentIdx uint8, path StridePath, depth i // The merge operation is destructive on the receiver n, but leaves the source node o unchanged. // // Returns the number of duplicate prefixes that were overwritten during merging. -func (n *_NODE_TYPE[V]) UnionRec(cloneFn value.CloneFunc[V], o *_NODE_TYPE[V], depth int) (duplicates int) { +func (n *_NODE_TYPE[V]) UnionRec(cloneFn func(V) V, o *_NODE_TYPE[V], depth int) (duplicates int) { if cloneFn == nil { - cloneFn = value.CopyVal + cloneFn = func(v V) V { return v } } buf := [256]uint8{} @@ -1288,9 +1324,9 @@ func (n *_NODE_TYPE[V]) UnionRec(cloneFn value.CloneFunc[V], o *_NODE_TYPE[V], d } // UnionRecPersist is similar to unionRec but performs an immutable union of nodes. -func (n *_NODE_TYPE[V]) UnionRecPersist(cloneFn value.CloneFunc[V], o *_NODE_TYPE[V], depth int) (duplicates int) { +func (n *_NODE_TYPE[V]) UnionRecPersist(cloneFn func(V) V, o *_NODE_TYPE[V], depth int) (duplicates int) { if cloneFn == nil { - cloneFn = value.CopyVal + cloneFn = func(v V) V { return v } } buf := [256]uint8{} @@ -1339,7 +1375,7 @@ func (n *_NODE_TYPE[V]) UnionRecPersist(cloneFn value.CloneFunc[V], o *_NODE_TYP // fringe, node <-- insert new node, push this fringe down, union rec-descent // fringe, leaf <-- insert new node, push this fringe down, insert other leaf at depth+1 // fringe, fringe <-- just overwrite value -func (n *_NODE_TYPE[V]) handleMatrix(cloneFn value.CloneFunc[V], thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { +func (n *_NODE_TYPE[V]) handleMatrix(cloneFn func(V) V, thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { // Do ALL type assertions upfront - reduces line noise var ( thisNode, thisIsNode = thisChild.(*_NODE_TYPE[V]) @@ -1456,7 +1492,7 @@ func (n *_NODE_TYPE[V]) handleMatrix(cloneFn value.CloneFunc[V], thisExists bool // fringe, node <-- insert new node, push this fringe down, union rec-descent // fringe, leaf <-- insert new node, push this fringe down, insert other leaf at depth+1 // fringe, fringe <-- just overwrite value -func (n *_NODE_TYPE[V]) handleMatrixPersist(cloneFn value.CloneFunc[V], thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { +func (n *_NODE_TYPE[V]) handleMatrixPersist(cloneFn func(V) V, thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { // Do ALL type assertions upfront - reduces line noise var ( thisNode, thisIsNode = thisChild.(*_NODE_TYPE[V]) @@ -1688,9 +1724,7 @@ func (n *_NODE_TYPE[V]) AllRecSorted(path StridePath, depth int, is4 bool, yield } case *FringeNode[V]: fringePfx := CidrForFringe(path[:], depth, is4, childAddr) - // callback for this fringe if !yield(fringePfx, kid.Value) { - // early exit return false } @@ -1725,9 +1759,7 @@ func (n *_NODE_TYPE[V]) AllRecSorted(path StridePath, depth int, is4 bool, yield } case *FringeNode[V]: fringePfx := CidrForFringe(path[:], depth, is4, addr) - // callback for this fringe if !yield(fringePfx, kid.Value) { - // early exit return false } @@ -1913,9 +1945,10 @@ func (n *_NODE_TYPE[V]) EachSubnet(octets []byte, depth int, is4 bool, pfxIdx ui // the iteration early. func (n *_NODE_TYPE[V]) Supernets(pfx netip.Prefix, yield func(netip.Prefix, V) bool) { ip := pfx.Addr() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) // stack of the traversed nodes for reverse ordering of supernets stack := [MaxTreeDepth]*_NODE_TYPE[V]{} @@ -1928,7 +1961,7 @@ func (n *_NODE_TYPE[V]) Supernets(pfx netip.Prefix, yield func(netip.Prefix, V) LOOP: for depth, octet = range octets { // stepped one past the last stride of interest; back up to last and exit - if depth > lastOctetPlusOne { + if depth > strideCount { depth-- break } @@ -1989,11 +2022,10 @@ LOOP: // all others are just host routes var idx uint8 octet = octets[depth] - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx = art.PfxToIdx(octet, lastBits) + // Last “octet” from prefix + // Note: For /32 and /128, depth never reaches strideCount (4/16), + if depth == strideCount { + idx = art.PfxToIdx(octet, modBits) } else { idx = art.OctetToIdx(octet) } @@ -2030,17 +2062,18 @@ LOOP: func (n *_NODE_TYPE[V]) Subnets(pfx netip.Prefix, yield func(netip.Prefix, V) bool) { // values derived from pfx ip := pfx.Addr() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) // find the trie node for depth, octet := range octets { - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), + // Last “octet” from prefix + // Note: For /32 and /128, depth never reaches strideCount (4/16), // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx := art.PfxToIdx(octet, lastBits) + if depth == strideCount { + idx := art.PfxToIdx(octet, modBits) n.EachSubnet(octets, depth, is4, idx, yield) return } @@ -2158,7 +2191,7 @@ func (n *_NODE_TYPE[V]) Overlaps(o *_NODE_TYPE[V], depth int) bool { // OverlapsRoutes compares the prefix sets of two nodes (n and o). // // It first checks for direct bitset intersection (identical indices), -// then walks both prefix sets using lpmTest to detect if any +// then walks both prefix sets using the Contains method to detect if any // of the n-prefixes is contained in o, or vice versa. func (n *_NODE_TYPE[V]) OverlapsRoutes(o *_NODE_TYPE[V]) bool { // some prefixes are identical, trivial overlap @@ -2301,19 +2334,20 @@ func (n *_NODE_TYPE[V]) OverlapsSameChildren(o *_NODE_TYPE[V], depth int) bool { // trie traversal across varying prefix lengths and compression levels. func (n *_NODE_TYPE[V]) OverlapsPrefixAtDepth(pfx netip.Prefix, depth int) bool { ip := pfx.Addr() + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) for ; depth < len(octets); depth++ { - if depth > lastOctetPlusOne { + if depth > strideCount { break } octet := octets[depth] // full octet path in node trie, check overlap with last prefix octet - if depth == lastOctetPlusOne { - return n.OverlapsIdx(art.PfxToIdx(octet, lastBits)) + if depth == strideCount { + return n.OverlapsIdx(art.PfxToIdx(octet, modBits)) } // test if any route overlaps prefix´ so far diff --git a/vendor/github.com/gaissmai/bart/internal/nodes/commontests_tmpl.go b/vendor/github.com/gaissmai/bart/internal/nodes/commontests_tmpl.go index fd2262cba4..d600ebcdf8 100644 --- a/vendor/github.com/gaissmai/bart/internal/nodes/commontests_tmpl.go +++ b/vendor/github.com/gaissmai/bart/internal/nodes/commontests_tmpl.go @@ -1,6 +1,6 @@ //go:build generate -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT //go:generate ../../scripts/generate-node-tests.sh @@ -35,8 +35,8 @@ func (n *_NODE_TYPE[V]) DumpRec(io.Writer, StridePath, int, bool) func (n *_NODE_TYPE[V]) FprintRec(io.Writer, TrieItem[V], string) (_ error) { return } func (n *_NODE_TYPE[V]) Insert(netip.Prefix, V, int) (_ bool) { return } func (n *_NODE_TYPE[V]) Delete(netip.Prefix) (_ bool) { return } -func (n *_NODE_TYPE[V]) InsertPersist(value.CloneFunc[V], netip.Prefix, V, int) (_ bool) { return } -func (n *_NODE_TYPE[V]) DeletePersist(value.CloneFunc[V], netip.Prefix) (_ bool) { return } +func (n *_NODE_TYPE[V]) InsertPersist(func(V) V, netip.Prefix, V, int) (_ bool) { return } +func (n *_NODE_TYPE[V]) DeletePersist(func(V) V, netip.Prefix) (_ bool) { return } func (n *_NODE_TYPE[V]) Subnets(netip.Prefix, func(netip.Prefix, V) bool) { return } func (n *_NODE_TYPE[V]) Supernets(netip.Prefix, func(netip.Prefix, V) bool) { return } func (n *_NODE_TYPE[V]) AllRec(StridePath, int, bool, func(netip.Prefix, V) bool) (_ bool) { return } @@ -766,3 +766,501 @@ func TestFprintRec_NonZST__NODE_TYPE(t *testing.T) { t.Errorf("Expected prefix and value 'testval' in output, but got:\n%s", output) } } + +// TestEqualRec__NODE_TYPE tests the recursive node equality comparison. +func TestEqualRec__NODE_TYPE(t *testing.T) { + t.Parallel() + + // nil checks + var nilNode1, nilNode2 *_NODE_TYPE[int] + if !nilNode1.EqualRec(nilNode2) { + t.Error("nil nodes should be equal") + } + + n1 := new(_NODE_TYPE[int]) + if n1.EqualRec(nilNode1) { + t.Error("non-nil node should not be equal to nil") + } + if nilNode1.EqualRec(n1) { + t.Error("nil node should not be equal to non-nil") + } + + // identical nodes + if !n1.EqualRec(n1) { + t.Error("node should be equal to itself") + } + + // different prefix bitsets + n2 := new(_NODE_TYPE[int]) + n1.Insert(mpp("10.0.0.0/7"), 42, 0) + if n1.EqualRec(n2) { + t.Error("nodes with different prefixes should not be equal") + } + + // different values + // Skip for LiteNode because it has no real payload values + if _, isLite := any(n1).(*LiteNode[int]); !isLite { + n2.Insert(mpp("10.0.0.0/7"), 43, 0) + if n1.EqualRec(n2) { + t.Error("nodes with same prefixes but different values should not be equal") + } + } + + // same prefix and value + n2_same := new(_NODE_TYPE[int]) + n2_same.Insert(mpp("10.0.0.0/7"), 42, 0) + if _, isLite := any(n1).(*LiteNode[int]); !isLite { + if !n1.EqualRec(n2_same) { + t.Error("nodes with same prefixes and values should be equal") + } + } + + // different children bitsets + n3 := new(_NODE_TYPE[int]) + n4 := new(_NODE_TYPE[int]) + n3.Insert(mpp("10.0.0.0/8"), 42, 0) // this is a fringe node at depth 1, and inserts into Children + if n3.EqualRec(n4) { + t.Error("nodes with different children should not be equal") + } + + // fringe nodes with different values + if _, isLite := any(n3).(*LiteNode[int]); !isLite { + n4.Insert(mpp("10.0.0.0/8"), 43, 0) + if n3.EqualRec(n4) { + t.Error("nodes with different fringe values should not be equal") + } + } + + // leaf nodes with different prefixes/values + n5 := new(_NODE_TYPE[int]) + n6 := new(_NODE_TYPE[int]) + n5.Insert(mpp("10.20.0.0/15"), 42, 0) // leaf node at depth 1 + n6.Insert(mpp("10.30.0.0/15"), 42, 0) // different leaf node prefix + if n5.EqualRec(n6) { + t.Error("nodes with different leaf prefixes should not be equal") + } + + if _, isLite := any(n5).(*LiteNode[int]); !isLite { + n7 := new(_NODE_TYPE[int]) + n7.Insert(mpp("10.20.0.0/15"), 43, 0) // different leaf value + if n5.EqualRec(n7) { + t.Error("nodes with different leaf values should not be equal") + } + } +} + +// TestOverlapsExtra__NODE_TYPE tests specific edge cases in trie overlap detection. +func TestOverlapsExtra__NODE_TYPE(t *testing.T) { + t.Parallel() + + // 1. Test OverlapsSameChildren loop continuation & completion (returning false) + t.Run("same_children_no_overlap", func(t *testing.T) { + t.Parallel() + n1 := new(_NODE_TYPE[int]) + n2 := new(_NODE_TYPE[int]) + + // Insert disjoint child subnets at address 10 and 20 + n1.Insert(mpp("10.10.1.0/24"), 42, 0) + n1.Insert(mpp("10.20.1.0/24"), 42, 0) + + n2.Insert(mpp("10.10.2.0/24"), 42, 0) + n2.Insert(mpp("10.20.2.0/24"), 42, 0) + + if n1.Overlaps(n2, 0) { + t.Error("expected no overlap between disjoint child subnets") + } + }) + + // 2. Test OverlapsSameChildren loop continuation & early exit (returning true) + t.Run("same_children_first_no_overlap_second_overlap", func(t *testing.T) { + t.Parallel() + n1 := new(_NODE_TYPE[int]) + n2 := new(_NODE_TYPE[int]) + + // Address 10 does not overlap, address 20 does overlap + n1.Insert(mpp("10.10.1.0/24"), 42, 0) + n1.Insert(mpp("10.20.1.0/24"), 42, 0) + + n2.Insert(mpp("10.10.2.0/24"), 42, 0) + n2.Insert(mpp("10.20.1.0/24"), 42, 0) // overlap here + + if !n1.Overlaps(n2, 0) { + t.Error("expected overlap since second child subnet overlaps") + } + }) + + // 3. Test OverlapsSameChildren with matching child at address 255 that does not overlap + t.Run("same_children_boundary_255_no_overlap", func(t *testing.T) { + t.Parallel() + n1 := new(_NODE_TYPE[int]) + n2 := new(_NODE_TYPE[int]) + + n1.Insert(mpp("10.255.1.0/24"), 42, 0) + n2.Insert(mpp("10.255.2.0/24"), 42, 0) + + if n1.Overlaps(n2, 0) { + t.Error("expected no overlap on boundary 255") + } + }) + + // 4. Test OverlapsPrefixAtDepth with LeafNode and FringeNode + t.Run("prefix_overlaps_leaf_node_true", func(t *testing.T) { + t.Parallel() + n1 := new(_NODE_TYPE[int]) + n1.Insert(mpp("10.20.0.0/15"), 42, 0) + + if !n1.OverlapsPrefixAtDepth(mpp("10.20.0.0/16"), 0) { + t.Error("expected overlap with leaf node prefix") + } + }) + + t.Run("prefix_overlaps_leaf_node_false", func(t *testing.T) { + t.Parallel() + n1 := new(_NODE_TYPE[int]) + n1.Insert(mpp("10.20.0.0/15"), 42, 0) + + if n1.OverlapsPrefixAtDepth(mpp("10.30.0.0/16"), 0) { + t.Error("expected no overlap with disjoint prefix") + } + }) + + t.Run("prefix_overlaps_fringe_node", func(t *testing.T) { + t.Parallel() + n1 := new(_NODE_TYPE[int]) + n1.Insert(mpp("10.0.0.0/8"), 42, 0) + + if !n1.OverlapsPrefixAtDepth(mpp("10.0.0.0/16"), 0) { + t.Error("expected overlap with fringe node") + } + }) + + // 5. Test OverlapsPrefixAtDepth with deeper path walk + t.Run("prefix_overlaps_idx_at_last_octet", func(t *testing.T) { + t.Parallel() + n1 := new(_NODE_TYPE[int]) + n1.Insert(mpp("10.20.30.0/24"), 42, 0) + + if !n1.OverlapsPrefixAtDepth(mpp("10.20.0.0/16"), 0) { + t.Error("expected overlap at last octet") + } + }) + + t.Run("prefix_overlaps_contains_at_depth", func(t *testing.T) { + t.Parallel() + n1 := new(_NODE_TYPE[int]) + n1.Insert(mpp("10.128.30.0/24"), 42, 0) + n1.Insert(mpp("10.128.0.0/9"), 42, 0) + + if !n1.OverlapsPrefixAtDepth(mpp("10.128.30.0/24"), 0) { + t.Error("expected overlap with contains check at depth 1") + } + }) +} + +// TestDumpStringExtra__NODE_TYPE tests type classification in DumpString and DumpRec. +func TestDumpStringExtra__NODE_TYPE(t *testing.T) { + t.Parallel() + + // 1. Create a halfNode at the root + n1 := new(_NODE_TYPE[int]) + // LeafNode under child slot 10: + n1.Insert(mpp("10.20.0.0/15"), 42, 0) + // Internal node under child slot 11 (requires branching to prevent path compression): + n1.Insert(mpp("11.30.40.0/24"), 42, 0) + n1.Insert(mpp("11.30.50.0/24"), 42, 0) + + dump1 := n1.DumpString(nil, 0, true) + if !strings.Contains(dump1, "HALF") { + t.Errorf("expected HALF node type in dump, got:\n%s", dump1) + } + + // 2. Create a pathNode at the root + n2 := new(_NODE_TYPE[int]) + // Branching paths under child slot 10 to create an internal node at root child 10 + n2.Insert(mpp("10.20.30.0/24"), 42, 0) + n2.Insert(mpp("10.20.40.0/24"), 42, 0) + + dump2 := n2.DumpString(nil, 0, true) + if !strings.Contains(dump2, "PATH") { + t.Errorf("expected PATH node type in dump, got:\n%s", dump2) + } + + // 3. Test DumpRec on nil/empty node + var nilNode *_NODE_TYPE[int] + var buf bytes.Buffer + nilNode.DumpRec(&buf, StridePath{}, 0, true) + if buf.Len() != 0 { + t.Error("expected empty buffer for nil DumpRec") + } + + buf.Reset() + emptyNode := new(_NODE_TYPE[int]) + emptyNode.DumpRec(&buf, StridePath{}, 0, true) + if buf.Len() != 0 { + t.Error("expected empty buffer for empty DumpRec") + } +} + +// TestDeletePurgeExtra__NODE_TYPE tests the bottom-up trie compression on delete. +func TestDeletePurgeExtra__NODE_TYPE(t *testing.T) { + t.Parallel() + + // 1. Trigger PurgeAndCompress: case childCount == 1 with LeafNode + t.Run("purge_leaf_node", func(t *testing.T) { + t.Parallel() + n := new(_NODE_TYPE[int]) + n.Insert(mpp("10.20.10.0/24"), 42, 0) + n.Insert(mpp("10.20.20.0/24"), 42, 0) + + n.Delete(mpp("10.20.20.0/24")) + + if _, ok := n.Get(mpp("10.20.10.0/24")); !ok { + t.Error("expected remaining leaf node to be present") + } + }) + + // 2. Trigger PurgeAndCompress: case childCount == 1 with FringeNode + t.Run("purge_fringe_node", func(t *testing.T) { + t.Parallel() + n := new(_NODE_TYPE[int]) + n.Insert(mpp("10.20.0.0/16"), 42, 0) + n.Insert(mpp("10.30.0.0/16"), 42, 0) + + n.Delete(mpp("10.30.0.0/16")) + + if _, ok := n.Get(mpp("10.20.0.0/16")); !ok { + t.Error("expected remaining fringe node to be present") + } + }) + + // 3. Trigger PurgeAndCompress: case pfxCount == 1 + t.Run("purge_single_prefix", func(t *testing.T) { + t.Parallel() + n := new(_NODE_TYPE[int]) + n.Insert(mpp("10.20.0.0/16"), 42, 0) + n.Insert(mpp("10.0.0.0/8"), 42, 0) + + n.Delete(mpp("10.20.0.0/16")) + + if _, ok := n.Get(mpp("10.0.0.0/8")); !ok { + t.Error("expected remaining prefix to be present") + } + }) +} + +// TestUnionRecExtra__NODE_TYPE tests specific edge cases in trie Union operations. +func TestUnionRecExtra__NODE_TYPE(t *testing.T) { + t.Parallel() + + _, isLite := any(new(_NODE_TYPE[int])).(*LiteNode[int]) + + // 1. Cover handleMatrix case 2: thisIsNode && otherIsLeaf, where leaf already exists in thisNode + t.Run("node_leaf_exists", func(t *testing.T) { + t.Parallel() + n1 := new(_NODE_TYPE[int]) + n2 := new(_NODE_TYPE[int]) + + // Both n1 and n2 have child at octet 10, but: + // n1's child is an internal node (requires branching to prevent path compression) + n1.Insert(mpp("10.10.10.0/24"), 42, 0) + n1.Insert(mpp("10.10.20.0/24"), 42, 0) + + // n2's child is a LeafNode with "10.10.20.0/24" + n2.Insert(mpp("10.10.20.0/24"), 43, 0) + + n1.UnionRec(value.CloneFnFactory[int](), n2, 0) + + val, ok := n1.Get(mpp("10.10.20.0/24")) + if !ok { + t.Fatal("expected prefix to exist") + } + if !isLite && val != 43 { + t.Errorf("expected value 43, got %v", val) + } + }) + + // 2. Cover handleMatrix case 2: thisIsNode && otherIsFringe, where fringe already exists in thisNode + t.Run("node_fringe_exists", func(t *testing.T) { + t.Parallel() + n1 := new(_NODE_TYPE[int]) + n2 := new(_NODE_TYPE[int]) + + // Both n1 and n2 have child at octet 10. + // n1's child is an internal node and contains the default route/fringe "10.0.0.0/8" + n1.Insert(mpp("10.10.10.0/24"), 42, 0) + n1.Insert(mpp("10.0.0.0/8"), 42, 0) + + // n2's child is a FringeNode "10.0.0.0/8" + n2.Insert(mpp("10.0.0.0/8"), 43, 0) + + n1.UnionRec(value.CloneFnFactory[int](), n2, 0) + + val, ok := n1.Get(mpp("10.0.0.0/8")) + if !ok { + t.Fatal("expected prefix to exist") + } + if !isLite && val != 43 { + t.Errorf("expected value 43, got %v", val) + } + }) + + // 3. Cover handleMatrixPersist case 2 counterpart + t.Run("node_leaf_exists_persist", func(t *testing.T) { + t.Parallel() + n1 := new(_NODE_TYPE[int]) + n2 := new(_NODE_TYPE[int]) + + n1.Insert(mpp("10.10.10.0/24"), 42, 0) + n1.Insert(mpp("10.10.20.0/24"), 42, 0) + + n2.Insert(mpp("10.10.20.0/24"), 43, 0) + + n1.UnionRecPersist(value.CloneFnFactory[int](), n2, 0) + + val, ok := n1.Get(mpp("10.10.20.0/24")) + if !ok { + t.Fatal("expected prefix to exist") + } + if !isLite && val != 43 { + t.Errorf("expected value 43, got %v", val) + } + }) + + t.Run("node_fringe_exists_persist", func(t *testing.T) { + t.Parallel() + n1 := new(_NODE_TYPE[int]) + n2 := new(_NODE_TYPE[int]) + + n1.Insert(mpp("10.10.10.0/24"), 42, 0) + n1.Insert(mpp("10.0.0.0/8"), 42, 0) + + n2.Insert(mpp("10.0.0.0/8"), 43, 0) + + n1.UnionRecPersist(value.CloneFnFactory[int](), n2, 0) + + val, ok := n1.Get(mpp("10.0.0.0/8")) + if !ok { + t.Fatal("expected prefix to exist") + } + if !isLite && val != 43 { + t.Errorf("expected value 43, got %v", val) + } + }) +} + +// TestSubnetsEarlyExit__NODE_TYPE tests early termination in Subnets traversal. +func TestSubnetsEarlyExit__NODE_TYPE(t *testing.T) { + t.Parallel() + + n := new(_NODE_TYPE[int]) + n.Insert(mpp("10.0.0.0/8"), 1, 0) + n.Insert(mpp("10.10.10.0/24"), 2, 0) + n.Insert(mpp("10.10.20.0/24"), 3, 0) + n.Insert(mpp("10.20.0.0/15"), 4, 0) + + // Traversal 1: yield returns false immediately + yieldCount := 0 + n.Subnets(mpp("10.0.0.0/8"), func(pfx netip.Prefix, val int) bool { + yieldCount++ + return false // stop immediately + }) + + if yieldCount != 1 { + t.Errorf("expected yieldCount to be 1, got %d", yieldCount) + } + + // Traversal 2: yield returns false after 2 items + yieldCount = 0 + n.Subnets(mpp("10.0.0.0/8"), func(pfx netip.Prefix, val int) bool { + yieldCount++ + if yieldCount == 2 { + return false + } + return true + }) + + if yieldCount != 2 { + t.Errorf("expected yieldCount to be 2, got %d", yieldCount) + } +} + +// TestSupernetsExtra__NODE_TYPE tests specific edge cases and early exits in Supernets traversal. +func TestSupernetsExtra__NODE_TYPE(t *testing.T) { + t.Parallel() + + n := new(_NODE_TYPE[int]) + // LeafNode at depth 1: + n.Insert(mpp("10.20.0.0/15"), 1, 0) + // FringeNode at depth 1: + n.Insert(mpp("10.30.0.0/16"), 2, 0) + // Normal prefixes: + n.Insert(mpp("10.0.0.0/8"), 3, 0) + n.Insert(mpp("10.40.50.0/24"), 4, 0) + + // 1. LeafNode longer prefix check + yielded := []netip.Prefix{} + n.Supernets(mpp("10.20.0.0/15"), func(pfx netip.Prefix, val int) bool { + yielded = append(yielded, pfx) + return true + }) + if len(yielded) != 2 { + t.Errorf("expected 2 supernets, got %v", yielded) + } + + // 2. FringeNode longer prefix check + yielded = []netip.Prefix{} + n.Supernets(mpp("10.30.0.0/16"), func(pfx netip.Prefix, val int) bool { + yielded = append(yielded, pfx) + return true + }) + if len(yielded) != 2 { + t.Errorf("expected 2 supernets, got %v", yielded) + } + + // 3. Early exits on yield + // LeafNode yield false + yieldCount := 0 + n.Supernets(mpp("10.20.0.0/15"), func(pfx netip.Prefix, val int) bool { + yieldCount++ + if pfx == mpp("10.20.0.0/15") { + return false + } + return true + }) + if yieldCount != 1 { + t.Errorf("expected yieldCount 1 on leaf early exit, got %d", yieldCount) + } + + // FringeNode yield false + yieldCount = 0 + n.Supernets(mpp("10.30.0.0/16"), func(pfx netip.Prefix, val int) bool { + yieldCount++ + if pfx == mpp("10.30.0.0/16") { + return false + } + return true + }) + if yieldCount != 1 { + t.Errorf("expected yieldCount 1 on fringe early exit, got %d", yieldCount) + } + + // Backtracking yield false + yieldCount = 0 + n.Supernets(mpp("10.40.50.0/24"), func(pfx netip.Prefix, val int) bool { + yieldCount++ + return false + }) + if yieldCount != 1 { + t.Errorf("expected yieldCount 1 on backtracking early exit, got %d", yieldCount) + } + + // 4. Trigger depth > strideCount + yielded = []netip.Prefix{} + n.Supernets(mpp("10.40.0.0/16"), func(pfx netip.Prefix, val int) bool { + yielded = append(yielded, pfx) + return true + }) + if len(yielded) != 1 || yielded[0] != mpp("10.0.0.0/8") { + t.Errorf("expected only 10.0.0.0/8, got %v", yielded) + } +} diff --git a/vendor/github.com/gaissmai/bart/internal/nodes/fast.go b/vendor/github.com/gaissmai/bart/internal/nodes/fast.go index 2248fc8ecf..fdc5185c3b 100644 --- a/vendor/github.com/gaissmai/bart/internal/nodes/fast.go +++ b/vendor/github.com/gaissmai/bart/internal/nodes/fast.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package nodes @@ -6,236 +6,173 @@ package nodes import ( "iter" - "github.com/gaissmai/bart/internal/bitset" "github.com/gaissmai/bart/internal/lpm" - "github.com/gaissmai/bart/internal/value" + "github.com/gaissmai/bart/internal/sparse" ) -// FastNode is a trie level node in the multibit routing table. -// -// Each FastNode contains two sections (Prefixes and Children), each with -// a BitSet256 tracking occupied indices and a fixed-size Items array: -// - Prefixes.BitSet256: tracks which prefix indices are occupied. -// - Prefixes.Items: [256]*V storing routing entry values. -// - Children.BitSet256: tracks which child indices are occupied. -// - Children.Items: [256]*any holding subtries or path-compressed leaves/fringes -// (branching factor 256, 8 bits per stride). -// - PfxCount: cached count of active prefixes (avoids BitSet.Size() calls). -// - CldCount: cached count of child nodes (avoids BitSet.Size() calls). -// -// Prefixes use a complete binary tree layout driven by the baseIndex() function -// from the ART algorithm for fast LPM lookup. -// -// Entries in Children.Items (each *any containing a pointer) may be: -// - **FastNode[V] -> internal child node for further traversal -// - **LeafNode[V] -> path-comp. node (depth < maxDepth - 1) -// - **FringeNode[V] -> path-comp. node (depth == maxDepth - 1, stride-aligned) -// -// Note: Children.Items uses *any (pointer to any) instead of any to reduce memory by -// ~30%, since many slots are nil and *any takes 1 word vs 2 words for nil any. +// FastNode is based on [BartNode], but it also uses a cache ([256]uint8) +// per node to speed up traversal of the multi-bit trie. +// Lookups become faster, but this requires more memory per prefix, +// and updates (insertions/deletions) also become slower due to the +// overhead of managing the cache. type FastNode[V any] struct { - // Prefixes holding prefix -> value pointers, organized as a CBT - // for fast LPM lookup within the node. - Prefixes struct { - bitset.BitSet256 - Items [256]*V - } + Prefixes sparse.Array256[V] + Children sparse.Array256[any] + // map addr to slice idx (aka rank) + childRankCache [256]uint8 +} - // Children holding subtries or path-compressed leaves or fringes. - Children struct { - bitset.BitSet256 - Items [256]*any // pointer to any, see explanation above +// IsEmpty returns true if the node contains no routing entries (prefixes) +// and no child nodes. Empty nodes are candidates for compression or removal +// during trie optimization. +func (n *FastNode[V]) IsEmpty() bool { + if n == nil { + return true } - - // PfxCount replaces expensive BitSet256.Size() calls. Automatically - // maintained during InsertPrefix() and DeletePrefix() operations. - PfxCount uint16 - - // CldCount replaces expensive BitSet.Size() calls. Automatically - // maintained during InsertChild() and DeleteChild() operations. - CldCount uint16 + return n.Prefixes.Len() == 0 && n.Children.Len() == 0 } // PrefixCount returns the number of prefixes stored in this node. func (n *FastNode[V]) PrefixCount() int { - return int(n.PfxCount) + return n.Prefixes.Len() } // ChildCount returns the number of slots used in this node. func (n *FastNode[V]) ChildCount() int { - return int(n.CldCount) + return n.Children.Len() } -// IsEmpty returns true if node has neither prefixes nor children -func (n *FastNode[V]) IsEmpty() bool { - if n == nil { - return true - } - return n.PfxCount == 0 && n.CldCount == 0 +// InsertPrefix adds or updates a routing entry at the specified index with the given value. +// It returns true if a prefix already existed at that index (indicating an update), +// false if this is a new insertion. +func (n *FastNode[V]) InsertPrefix(idx uint8, val V) (exists bool) { + _, exists = n.Prefixes.InsertAt(idx, val) + return } -// GetChild returns the child node at the specified address and true if it exists. -// If no child exists at addr, returns nil and false. -func (n *FastNode[V]) GetChild(addr uint8) (any, bool) { - if anyPtr := n.Children.Items[addr]; anyPtr != nil { - return *anyPtr, true - } - return nil, false +// GetPrefix retrieves the value associated with the prefix at the given index. +// Returns the value and true if found, or zero value and false if not present. +func (n *FastNode[V]) GetPrefix(idx uint8) (val V, exists bool) { + return n.Prefixes.Get(idx) } -// MustGetChild returns the child node at the specified address. -// Panics if no child exists at addr. This method should only be called -// when the caller has verified the child exists. -func (n *FastNode[V]) MustGetChild(addr uint8) any { - // panics if n.children[addr] is nil - return *n.Children.Items[addr] +// MustGetPrefix retrieves the value at the specified index, panicking if not found. +// This method should only be used when the caller is certain the index exists. +func (n *FastNode[V]) MustGetPrefix(idx uint8) (val V) { + return n.Prefixes.MustGet(idx) } -// AllChildren returns an iterator over all child nodes. -// Each iteration yields the child's address (uint8) and the child node (any). -func (n *FastNode[V]) AllChildren() iter.Seq2[uint8, any] { - return func(yield func(addr uint8, child any) bool) { +// AllIndices returns an iterator over all prefix entries. +// Each iteration yields the prefix index (uint8) and its associated value (V). +func (n *FastNode[V]) AllIndices() iter.Seq2[uint8, V] { + return func(yield func(uint8, V) bool) { var buf [256]uint8 - for _, addr := range n.Children.AsSlice(&buf) { - child := *n.Children.Items[addr] - if !yield(addr, child) { + for i, idx := range n.Prefixes.AsSlice(&buf) { + if !yield(idx, n.Prefixes.Items[i]) { return } } } } -// InsertChild inserts a child node at the specified address. -// Returns true if a child already existed at addr (overwrite case), -// false if this is a new insertion. +// DeletePrefix removes the prefix at the specified index. +// Returns true if the prefix existed, otherwise false. +func (n *FastNode[V]) DeletePrefix(idx uint8) (exists bool) { + _, exists = n.Prefixes.DeleteAt(idx) + return exists +} + +// InsertChild adds a child node at the specified address (0-255). +// The child can be a *FastNode[V], *LeafNode[V], or *FringeNode[V]. +// Returns true if a child already existed at that address. func (n *FastNode[V]) InsertChild(addr uint8, child any) (exists bool) { - if p := n.Children.Items[addr]; p != nil { - // Reuse existing *any slot to cut allocations and GC churn - *p = child // overwrite - return true + var rank0 int + rank0, exists = n.Children.InsertAt(addr, child) + if exists { + // Update only: the value at addr is overwritten in-place by InsertAt. + // childRankCache[addr] is not refreshed because the rank is unchanged + // the position of addr in the sparse slice does not shift on an update. + return } - n.Children.Set(addr) - n.CldCount++ - - // pointer to any reduces per-slot memory for nil entries versus storing `any` directly. - p := new(any) - *p = child - n.Children.Items[addr] = p + // new child inserted? cache the rank value for this addr + //nolint:gosec // G115: integer overflow conversion int -> uint8 + n.childRankCache[addr] = uint8(rank0) - return false + // increment all cached ranks after addr + for i := 255; i > int(addr); i-- { + n.childRankCache[i]++ + } + return } // DeleteChild removes the child node at the specified address. // This operation is idempotent - removing a non-existent child is safe. func (n *FastNode[V]) DeleteChild(addr uint8) (exists bool) { - if n.Children.Items[addr] == nil { - return false - } - n.CldCount-- - - n.Children.Clear(addr) - n.Children.Items[addr] = nil - return true -} + _, exists = n.Children.DeleteAt(addr) -// InsertPrefix adds or updates a routing entry at the specified index with the given value. -// It returns true if a prefix already existed at that index (indicating an update), -// false if this is a new insertion. -func (n *FastNode[V]) InsertPrefix(idx uint8, val V) (exists bool) { - if exists = n.Prefixes.Test(idx); !exists { - n.Prefixes.Set(idx) - n.PfxCount++ + // nothing deleted + if !exists { + return exists } - // insert or update - - // To ensure allot works as intended, every unique prefix in the - // FastNode must point to a distinct value pointer, even for identical values. - // Using new() and assignment guarantees each inserted prefix gets its own address, - valPtr := new(V) - *valPtr = val - - oldValPtr := n.Prefixes.Items[idx] - - // overwrite oldValPtr with valPtr - n.allot(idx, oldValPtr, valPtr) + // decrement all cached ranks after addr + for i := 255; i > int(addr); i-- { + n.childRankCache[i]-- + } return exists } -// GetPrefix returns the value for the given prefix index and true if it exists. -// If no prefix exists at idx, returns the zero value and false. -func (n *FastNode[V]) GetPrefix(idx uint8) (val V, exists bool) { - if exists = n.Prefixes.Test(idx); exists { - val = *n.Prefixes.Items[idx] +// GetChild retrieves the child node at the specified address. +// Returns the child and true if found, or nil and false if not present. +func (n *FastNode[V]) GetChild(addr uint8) (any, bool) { + if n.Children.Test(addr) { + rank0 := n.childRankCache[addr] + return n.Children.Items[rank0], true } - return val, exists + return nil, false } -// MustGetPrefix returns the value for the given prefix index. -// Panics if no prefix exists at idx. This method should only be called -// when the caller has verified the prefix exists. -func (n *FastNode[V]) MustGetPrefix(idx uint8) V { - return *n.Prefixes.Items[idx] +// MustGetChild retrieves the child at addr using the pre-cached rank stored in +// childRankCache[addr] for a direct O(1) array access without an existence check. +// +// The caller must guarantee that addr is present (Children.Test(addr) == true). +// If addr is absent, childRankCache[addr] contains the number of occupied +// addresses less than addr (maintained by InsertChild/DeleteChild), so the +// behaviour is undefined: either a wrong child is returned silently, or the +// call panics with an index-out-of-range error. +func (n *FastNode[V]) MustGetChild(addr uint8) any { + rank0 := n.childRankCache[addr] + return n.Children.Items[rank0] } -// AllIndices returns an iterator over all prefix entries. -// Each iteration yields the prefix index (uint8) and its associated value (V). -func (n *FastNode[V]) AllIndices() iter.Seq2[uint8, V] { - return func(yield func(uint8, V) bool) { +// AllChildren returns an iterator over all child nodes. +// Each iteration yields the child's address (uint8) and the child node (any). +func (n *FastNode[V]) AllChildren() iter.Seq2[uint8, any] { + return func(yield func(addr uint8, child any) bool) { var buf [256]uint8 - for _, idx := range n.Prefixes.AsSlice(&buf) { - val := n.MustGetPrefix(idx) - if !yield(idx, val) { + addrs := n.Children.AsSlice(&buf) + for i, addr := range addrs { + if !yield(addr, n.Children.Items[i]) { return } } } } -// DeletePrefix removes the route at the given index. -// Returns true if the prefix existed, otherwise false. -func (n *FastNode[V]) DeletePrefix(idx uint8) (exists bool) { - if exists = n.Prefixes.Test(idx); !exists { - // Route entry doesn't exist - return exists - } - n.PfxCount-- - - valPtr := n.Prefixes.Items[idx] - parentValPtr := n.Prefixes.Items[idx>>1] - - // delete -> overwrite valPtr with parentValPtr - n.allot(idx, valPtr, parentValPtr) - - n.Prefixes.Clear(idx) - return true -} - -// Contains returns true if the given index has any matching longest-prefix -// in the current node's prefix table. +// Contains returns true if an index (idx) has any matching longest-prefix +// in the current node’s prefix table. // -// This function performs a presence check using the ART algorithm's -// hierarchical prefix structure. It tests whether any ancestor prefix -// exists for the given index by probing the slot at idx (children inherit -// ancestor pointers via allot). -func (n *FastNode[V]) Contains(idx uint8) (ok bool) { - return n.Prefixes.Items[idx] != nil -} - -// Lookup performs a longest-prefix match (LPM) Lookup for the given index -// within the current node's prefix table in O(1). +// This function performs a presence check without retrieving the associated value. +// It is faster than a full lookup, as it only tests for intersection with the +// backtracking bitset for the given index. // -// The function returns the matched value and true if a matching prefix exists; -// otherwise, it returns the zero value and false. The Lookup uses the ART -// algorithm's hierarchical structure to find the most specific -// matching prefix. -func (n *FastNode[V]) Lookup(idx uint8) (val V, ok bool) { - if valPtr := n.Prefixes.Items[idx]; valPtr != nil { - return *valPtr, true - } - return val, ok +// The prefix table is structured as a complete binary tree (CBT), and LPM testing +// is done via a bitset operation that maps the traversal path from the given index +// toward its possible ancestors. +func (n *FastNode[V]) Contains(idx uint8) bool { + return n.Prefixes.Intersects(&lpm.LookupTbl[idx]) } // LookupIdx performs a longest-prefix match (LPM) lookup for the given index (idx) @@ -244,64 +181,35 @@ func (n *FastNode[V]) Lookup(idx uint8) (val V, ok bool) { // The function returns the matched base index, associated value, and true if a // matching prefix exists at this level; otherwise, ok is false. // -// Its semantics are identical to [node.LookupIdx]. +// Internally, the prefix table is organized as a complete binary tree (CBT) indexed +// via the baseIndex function. Unlike the original ART algorithm, this implementation +// does not use an allotment-based approach. Instead, it performs CBT backtracking +// using a bitset-based operation with a precomputed backtracking pattern specific to idx. func (n *FastNode[V]) LookupIdx(idx uint8) (top uint8, val V, ok bool) { // top is the idx of the longest-prefix-match if top, ok = n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx]); ok { - return top, *n.Prefixes.Items[top], true + return top, n.MustGetPrefix(top), true } return top, val, ok } -// allot updates entries whose stored valPtr matches oldValPtr, in the -// subtree rooted at idx. Matching entries have their stored oldValPtr set to -// valPtr, and their value set to val. -// -// allot is the core of the ART algorithm, enabling efficient insertion/deletion -// while preserving very fast lookups. -// -// Example of (uninterrupted) allotment sequence: -// -// addr/bits: 0/5 -> {0/5, 0/6, 4/6, 0/7, 2/7, 4/7, 6/7} -// ╭────╮╭─────────┬────╮ -// idx: 32 -> 32 64 65 128 129 130 131 -// ╰─────────╯╰─────────────┴────╯ -// -// Using an iterative form ensures better inlining opportunities. -func (n *FastNode[V]) allot(idx uint8, oldValPtr, valPtr *V) { - // iteration with stack instead of recursion - stack := make([]uint8, 0, 256) - - // start idx - stack = append(stack, idx) - - for i := 0; i < len(stack); i++ { - idx = stack[i] - - // stop this allot path, idx already points to a more specific route. - if n.Prefixes.Items[idx] != oldValPtr { - continue // take next path from stack - } - - // overwrite - n.Prefixes.Items[idx] = valPtr - - // max idx is 255, so stop the duplication at 128 and above - if idx >= 128 { - continue - } - - // child nodes, it's a complete binary tree - // left: idx*2 - // right: (idx*2)+1 - stack = append(stack, idx<<1, (idx<<1)+1) - } +// Lookup is just a simple wrapper for LookupIdx. +func (n *FastNode[V]) Lookup(idx uint8) (val V, ok bool) { + _, val, ok = n.LookupIdx(idx) + return val, ok } -// CloneFlat returns a shallow copy of the current FastNode[V], -// Its semantics are identical to [bartNode.CloneFlat] but the -// implementation is more complex. -func (n *FastNode[V]) CloneFlat(cloneFn value.CloneFunc[V]) *FastNode[V] { +// CloneFlat returns a shallow copy of the current node, optionally performing deep copies of values. +// +// If cloneFn is nil, the stored values in prefixes are copied directly without modification. +// Otherwise, cloneFn is applied to each stored value for deep cloning. +// Child nodes are cloned shallowly: LeafNode and FringeNode children are cloned via their clone methods, +// but child nodes of type *FastNode[V] (subnodes) are assigned as-is without recursive cloning. +// This method does not recursively clone descendants beyond the immediate children. +// +// Note: The returned node is a new instance with copied slices but only shallow copies of nested nodes, +// except for LeafNode and FringeNode children which are cloned according to cloneFn. +func (n *FastNode[V]) CloneFlat(cloneFn func(V) V) *FastNode[V] { if n == nil { return nil } @@ -311,43 +219,32 @@ func (n *FastNode[V]) CloneFlat(cloneFn value.CloneFunc[V]) *FastNode[V] { return c } - // Copy counters and bitsets (by value). - c.PfxCount = n.PfxCount - c.CldCount = n.CldCount - c.Prefixes.BitSet256 = n.Prefixes.BitSet256 - c.Children.BitSet256 = n.Children.BitSet256 - - // it's a clone of the prefixes ... - // but the allot algorithm makes it more difficult - // see also insertPrefix - for idx, val := range n.AllIndices() { - newValPtr := new(V) - - if cloneFn == nil { - *newValPtr = val // just copy the value - } else { - *newValPtr = cloneFn(val) // clone the value - } + // copy ... + c.Prefixes = *(n.Prefixes.Copy()) - oldValPtr := c.Prefixes.Items[idx] // likely nil initially - c.allot(idx, oldValPtr, newValPtr) + // ... and clone the values + if cloneFn != nil { + for i, v := range c.Prefixes.Items { + c.Prefixes.Items[i] = cloneFn(v) + } } - // flat clone of the children - for addr, kidAny := range n.AllChildren() { - switch kid := kidAny.(type) { - case *FastNode[V]: - // link with new *any pointer - c.Children.Items[addr] = &kidAny + // copy ... + c.childRankCache = n.childRankCache + c.Children = *(n.Children.Copy()) + // Iterate over children to flat clone leaf/fringe nodes; + // for *FastNode[V] children, keep shallow references (no recursive clone) + for i, anyKid := range c.Children.Items { + switch kid := anyKid.(type) { + case *FastNode[V]: + // Shallow copy case *LeafNode[V]: - leafAny := any(kid.CloneLeaf(cloneFn)) - c.Children.Items[addr] = &leafAny - + // Clone leaf nodes, applying cloneFn as needed + c.Children.Items[i] = kid.CloneLeaf(cloneFn) case *FringeNode[V]: - fringeAny := any(kid.CloneFringe(cloneFn)) - c.Children.Items[addr] = &fringeAny - + // Clone fringe nodes, applying cloneFn as needed + c.Children.Items[i] = kid.CloneFringe(cloneFn) default: panic("logic error, wrong node type") } @@ -356,9 +253,21 @@ func (n *FastNode[V]) CloneFlat(cloneFn value.CloneFunc[V]) *FastNode[V] { return c } -// CloneRec performs a recursive deep copy of the FastNode[V] and all its descendants. -// Its semantics are identical to [bartNode.cloneRec]. -func (n *FastNode[V]) CloneRec(cloneFn value.CloneFunc[V]) *FastNode[V] { +// CloneRec performs a recursive deep copy of the node and all its descendants. +// +// If cloneFn is nil, the stored values are copied directly without modification. +// Otherwise cloneFn is applied to each stored value for deep cloning. +// +// This method first creates a shallow clone of the current node using CloneFlat, +// applying cloneFn to values as described there. Then it recursively clones all +// child nodes of type *FastNode[V], performing a full deep clone down the subtree. +// +// Child nodes of type *LeafNode[V] and *FringeNode[V] are already cloned +// by CloneFlat. +// +// Returns a new instance of FastNode[V] which is a complete deep clone of the +// receiver node with all descendants. +func (n *FastNode[V]) CloneRec(cloneFn func(V) V) *FastNode[V] { if n == nil { return nil } @@ -367,11 +276,9 @@ func (n *FastNode[V]) CloneRec(cloneFn value.CloneFunc[V]) *FastNode[V] { c := n.CloneFlat(cloneFn) // Recursively clone all child nodes of type *FastNode[V] - for addr, kidAny := range c.AllChildren() { - switch kid := kidAny.(type) { - case *FastNode[V]: - nodeAny := any(kid.CloneRec(cloneFn)) - c.Children.Items[addr] = &nodeAny + for i, kidAny := range c.Children.Items { + if kid, ok := kidAny.(*FastNode[V]); ok { + c.Children.Items[i] = kid.CloneRec(cloneFn) } } diff --git a/vendor/github.com/gaissmai/bart/internal/nodes/fastmethodsgenerated.go b/vendor/github.com/gaissmai/bart/internal/nodes/fastmethodsgenerated.go index 6b6448a95b..fdd0fdea83 100644 --- a/vendor/github.com/gaissmai/bart/internal/nodes/fastmethodsgenerated.go +++ b/vendor/github.com/gaissmai/bart/internal/nodes/fastmethodsgenerated.go @@ -1,6 +1,6 @@ // Code generated from file "commonmethods_tmpl.go"; DO NOT EDIT. -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package nodes @@ -17,65 +17,76 @@ import ( "github.com/gaissmai/bart/internal/value" ) -// Insert inserts a network prefix and its associated value into the -// trie starting at the specified byte depth. +// Insert adds or updates a network prefix and its associated value in the trie. +// Traversal begins at the specified byte depth. // -// The function traverses the prefix address from the given depth and inserts -// the value either directly into the node's prefix table or as a compressed -// leaf or fringe node. If a conflicting leaf or fringe exists, it creates -// a new intermediate node to accommodate both entries. +// The trie utilizes path compression to conserve memory. A prefix is inserted: +// - Uncompressed into a node's prefix table at depth == strideCount. +// - As a path-compressed FringeNode if it qualifies as fringe [IsFringe]. +// - As a path-compressed LeafNode otherwise. +// +// When a new prefix collides with an existing compressed node (Leaf or Fringe), +// Insert resolves the collision by creating a new intermediate node, pushing +// the existing entry down to the next level, and continuing traversal. // // Parameters: -// - pfx: The network prefix to insert (must be in canonical form) -// - val: The value to associate with the prefix -// - depth: The current depth in the trie (0-based byte index) +// - pfx: The network prefix to insert (must be in canonical/masked form). +// - val: The value to associate with the prefix. +// - depth: The current depth in the trie (0-based byte index). // -// Returns true if a prefix already existed and was updated, false for new insertions. +// Returns true if an existing prefix was updated, false if a new insertion occurred. func (n *FastNode[V]) Insert(pfx netip.Prefix, val V, depth int) (exists bool) { ip := pfx.Addr() // the pfx must be in canonical form + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // find the proper trie node to insert prefix - // start with prefix octet at depth + // Traverse the prefix's octets. Each depth corresponds to an 8-bit stride. + // We descend through the trie until we either reach the final stride (depth == strideCount) + // or find an empty child slot where we can path-compress the remaining strides. for ; depth < len(octets); depth++ { octet := octets[depth] - // last masked octet: insert/override prefix/val into node - if depth == lastOctetPlusOne { - return n.InsertPrefix(art.PfxToIdx(octet, lastBits), val) + // The current depth matches the prefix's stride count, meaning this is the final + // node for this prefix. We insert it directly into this node's prefix table. + if depth == strideCount { + return n.InsertPrefix(art.PfxToIdx(octet, modBits), val) } - // reached end of trie path ... + // No child exists at this octet path. Instead of creating intermediate nodes + // for the remaining strides, we path-compress the rest of the prefix into a single child slot. if !n.Children.Test(octet) { - // insert prefix path compressed as leaf or fringe - if IsFringe(depth, pfx) { + // If the prefix is perfectly aligned with the next stride boundary (e.g., /16 at depth 1), + // it acts as a default route for everything below it. We store it as a FringeNode. + // Otherwise, it has trailing bits or crosses boundaries, so we store it as a LeafNode. + if IsFringe(depth, pfxLen) { return n.InsertChild(octet, NewFringeNode(val)) } return n.InsertChild(octet, NewLeafNode(pfx, val)) } - // ... or descend down the trie + // A child already exists at this octet path. Retrieve it to either continue + // our descent along the strides or resolve a structural collision with a compressed node. kid := n.MustGetChild(octet) - // kid is node or leaf at addr switch kid := kid.(type) { case *FastNode[V]: - n = kid // descend down to next trie level + // Standard intermediate node: descend to the next trie level. + n = kid case *LeafNode[V]: - // reached a path compressed prefix - // override value in slot if prefixes are equal + // Collision with an existing path-compressed LeafNode. + // If it's the exact same prefix, simply update the value. if kid.Prefix == pfx { kid.Value = val - // exists return true } - // create new node - // push the leaf down - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: the paths diverge. + // 1. Create a new intermediate node. + // 2. Push the existing leaf down into this new node. + // 3. Replace the current child slot with the new node. + // 4. Descend into the new node to continue inserting 'pfx'. newNode := new(FastNode[V]) newNode.Insert(kid.Prefix, kid.Value, depth+1) @@ -83,18 +94,17 @@ func (n *FastNode[V]) Insert(pfx netip.Prefix, val V, depth int) (exists bool) { n = newNode case *FringeNode[V]: - // reached a path compressed fringe - // override value in slot if pfx is a fringe - if IsFringe(depth, pfx) { + // Collision with an existing path-compressed FringeNode. + // If the incoming prefix is also a fringe at this depth, update the value. + if IsFringe(depth, pfxLen) { kid.Value = val - // exists return true } - // create new node - // push the fringe down, it becomes a default route (idx=1) - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: + // The existing FringeNode acts as a catch-all (default route) for this sub-trie. + // To allow the incoming prefix to branch further, we expand the FringeNode + // into a full intermediate node and place its value at the default route index (1). newNode := new(FastNode[V]) newNode.InsertPrefix(1, kid.Value) @@ -108,63 +118,74 @@ func (n *FastNode[V]) Insert(pfx netip.Prefix, val V, depth int) (exists bool) { panic("unreachable") } -// InsertPersist is similar to insert but the receiver isn't modified. -// Assumes the caller has pre-cloned the root (COW). It clones the -// internal nodes along the descent path before mutating them. -func (n *FastNode[V]) InsertPersist(cloneFn value.CloneFunc[V], pfx netip.Prefix, val V, depth int) (exists bool) { +// InsertPersist adds or updates a network prefix and its associated value in the trie +// using Copy-On-Write (COW) semantics. Traversal begins at the specified byte depth. +// +// Unlike [Insert], InsertPersist ensures structural integrity of the existing tree +// by cloning internal nodes along the descent path (Copy-On-Write) before mutation. +// +// Parameters: +// - cloneFn: The function used to clone values (V). +// - pfx: The network prefix to insert (must be in canonical/masked form). +// - val: The value to associate with the prefix. +// - depth: The current depth in the trie (0-based byte index). +// +// Returns true if an existing prefix was updated, false if a new insertion occurred. +func (n *FastNode[V]) InsertPersist(cloneFn func(V) V, pfx netip.Prefix, val V, depth int) (exists bool) { ip := pfx.Addr() // the pfx must be in canonical form + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // find the proper trie node to insert prefix - // start with prefix octet at depth + // Traverse the prefix's octets. Each depth corresponds to an 8-bit stride. + // We descend through the trie until we either reach the final stride (depth == strideCount) + // or find an empty child slot where we can path-compress the remaining strides. for ; depth < len(octets); depth++ { octet := octets[depth] - // last masked octet: insert/override prefix/val into node - if depth == lastOctetPlusOne { - return n.InsertPrefix(art.PfxToIdx(octet, lastBits), val) + // The current depth matches the prefix's stride count, meaning this is the final + // node for this prefix. We insert it directly into this node's prefix table. + if depth == strideCount { + return n.InsertPrefix(art.PfxToIdx(octet, modBits), val) } - // reached end of trie path ... + // No child exists at this octet path. Instead of creating intermediate nodes + // for the remaining strides, we path-compress the rest of the prefix into a single child slot. if !n.Children.Test(octet) { - // insert prefix path compressed as leaf or fringe - if IsFringe(depth, pfx) { + // If the prefix is perfectly aligned with the next stride boundary (e.g., /16 at depth 1), + // it acts as a default route for everything below it. We store it as a FringeNode. + // Otherwise, it has trailing bits or crosses boundaries, so we store it as a LeafNode. + if IsFringe(depth, pfxLen) { return n.InsertChild(octet, NewFringeNode(val)) } return n.InsertChild(octet, NewLeafNode(pfx, val)) } - // ... or descend down the trie + // A child already exists at this octet path. Retrieve it to either continue + // our descent along the strides or resolve a structural collision with a compressed node. kid := n.MustGetChild(octet) - // kid is node or leaf at addr switch kid := kid.(type) { case *FastNode[V]: - // clone the traversed path - - // kid points now to cloned kid + // Standard intermediate node: Clone the traversed path to maintain persistence (COW). + // We clone the child node before modifying it, then replace the current child slot. kid = kid.CloneFlat(cloneFn) - - // replace kid with clone n.InsertChild(octet, kid) - n = kid - continue // descend down to next trie level case *LeafNode[V]: - // reached a path compressed prefix - // override value in slot if prefixes are equal + // Collision with an existing path-compressed LeafNode. + // If it's the exact same prefix, simply update the value. if kid.Prefix == pfx { kid.Value = val - // exists return true } - // create new node - // push the leaf down - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: the paths diverge. + // 1. Create a new intermediate node. + // 2. Push the existing leaf down into this new node. + // 3. Replace the current child slot with the new node. + // 4. Descend into the new node to continue inserting 'pfx'. newNode := new(FastNode[V]) newNode.Insert(kid.Prefix, kid.Value, depth+1) @@ -172,18 +193,17 @@ func (n *FastNode[V]) InsertPersist(cloneFn value.CloneFunc[V], pfx netip.Prefix n = newNode case *FringeNode[V]: - // reached a path compressed fringe - // override value in slot if pfx is a fringe - if IsFringe(depth, pfx) { + // Collision with an existing path-compressed FringeNode. + // If the incoming prefix is also a fringe at this depth, update the value. + if IsFringe(depth, pfxLen) { kid.Value = val - // exists return true } - // create new node - // push the fringe down, it becomes a default route (idx=1) - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: + // The existing FringeNode acts as a catch-all (default route) for this sub-trie. + // To allow the incoming prefix to branch further, we expand the FringeNode + // into a full intermediate node and place its value at the default route index (1). newNode := new(FastNode[V]) newNode.InsertPrefix(1, kid.Value) @@ -193,162 +213,170 @@ func (n *FastNode[V]) InsertPersist(cloneFn value.CloneFunc[V], pfx netip.Prefix default: panic("logic error, wrong node type") } - } - panic("unreachable") } -// PurgeAndCompress performs bottom-up compression of the trie. +// PurgeAndCompress performs bottom-up trie maintenance to restore path compression +// after a deletion. It unwinds the provided stack of parent nodes, identifying +// nodes that have become sparse (i.e., containing only a single prefix or a single +// child node) and prunes them by promoting the underlying entries to the parent level. // -// The function unwinds the provided stack of parent nodes, checking each level -// for compression opportunities based on child and prefix count. -// It may convert: -// - Nodes with a single prefix into leaf one level above. -// - Nodes with a single leaf or fringe into leaf one level above. +// This ensures the trie remains memory-efficient by collapsing redundant intermediate +// nodes back into path-compressed LeafNodes or FringeNodes whenever possible. // // Parameters: -// - stack: Array of parent nodes to process during unwinding -// - octets: The path of octets taken to reach the current position -// - is4: True for IPv4 processing, false for IPv6 +// - stack: Array of parent nodes to process during bottom-up unwinding. +// - octets: The full path of octets leading to the current node. +// - is4: True for IPv4 processing, false for IPv6. func (n *FastNode[V]) PurgeAndCompress(stack []*FastNode[V], octets []uint8, is4 bool) { - // unwind the stack + // Iterate backwards through the ancestor stack to prune nodes from the bottom up. for depth := len(stack) - 1; depth >= 0; depth-- { parent := stack[depth] octet := octets[depth] + // Check if the current node is redundant. + // A node may be redundant if it contains exactly one entry (either a prefix or + // a compressed child node). pfxCount := n.PrefixCount() childCount := n.ChildCount() + // If it contains more than one entry, it is always structurally significant and cannot be pruned. if pfxCount+childCount > 1 { return } switch { case childCount == 1: - singleAddr, _ := n.Children.FirstSet() // single addr must be first bit set + // The node has exactly one child. We determine if it is a path node, + // a compressed LeafNode, or a compressed FringeNode. + singleAddr, _ := n.Children.FirstSet() anyKid := n.MustGetChild(singleAddr) switch kid := anyKid.(type) { case *FastNode[V]: - // fast exit, we are at an intermediate path node - // no further delete/compress upwards the stack is possible + // The child is an intermediate path node; the tree structure is required + // at this level. Compression cannot proceed further up. return case *LeafNode[V]: - // just one leaf, delete this node and reinsert the leaf above + // The child is a compressed LeafNode. Prune the current node + // and re-insert the leaf into the parent to elevate it. parent.DeleteChild(octet) - - // ... (re)insert the leaf at parents depth parent.Insert(kid.Prefix, kid.Value, depth) case *FringeNode[V]: - // just one fringe, delete this node and reinsert the fringe as leaf above + // The child is a compressed FringeNode. Prune the current node + // and re-insert the fringe as a leaf into the parent. parent.DeleteChild(octet) - // rebuild the prefix with octets, depth, ip version and addr - // depth is the parent's depth, so add +1 here for the kid - // lastOctet in cidrForFringe is the only addr (singleAddr) + // Reconstruct the full prefix for the fringe, as path compression + // requires the entire CIDR path, not just the remainder. + // depth is the parent's depth, so we offset by 1 for the kid's position. fringePfx := CidrForFringe(octets, depth+1, is4, singleAddr) - // ... (re)reinsert prefix/value at parents depth parent.Insert(fringePfx, kid.Value, depth) } case pfxCount == 1: - // just one prefix, delete this node and reinsert the idx as leaf above + // The node has exactly one prefix. Prune the node and elevate the + // prefix to the parent level as a leaf/fringe. parent.DeleteChild(octet) - // get prefix back from idx ... - idx, _ := n.Prefixes.FirstSet() // single idx must be first bit set + // Retrieve the single prefix stored in this node. + idx, _ := n.Prefixes.FirstSet() val := n.MustGetPrefix(idx) - // ... and octet path + // Reconstruct the prefix from the path for re-insertion. path := StridePath{} copy(path[:], octets) - - // depth is the parent's depth, so add +1 here for the kid pfx := CidrFromPath(path, depth+1, is4, idx) - // ... (re)insert prefix/value at parents depth parent.Insert(pfx, val, depth) + default: + panic("unreachable") } - // climb up the stack + // Move up to the next parent in the stack to continue pruning. n = parent } } -// Delete deletes the prefix and returns true if the prefix existed, -// or false otherwise. The prefix must be in canonical form. +// Delete removes the prefix from the trie rooted at n and returns true if the +// prefix existed, false if it was not found. The prefix must be in canonical +// (masked) form. +// +// The trie uses path compression, so a prefix may be stored in one of three ways: +// - In the current node's prefix table when the prefix length aligns exactly +// with the stride boundary at this depth (depth == strideCount). +// - As a path-compressed FringeNode in a child slot for stride-aligned prefixes +// (e.g. /8, /16, /24); occurs at depth == strideCount-1. +// - As a path-compressed LeafNode in a child slot for prefixes otherwise. +// +// After a successful deletion, PurgeAndCompress walks the ancestor stack to prune +// now-empty nodes and restore path compression upward. func (n *FastNode[V]) Delete(pfx netip.Prefix) (exists bool) { - // invariant, prefix must be masked - - // values derived from pfx - ip := pfx.Addr() + ip := pfx.Addr() // pfx must be in canonical (masked) form + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // record the nodes on the path to the deleted node, needed to purge - // and/or path compress nodes after the deletion of a prefix + // Record ancestor nodes as we descend; PurgeAndCompress uses this stack to + // walk back up and clean up empty or re-compressible nodes after deletion. stack := [MaxTreeDepth]*FastNode[V]{} - // find the trie node for depth, octet := range octets { - depth = depth & DepthMask // BCE, Delete must be fast + depth &= DepthMask // BCE hint; keep Delete on the fast path - // push current node on stack for path recording - stack[depth] = n + stack[depth] = n // record current node before descending - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - // try to delete prefix in trie node - if exists = n.DeletePrefix(art.PfxToIdx(octet, lastBits)); !exists { + // At the stride boundary, the prefix is stored directly in this node's + // prefix table. + if depth == strideCount { + if exists = n.DeletePrefix(art.PfxToIdx(octet, modBits)); !exists { return false } - // remove now-empty nodes and re-path-compress upwards + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) return true } + // no child node exists at this octet; the prefix is not in the trie if !n.Children.Test(octet) { return false } + + // A child node exists at this octet path, retrieve it. kid := n.MustGetChild(octet) - // kid is node or leaf or fringe at octet switch kid := kid.(type) { case *FastNode[V]: - n = kid // descend down to next trie level + n = kid // descend to the next trie level case *FringeNode[V]: - // if pfx is no fringe at this depth, fast exit - if !IsFringe(depth, pfx) { + // A FringeNode holds a single stride-aligned prefix (/8, /16, ...). + // If pfx does not qualify as a fringe at this depth, it cannot be here. + if !IsFringe(depth, pfxLen) { return false } - // pfx is fringe at depth, delete fringe n.DeleteChild(octet) - // remove now-empty nodes and re-path-compress upwards + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true case *LeafNode[V]: - // Attention: pfx must be masked to be comparable! + // A LeafNode holds exactly one path-compressed prefix. + // Compare using the canonical (masked) form for an exact match. if kid.Prefix != pfx { return false } - // prefix is equal leaf, delete leaf n.DeleteChild(octet) - // remove now-empty nodes and re-path-compress upwards + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true default: @@ -359,142 +387,150 @@ func (n *FastNode[V]) Delete(pfx netip.Prefix) (exists bool) { panic("unreachable") } -// DeletePersist is similar to delete but does not mutate the original trie. -// Assumes the caller has pre-cloned the root (COW). It clones the -// internal nodes along the descent path before mutating them. -func (n *FastNode[V]) DeletePersist(cloneFn value.CloneFunc[V], pfx netip.Prefix) (exists bool) { - ip := pfx.Addr() // the pfx must be in canonical form +// DeletePersist removes the prefix from the trie rooted at n using Copy-On-Write (COW) semantics. +// It returns true if the prefix existed, false if it was not found. The prefix must be in +// canonical (masked) form. +// +// Like [Delete], this method uses path compression. However, DeletePersist ensures +// the structural integrity of the existing tree by cloning internal nodes along the +// descent path (COW) before mutation. +// +// After a successful deletion, PurgeAndCompress walks the ancestor stack to prune +// now-empty nodes and restore path compression upward. +func (n *FastNode[V]) DeletePersist(cloneFn func(V) V, pfx netip.Prefix) (exists bool) { + ip := pfx.Addr() // pfx must be in canonical (masked) form + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // Stack to keep track of cloned nodes along the path, - // needed for purge and path compression after delete. + // Record ancestor nodes as we descend; PurgeAndCompress uses this stack to + // walk back up and clean up empty or re-compressible nodes after deletion. + // Since this is a COW operation, we store the cloned nodes here. stack := [MaxTreeDepth]*FastNode[V]{} - // Traverse the trie to locate the prefix to delete. for depth, octet := range octets { - // Keep track of the cloned node at current depth. - stack[depth] = n + depth &= DepthMask // BCE hint; keep DeletePersist on the fast path + stack[depth] = n // record current node before descending - if depth == lastOctetPlusOne { - // Attempt to delete the prefix from the node's prefixes. - if exists = n.DeletePrefix(art.PfxToIdx(octet, lastBits)); !exists { - // Prefix not found, nothing deleted. + // At the stride boundary, the prefix is stored directly in this node's + // prefix table. + if depth == strideCount { + if exists = n.DeletePrefix(art.PfxToIdx(octet, modBits)); !exists { return false } - // After deletion, purge nodes and compress the path if needed. + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true } - addr := octet - - // If child node doesn't exist, no prefix to delete. - if !n.Children.Test(addr) { + // no child node exists at this octet; the prefix is not in the trie + if !n.Children.Test(octet) { return false } - // Fetch child node at current address. - kid := n.MustGetChild(addr) + // A child node exists at this octet path, retrieve it to either continue + // our descent or perform a persistent delete on a compressed node. + kid := n.MustGetChild(octet) switch kid := kid.(type) { case *FastNode[V]: - // Clone the internal node for copy-on-write. + // Standard intermediate node: Clone the traversed path to maintain + // persistence (COW). We clone the child node before modifying it, + // then replace the current child slot. kid = kid.CloneFlat(cloneFn) - - // Replace child with cloned node. - n.InsertChild(addr, kid) - - // Descend to cloned child node. + n.InsertChild(octet, kid) n = kid continue case *FringeNode[V]: - // Reached a path compressed fringe. - if !IsFringe(depth, pfx) { - // Prefix to delete not found here. + // A FringeNode holds a single stride-aligned prefix (/8, /16, ...). + // If pfx does not qualify as a fringe at this depth, it cannot be here. + if !IsFringe(depth, pfxLen) { return false } - // Delete the fringe node. - n.DeleteChild(addr) + n.DeleteChild(octet) - // Purge and compress affected path. + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true case *LeafNode[V]: - // Reached a path compressed leaf node. + // A LeafNode holds exactly one path-compressed prefix. + // Compare using the canonical (masked) form for an exact match. if kid.Prefix != pfx { - // Leaf prefix does not match; nothing to delete. return false } - // Delete leaf node. - n.DeleteChild(addr) + n.DeleteChild(octet) - // Purge and compress affected path. + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true default: - // Unexpected node type indicates a logic error. panic("logic error, wrong node type") } } - // Should never happen: traversal always returns or panics inside loop. panic("unreachable") } // Get retrieves the value associated with the given network prefix. -// Returns the stored value and true if the prefix exists in this node, -// zero value and false if the prefix is not found. +// Traversal descends through the trie using the prefix's octets. +// +// The lookup handles path compression transparently: +// - If the path matches an internal node at the target stride, the value is retrieved +// from the node's prefix table. +// - If the path leads to a compressed LeafNode or FringeNode, the function verifies +// the prefix match before returning the value. // // Parameters: -// - pfx: The network prefix to look up (must be in canonical form) +// - pfx: The network prefix to look up (must be in canonical form). // // Returns: -// - val: The value associated with the prefix (zero value if not found) -// - exists: True if the prefix was found, false otherwise +// - val: The value associated with the prefix (zero value if not found). +// - exists: True if the prefix was found, false otherwise. func (n *FastNode[V]) Get(pfx netip.Prefix) (val V, exists bool) { - // invariant, prefix must be masked - - // values derived from pfx + // The prefix must be provided in canonical (masked) form for correct trie traversal. ip := pfx.Addr() + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // find the trie node + // Traverse the trie octet by octet based on the prefix path. for depth, octet := range octets { - if depth == lastOctetPlusOne { - return n.GetPrefix(art.PfxToIdx(octet, lastBits)) + // At the target stride boundary, the prefix is expected in this node's + // prefix table. + if depth == strideCount { + return n.GetPrefix(art.PfxToIdx(octet, modBits)) } + // If no child exists at this path, the prefix is not in the trie. kidAny, ok := n.GetChild(octet) if !ok { return val, false } - // kid is node or leaf or fringe at octet + // Identify the node type at this path segment. switch kid := kidAny.(type) { case *FastNode[V]: - n = kid // descend down to next trie level + // Standard intermediate node: descend to the next level. + n = kid case *FringeNode[V]: - // reached a path compressed fringe, stop traversing - if IsFringe(depth, pfx) { + // Reached a path-compressed FringeNode. + // Verify if the prefix qualifies as a fringe at this depth to return a match. + if IsFringe(depth, pfxLen) { return kid.Value, true } return val, false case *LeafNode[V]: - // reached a path compressed prefix, stop traversing + // Reached a path-compressed LeafNode. + // Check if the stored prefix matches the lookup prefix exactly. if kid.Prefix == pfx { return kid.Value, true } @@ -512,7 +548,7 @@ func (n *FastNode[V]) Get(pfx netip.Prefix) (val V, exists bool) { // The callback receives the current value (if found) and existence flag, and returns // a new value and deletion flag. // -// modify returns the size delta (-1, 0, +1). +// Modify returns the size delta (-1, 0, +1). // This method handles path traversal, node creation for new paths, and automatic // purge/compress operations after deletions. // @@ -526,9 +562,10 @@ func (n *FastNode[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V, var zero V ip := pfx.Addr() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) // record the nodes on the path to the deleted node, needed to purge // and/or path compress nodes after the deletion of a prefix @@ -536,16 +573,15 @@ func (n *FastNode[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V, // find the proper trie node to update prefix for depth, octet := range octets { - depth = depth & DepthMask // BCE + depth &= DepthMask // BCE // push current node on stack for path recording stack[depth] = n - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx := art.PfxToIdx(octet, lastBits) + // The current depth matches the prefix's stride count, meaning this is the final + // stride for this prefix. Insert or update it directly in this node's prefix table. + if depth == strideCount { + idx := art.PfxToIdx(octet, modBits) oldVal, existed := n.GetPrefix(idx) newVal, del := cb(oldVal, existed) @@ -585,7 +621,7 @@ func (n *FastNode[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V, } // insert - if IsFringe(depth, pfx) { + if IsFringe(depth, pfxLen) { n.InsertChild(octet, NewFringeNode(newVal)) } else { n.InsertChild(octet, NewLeafNode(pfx, newVal)) @@ -644,7 +680,7 @@ func (n *FastNode[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V, case *FringeNode[V]: // update existing value if prefix is fringe - if IsFringe(depth, pfx) { + if IsFringe(depth, pfxLen) { newVal, del := cb(kid.Value, true) if !del { kid.Value = newVal @@ -774,8 +810,8 @@ func (n *FastNode[V]) EqualRec(o *FastNode[V]) bool { // // It returns immediately if n is nil or empty. For each visited internal node // it calls dump to write the node's representation, then iterates its child -// addresses and recurses into children that implement nodeDumper[V] (internal -// subnodes). The path slice and depth together represent the byte-wise path +// addresses and recurses into children of type *FastNode[V] (internal subnodes). +// The path slice and depth together represent the byte-wise path // from the root to the current node; depth is incremented for each recursion. // The is4 flag controls IPv4/IPv6 formatting used by dump. func (n *FastNode[V]) DumpRec(w io.Writer, path StridePath, depth int, is4 bool) { @@ -803,8 +839,8 @@ func (n *FastNode[V]) dump(w io.Writer, path StridePath, depth int, is4 bool) { bits := depth * strideLen indent := strings.Repeat(".", depth) - // printing values if V is not zero-sized - printValues := !value.IsZST[V]() + // printing values if V is not the empty struct{} + printValues := !value.IsEmptyStruct[V]() // node type with depth and octet path and bits. fmt.Fprintf(w, "\n%s[%s] depth: %d path: [%s] / %d\n", @@ -967,7 +1003,7 @@ func (n *FastNode[V]) DumpString(octets []uint8, depth int, is4 bool) string { // - stopNode: has children but no subnodes (nodes == 0) // - halfNode: contains at least one leaf or fringe and also has subnodes, but // no prefixes -// - fullNode: has prefixes or leaves/fringes and also has subnodes +// - fullNode: has prefixes and also has subnodes // - pathNode: has subnodes only (no prefixes, leaves, or fringes) // // The order of these checks is significant to ensure the correct classification. @@ -1024,7 +1060,7 @@ func (n *FastNode[V]) Stats() (s StatsT) { // It walks the node tree recursively and sums immediate counts (prefixes and // child slots) plus the number of nodes, leaves, and fringe nodes in the // subtree. If n is nil or empty, a zeroed stats is returned. The returned -// stats.nodes includes the current node. The function will panic if a child +// SubNodes count includes the current node. The function will panic if a child // has an unexpected concrete type. func (n *FastNode[V]) StatsRec() (s StatsT) { if n == nil || n.IsEmpty() { @@ -1080,8 +1116,8 @@ func (n *FastNode[V]) FprintRec(w io.Writer, parent TrieItem[V], pad string) err return CmpPrefix(a.Cidr, b.Cidr) }) - // printing values if V is not zero-sized - printValues := !value.IsZST[V]() + // printing values if V is not the empty struct{} + printValues := !value.IsEmptyStruct[V]() // for all direct item under this node ... for i, item := range directItems { @@ -1178,7 +1214,7 @@ func (n *FastNode[V]) DirectItemsRec(parentIdx uint8, path StridePath, depth int path[depth] = addr directItems = append(directItems, kid.DirectItemsRec(0, path, depth+1, is4)...) - case *LeafNode[V]: // path-compressed child, stop's recursion for this child + case *LeafNode[V]: // path-compressed child, stops recursion for this child item := TrieItem[V]{ Node: nil, Is4: is4, @@ -1187,7 +1223,7 @@ func (n *FastNode[V]) DirectItemsRec(parentIdx uint8, path StridePath, depth int } directItems = append(directItems, item) - case *FringeNode[V]: // path-compressed fringe, stop's recursion for this child + case *FringeNode[V]: // path-compressed fringe, stops recursion for this child item := TrieItem[V]{ Node: nil, Is4: is4, @@ -1221,9 +1257,9 @@ func (n *FastNode[V]) DirectItemsRec(parentIdx uint8, path StridePath, depth int // The merge operation is destructive on the receiver n, but leaves the source node o unchanged. // // Returns the number of duplicate prefixes that were overwritten during merging. -func (n *FastNode[V]) UnionRec(cloneFn value.CloneFunc[V], o *FastNode[V], depth int) (duplicates int) { +func (n *FastNode[V]) UnionRec(cloneFn func(V) V, o *FastNode[V], depth int) (duplicates int) { if cloneFn == nil { - cloneFn = value.CopyVal + cloneFn = func(v V) V { return v } } buf := [256]uint8{} @@ -1254,9 +1290,9 @@ func (n *FastNode[V]) UnionRec(cloneFn value.CloneFunc[V], o *FastNode[V], depth } // UnionRecPersist is similar to unionRec but performs an immutable union of nodes. -func (n *FastNode[V]) UnionRecPersist(cloneFn value.CloneFunc[V], o *FastNode[V], depth int) (duplicates int) { +func (n *FastNode[V]) UnionRecPersist(cloneFn func(V) V, o *FastNode[V], depth int) (duplicates int) { if cloneFn == nil { - cloneFn = value.CopyVal + cloneFn = func(v V) V { return v } } buf := [256]uint8{} @@ -1305,7 +1341,7 @@ func (n *FastNode[V]) UnionRecPersist(cloneFn value.CloneFunc[V], o *FastNode[V] // fringe, node <-- insert new node, push this fringe down, union rec-descent // fringe, leaf <-- insert new node, push this fringe down, insert other leaf at depth+1 // fringe, fringe <-- just overwrite value -func (n *FastNode[V]) handleMatrix(cloneFn value.CloneFunc[V], thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { +func (n *FastNode[V]) handleMatrix(cloneFn func(V) V, thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { // Do ALL type assertions upfront - reduces line noise var ( thisNode, thisIsNode = thisChild.(*FastNode[V]) @@ -1422,7 +1458,7 @@ func (n *FastNode[V]) handleMatrix(cloneFn value.CloneFunc[V], thisExists bool, // fringe, node <-- insert new node, push this fringe down, union rec-descent // fringe, leaf <-- insert new node, push this fringe down, insert other leaf at depth+1 // fringe, fringe <-- just overwrite value -func (n *FastNode[V]) handleMatrixPersist(cloneFn value.CloneFunc[V], thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { +func (n *FastNode[V]) handleMatrixPersist(cloneFn func(V) V, thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { // Do ALL type assertions upfront - reduces line noise var ( thisNode, thisIsNode = thisChild.(*FastNode[V]) @@ -1654,9 +1690,7 @@ func (n *FastNode[V]) AllRecSorted(path StridePath, depth int, is4 bool, yield f } case *FringeNode[V]: fringePfx := CidrForFringe(path[:], depth, is4, childAddr) - // callback for this fringe if !yield(fringePfx, kid.Value) { - // early exit return false } @@ -1691,9 +1725,7 @@ func (n *FastNode[V]) AllRecSorted(path StridePath, depth int, is4 bool, yield f } case *FringeNode[V]: fringePfx := CidrForFringe(path[:], depth, is4, addr) - // callback for this fringe if !yield(fringePfx, kid.Value) { - // early exit return false } @@ -1879,9 +1911,10 @@ func (n *FastNode[V]) EachSubnet(octets []byte, depth int, is4 bool, pfxIdx uint // the iteration early. func (n *FastNode[V]) Supernets(pfx netip.Prefix, yield func(netip.Prefix, V) bool) { ip := pfx.Addr() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) // stack of the traversed nodes for reverse ordering of supernets stack := [MaxTreeDepth]*FastNode[V]{} @@ -1894,7 +1927,7 @@ func (n *FastNode[V]) Supernets(pfx netip.Prefix, yield func(netip.Prefix, V) bo LOOP: for depth, octet = range octets { // stepped one past the last stride of interest; back up to last and exit - if depth > lastOctetPlusOne { + if depth > strideCount { depth-- break } @@ -1955,11 +1988,10 @@ LOOP: // all others are just host routes var idx uint8 octet = octets[depth] - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx = art.PfxToIdx(octet, lastBits) + // Last “octet” from prefix + // Note: For /32 and /128, depth never reaches strideCount (4/16), + if depth == strideCount { + idx = art.PfxToIdx(octet, modBits) } else { idx = art.OctetToIdx(octet) } @@ -1996,17 +2028,18 @@ LOOP: func (n *FastNode[V]) Subnets(pfx netip.Prefix, yield func(netip.Prefix, V) bool) { // values derived from pfx ip := pfx.Addr() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) // find the trie node for depth, octet := range octets { - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), + // Last “octet” from prefix + // Note: For /32 and /128, depth never reaches strideCount (4/16), // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx := art.PfxToIdx(octet, lastBits) + if depth == strideCount { + idx := art.PfxToIdx(octet, modBits) n.EachSubnet(octets, depth, is4, idx, yield) return } @@ -2124,7 +2157,7 @@ func (n *FastNode[V]) Overlaps(o *FastNode[V], depth int) bool { // OverlapsRoutes compares the prefix sets of two nodes (n and o). // // It first checks for direct bitset intersection (identical indices), -// then walks both prefix sets using lpmTest to detect if any +// then walks both prefix sets using the Contains method to detect if any // of the n-prefixes is contained in o, or vice versa. func (n *FastNode[V]) OverlapsRoutes(o *FastNode[V]) bool { // some prefixes are identical, trivial overlap @@ -2267,19 +2300,20 @@ func (n *FastNode[V]) OverlapsSameChildren(o *FastNode[V], depth int) bool { // trie traversal across varying prefix lengths and compression levels. func (n *FastNode[V]) OverlapsPrefixAtDepth(pfx netip.Prefix, depth int) bool { ip := pfx.Addr() + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) for ; depth < len(octets); depth++ { - if depth > lastOctetPlusOne { + if depth > strideCount { break } octet := octets[depth] // full octet path in node trie, check overlap with last prefix octet - if depth == lastOctetPlusOne { - return n.OverlapsIdx(art.PfxToIdx(octet, lastBits)) + if depth == strideCount { + return n.OverlapsIdx(art.PfxToIdx(octet, modBits)) } // test if any route overlaps prefix´ so far diff --git a/vendor/github.com/gaissmai/bart/internal/nodes/lite.go b/vendor/github.com/gaissmai/bart/internal/nodes/lite.go index f5fcbdcaf7..df889e7a3e 100644 --- a/vendor/github.com/gaissmai/bart/internal/nodes/lite.go +++ b/vendor/github.com/gaissmai/bart/internal/nodes/lite.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package nodes @@ -9,29 +9,17 @@ import ( "github.com/gaissmai/bart/internal/bitset" "github.com/gaissmai/bart/internal/lpm" "github.com/gaissmai/bart/internal/sparse" - "github.com/gaissmai/bart/internal/value" ) -// LiteNode is a trie level node in the multibit routing table. -// -// Each LiteNode contains two conceptually different bitset-based arrays: -// - Prefixes: a BitSet256 tracking which prefix indices are occupied, -// with Count tracking the number of active entries. -// - Children: holding subtries or path-compressed leaves/fringes with -// a branching factor of 256 (8 bits per stride). -// -// Entries in Children may be: -// - *LiteNode[V] -> internal child node for further traversal -// - *LeafNode[V] -> path-comp. node (depth < maxDepth - 1) -// - *FringeNode[V] -> path-comp. node (depth == maxDepth - 1, stride-aligned) -// -// Note: The type parameter V is a phantom type used solely for common -// method generation; LiteNode stores no values. +// LiteNode is a space-optimized version of [BartNode] that tracks prefix existence +// without storing associated values. type LiteNode[V any] struct { Children sparse.Array256[any] Prefixes struct { - // no values + // BitSet256 tracks the presence of prefixes at this level. bitset.BitSet256 + // Count maintains the current number of set bits, updated on modification + // to avoid expensive population counting. Count uint16 } } @@ -57,7 +45,7 @@ func (n *LiteNode[V]) ChildCount() int { } // InsertPrefix adds a routing entry at the specified index. -// It returns true if a prefix already existed at that index +// It returns true if a prefix already existed at that index, // false if this is a new insertion. func (n *LiteNode[V]) InsertPrefix(idx uint8, _ V) (exists bool) { if exists = n.Prefixes.Test(idx); exists { @@ -68,13 +56,14 @@ func (n *LiteNode[V]) InsertPrefix(idx uint8, _ V) (exists bool) { return exists } -// prefix is set at the given index. func (n *LiteNode[V]) GetPrefix(idx uint8) (_ V, exists bool) { + // no docstring by intention exists = n.Prefixes.Test(idx) return } func (n *LiteNode[V]) MustGetPrefix(idx uint8) (_ V) { + // no docstring by intention return } @@ -107,7 +96,8 @@ func (n *LiteNode[V]) DeletePrefix(idx uint8) (exists bool) { // The child can be a *LiteNode[V], *LeafNode, or *FringeNode. // Returns true if a child already existed at that address. func (n *LiteNode[V]) InsertChild(addr uint8, child any) (exists bool) { - return n.Children.InsertAt(addr, child) + _, exists = n.Children.InsertAt(addr, child) + return } // GetChild retrieves the child node at the specified address. @@ -129,8 +119,7 @@ func (n *LiteNode[V]) AllChildren() iter.Seq2[uint8, any] { var buf [256]uint8 addrs := n.Children.AsSlice(&buf) for i, addr := range addrs { - child := n.Children.Items[i] - if !yield(addr, child) { + if !yield(addr, n.Children.Items[i]) { return } } @@ -171,7 +160,7 @@ func (n *LiteNode[V]) LookupIdx(idx uint8) (top uint8, _ V, ok bool) { return } -// Lookup is just a simple wrapper for lookupIdx. +// Lookup is just a simple wrapper for LookupIdx. func (n *LiteNode[V]) Lookup(idx uint8) (_ V, ok bool) { _, _, ok = n.LookupIdx(idx) return @@ -180,7 +169,7 @@ func (n *LiteNode[V]) Lookup(idx uint8) (_ V, ok bool) { // CloneFlat returns a shallow copy of the current node. // // CloneFn is only used for interface satisfaction. -func (n *LiteNode[V]) CloneFlat(_ value.CloneFunc[V]) *LiteNode[V] { +func (n *LiteNode[V]) CloneFlat(_ func(V) V) *LiteNode[V] { if n == nil { return nil } @@ -213,7 +202,7 @@ func (n *LiteNode[V]) CloneFlat(_ value.CloneFunc[V]) *LiteNode[V] { // // Returns a new instance of LiteNode[V] which is a complete deep clone of the // receiver node with all descendants. -func (n *LiteNode[V]) CloneRec(_ value.CloneFunc[V]) *LiteNode[V] { +func (n *LiteNode[V]) CloneRec(_ func(V) V) *LiteNode[V] { if n == nil { return nil } diff --git a/vendor/github.com/gaissmai/bart/internal/nodes/litemethodsgenerated.go b/vendor/github.com/gaissmai/bart/internal/nodes/litemethodsgenerated.go index e1af8585de..c615cd02b2 100644 --- a/vendor/github.com/gaissmai/bart/internal/nodes/litemethodsgenerated.go +++ b/vendor/github.com/gaissmai/bart/internal/nodes/litemethodsgenerated.go @@ -1,6 +1,6 @@ // Code generated from file "commonmethods_tmpl.go"; DO NOT EDIT. -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package nodes @@ -17,65 +17,76 @@ import ( "github.com/gaissmai/bart/internal/value" ) -// Insert inserts a network prefix and its associated value into the -// trie starting at the specified byte depth. +// Insert adds or updates a network prefix and its associated value in the trie. +// Traversal begins at the specified byte depth. // -// The function traverses the prefix address from the given depth and inserts -// the value either directly into the node's prefix table or as a compressed -// leaf or fringe node. If a conflicting leaf or fringe exists, it creates -// a new intermediate node to accommodate both entries. +// The trie utilizes path compression to conserve memory. A prefix is inserted: +// - Uncompressed into a node's prefix table at depth == strideCount. +// - As a path-compressed FringeNode if it qualifies as fringe [IsFringe]. +// - As a path-compressed LeafNode otherwise. +// +// When a new prefix collides with an existing compressed node (Leaf or Fringe), +// Insert resolves the collision by creating a new intermediate node, pushing +// the existing entry down to the next level, and continuing traversal. // // Parameters: -// - pfx: The network prefix to insert (must be in canonical form) -// - val: The value to associate with the prefix -// - depth: The current depth in the trie (0-based byte index) +// - pfx: The network prefix to insert (must be in canonical/masked form). +// - val: The value to associate with the prefix. +// - depth: The current depth in the trie (0-based byte index). // -// Returns true if a prefix already existed and was updated, false for new insertions. +// Returns true if an existing prefix was updated, false if a new insertion occurred. func (n *LiteNode[V]) Insert(pfx netip.Prefix, val V, depth int) (exists bool) { ip := pfx.Addr() // the pfx must be in canonical form + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // find the proper trie node to insert prefix - // start with prefix octet at depth + // Traverse the prefix's octets. Each depth corresponds to an 8-bit stride. + // We descend through the trie until we either reach the final stride (depth == strideCount) + // or find an empty child slot where we can path-compress the remaining strides. for ; depth < len(octets); depth++ { octet := octets[depth] - // last masked octet: insert/override prefix/val into node - if depth == lastOctetPlusOne { - return n.InsertPrefix(art.PfxToIdx(octet, lastBits), val) + // The current depth matches the prefix's stride count, meaning this is the final + // node for this prefix. We insert it directly into this node's prefix table. + if depth == strideCount { + return n.InsertPrefix(art.PfxToIdx(octet, modBits), val) } - // reached end of trie path ... + // No child exists at this octet path. Instead of creating intermediate nodes + // for the remaining strides, we path-compress the rest of the prefix into a single child slot. if !n.Children.Test(octet) { - // insert prefix path compressed as leaf or fringe - if IsFringe(depth, pfx) { + // If the prefix is perfectly aligned with the next stride boundary (e.g., /16 at depth 1), + // it acts as a default route for everything below it. We store it as a FringeNode. + // Otherwise, it has trailing bits or crosses boundaries, so we store it as a LeafNode. + if IsFringe(depth, pfxLen) { return n.InsertChild(octet, NewFringeNode(val)) } return n.InsertChild(octet, NewLeafNode(pfx, val)) } - // ... or descend down the trie + // A child already exists at this octet path. Retrieve it to either continue + // our descent along the strides or resolve a structural collision with a compressed node. kid := n.MustGetChild(octet) - // kid is node or leaf at addr switch kid := kid.(type) { case *LiteNode[V]: - n = kid // descend down to next trie level + // Standard intermediate node: descend to the next trie level. + n = kid case *LeafNode[V]: - // reached a path compressed prefix - // override value in slot if prefixes are equal + // Collision with an existing path-compressed LeafNode. + // If it's the exact same prefix, simply update the value. if kid.Prefix == pfx { kid.Value = val - // exists return true } - // create new node - // push the leaf down - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: the paths diverge. + // 1. Create a new intermediate node. + // 2. Push the existing leaf down into this new node. + // 3. Replace the current child slot with the new node. + // 4. Descend into the new node to continue inserting 'pfx'. newNode := new(LiteNode[V]) newNode.Insert(kid.Prefix, kid.Value, depth+1) @@ -83,18 +94,17 @@ func (n *LiteNode[V]) Insert(pfx netip.Prefix, val V, depth int) (exists bool) { n = newNode case *FringeNode[V]: - // reached a path compressed fringe - // override value in slot if pfx is a fringe - if IsFringe(depth, pfx) { + // Collision with an existing path-compressed FringeNode. + // If the incoming prefix is also a fringe at this depth, update the value. + if IsFringe(depth, pfxLen) { kid.Value = val - // exists return true } - // create new node - // push the fringe down, it becomes a default route (idx=1) - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: + // The existing FringeNode acts as a catch-all (default route) for this sub-trie. + // To allow the incoming prefix to branch further, we expand the FringeNode + // into a full intermediate node and place its value at the default route index (1). newNode := new(LiteNode[V]) newNode.InsertPrefix(1, kid.Value) @@ -108,63 +118,74 @@ func (n *LiteNode[V]) Insert(pfx netip.Prefix, val V, depth int) (exists bool) { panic("unreachable") } -// InsertPersist is similar to insert but the receiver isn't modified. -// Assumes the caller has pre-cloned the root (COW). It clones the -// internal nodes along the descent path before mutating them. -func (n *LiteNode[V]) InsertPersist(cloneFn value.CloneFunc[V], pfx netip.Prefix, val V, depth int) (exists bool) { +// InsertPersist adds or updates a network prefix and its associated value in the trie +// using Copy-On-Write (COW) semantics. Traversal begins at the specified byte depth. +// +// Unlike [Insert], InsertPersist ensures structural integrity of the existing tree +// by cloning internal nodes along the descent path (Copy-On-Write) before mutation. +// +// Parameters: +// - cloneFn: The function used to clone values (V). +// - pfx: The network prefix to insert (must be in canonical/masked form). +// - val: The value to associate with the prefix. +// - depth: The current depth in the trie (0-based byte index). +// +// Returns true if an existing prefix was updated, false if a new insertion occurred. +func (n *LiteNode[V]) InsertPersist(cloneFn func(V) V, pfx netip.Prefix, val V, depth int) (exists bool) { ip := pfx.Addr() // the pfx must be in canonical form + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // find the proper trie node to insert prefix - // start with prefix octet at depth + // Traverse the prefix's octets. Each depth corresponds to an 8-bit stride. + // We descend through the trie until we either reach the final stride (depth == strideCount) + // or find an empty child slot where we can path-compress the remaining strides. for ; depth < len(octets); depth++ { octet := octets[depth] - // last masked octet: insert/override prefix/val into node - if depth == lastOctetPlusOne { - return n.InsertPrefix(art.PfxToIdx(octet, lastBits), val) + // The current depth matches the prefix's stride count, meaning this is the final + // node for this prefix. We insert it directly into this node's prefix table. + if depth == strideCount { + return n.InsertPrefix(art.PfxToIdx(octet, modBits), val) } - // reached end of trie path ... + // No child exists at this octet path. Instead of creating intermediate nodes + // for the remaining strides, we path-compress the rest of the prefix into a single child slot. if !n.Children.Test(octet) { - // insert prefix path compressed as leaf or fringe - if IsFringe(depth, pfx) { + // If the prefix is perfectly aligned with the next stride boundary (e.g., /16 at depth 1), + // it acts as a default route for everything below it. We store it as a FringeNode. + // Otherwise, it has trailing bits or crosses boundaries, so we store it as a LeafNode. + if IsFringe(depth, pfxLen) { return n.InsertChild(octet, NewFringeNode(val)) } return n.InsertChild(octet, NewLeafNode(pfx, val)) } - // ... or descend down the trie + // A child already exists at this octet path. Retrieve it to either continue + // our descent along the strides or resolve a structural collision with a compressed node. kid := n.MustGetChild(octet) - // kid is node or leaf at addr switch kid := kid.(type) { case *LiteNode[V]: - // clone the traversed path - - // kid points now to cloned kid + // Standard intermediate node: Clone the traversed path to maintain persistence (COW). + // We clone the child node before modifying it, then replace the current child slot. kid = kid.CloneFlat(cloneFn) - - // replace kid with clone n.InsertChild(octet, kid) - n = kid - continue // descend down to next trie level case *LeafNode[V]: - // reached a path compressed prefix - // override value in slot if prefixes are equal + // Collision with an existing path-compressed LeafNode. + // If it's the exact same prefix, simply update the value. if kid.Prefix == pfx { kid.Value = val - // exists return true } - // create new node - // push the leaf down - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: the paths diverge. + // 1. Create a new intermediate node. + // 2. Push the existing leaf down into this new node. + // 3. Replace the current child slot with the new node. + // 4. Descend into the new node to continue inserting 'pfx'. newNode := new(LiteNode[V]) newNode.Insert(kid.Prefix, kid.Value, depth+1) @@ -172,18 +193,17 @@ func (n *LiteNode[V]) InsertPersist(cloneFn value.CloneFunc[V], pfx netip.Prefix n = newNode case *FringeNode[V]: - // reached a path compressed fringe - // override value in slot if pfx is a fringe - if IsFringe(depth, pfx) { + // Collision with an existing path-compressed FringeNode. + // If the incoming prefix is also a fringe at this depth, update the value. + if IsFringe(depth, pfxLen) { kid.Value = val - // exists return true } - // create new node - // push the fringe down, it becomes a default route (idx=1) - // insert new child at current leaf position (addr) - // descend down, replace n with new child + // Collision resolution: + // The existing FringeNode acts as a catch-all (default route) for this sub-trie. + // To allow the incoming prefix to branch further, we expand the FringeNode + // into a full intermediate node and place its value at the default route index (1). newNode := new(LiteNode[V]) newNode.InsertPrefix(1, kid.Value) @@ -193,162 +213,170 @@ func (n *LiteNode[V]) InsertPersist(cloneFn value.CloneFunc[V], pfx netip.Prefix default: panic("logic error, wrong node type") } - } - panic("unreachable") } -// PurgeAndCompress performs bottom-up compression of the trie. +// PurgeAndCompress performs bottom-up trie maintenance to restore path compression +// after a deletion. It unwinds the provided stack of parent nodes, identifying +// nodes that have become sparse (i.e., containing only a single prefix or a single +// child node) and prunes them by promoting the underlying entries to the parent level. // -// The function unwinds the provided stack of parent nodes, checking each level -// for compression opportunities based on child and prefix count. -// It may convert: -// - Nodes with a single prefix into leaf one level above. -// - Nodes with a single leaf or fringe into leaf one level above. +// This ensures the trie remains memory-efficient by collapsing redundant intermediate +// nodes back into path-compressed LeafNodes or FringeNodes whenever possible. // // Parameters: -// - stack: Array of parent nodes to process during unwinding -// - octets: The path of octets taken to reach the current position -// - is4: True for IPv4 processing, false for IPv6 +// - stack: Array of parent nodes to process during bottom-up unwinding. +// - octets: The full path of octets leading to the current node. +// - is4: True for IPv4 processing, false for IPv6. func (n *LiteNode[V]) PurgeAndCompress(stack []*LiteNode[V], octets []uint8, is4 bool) { - // unwind the stack + // Iterate backwards through the ancestor stack to prune nodes from the bottom up. for depth := len(stack) - 1; depth >= 0; depth-- { parent := stack[depth] octet := octets[depth] + // Check if the current node is redundant. + // A node may be redundant if it contains exactly one entry (either a prefix or + // a compressed child node). pfxCount := n.PrefixCount() childCount := n.ChildCount() + // If it contains more than one entry, it is always structurally significant and cannot be pruned. if pfxCount+childCount > 1 { return } switch { case childCount == 1: - singleAddr, _ := n.Children.FirstSet() // single addr must be first bit set + // The node has exactly one child. We determine if it is a path node, + // a compressed LeafNode, or a compressed FringeNode. + singleAddr, _ := n.Children.FirstSet() anyKid := n.MustGetChild(singleAddr) switch kid := anyKid.(type) { case *LiteNode[V]: - // fast exit, we are at an intermediate path node - // no further delete/compress upwards the stack is possible + // The child is an intermediate path node; the tree structure is required + // at this level. Compression cannot proceed further up. return case *LeafNode[V]: - // just one leaf, delete this node and reinsert the leaf above + // The child is a compressed LeafNode. Prune the current node + // and re-insert the leaf into the parent to elevate it. parent.DeleteChild(octet) - - // ... (re)insert the leaf at parents depth parent.Insert(kid.Prefix, kid.Value, depth) case *FringeNode[V]: - // just one fringe, delete this node and reinsert the fringe as leaf above + // The child is a compressed FringeNode. Prune the current node + // and re-insert the fringe as a leaf into the parent. parent.DeleteChild(octet) - // rebuild the prefix with octets, depth, ip version and addr - // depth is the parent's depth, so add +1 here for the kid - // lastOctet in cidrForFringe is the only addr (singleAddr) + // Reconstruct the full prefix for the fringe, as path compression + // requires the entire CIDR path, not just the remainder. + // depth is the parent's depth, so we offset by 1 for the kid's position. fringePfx := CidrForFringe(octets, depth+1, is4, singleAddr) - // ... (re)reinsert prefix/value at parents depth parent.Insert(fringePfx, kid.Value, depth) } case pfxCount == 1: - // just one prefix, delete this node and reinsert the idx as leaf above + // The node has exactly one prefix. Prune the node and elevate the + // prefix to the parent level as a leaf/fringe. parent.DeleteChild(octet) - // get prefix back from idx ... - idx, _ := n.Prefixes.FirstSet() // single idx must be first bit set + // Retrieve the single prefix stored in this node. + idx, _ := n.Prefixes.FirstSet() val := n.MustGetPrefix(idx) - // ... and octet path + // Reconstruct the prefix from the path for re-insertion. path := StridePath{} copy(path[:], octets) - - // depth is the parent's depth, so add +1 here for the kid pfx := CidrFromPath(path, depth+1, is4, idx) - // ... (re)insert prefix/value at parents depth parent.Insert(pfx, val, depth) + default: + panic("unreachable") } - // climb up the stack + // Move up to the next parent in the stack to continue pruning. n = parent } } -// Delete deletes the prefix and returns true if the prefix existed, -// or false otherwise. The prefix must be in canonical form. +// Delete removes the prefix from the trie rooted at n and returns true if the +// prefix existed, false if it was not found. The prefix must be in canonical +// (masked) form. +// +// The trie uses path compression, so a prefix may be stored in one of three ways: +// - In the current node's prefix table when the prefix length aligns exactly +// with the stride boundary at this depth (depth == strideCount). +// - As a path-compressed FringeNode in a child slot for stride-aligned prefixes +// (e.g. /8, /16, /24); occurs at depth == strideCount-1. +// - As a path-compressed LeafNode in a child slot for prefixes otherwise. +// +// After a successful deletion, PurgeAndCompress walks the ancestor stack to prune +// now-empty nodes and restore path compression upward. func (n *LiteNode[V]) Delete(pfx netip.Prefix) (exists bool) { - // invariant, prefix must be masked - - // values derived from pfx - ip := pfx.Addr() + ip := pfx.Addr() // pfx must be in canonical (masked) form + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // record the nodes on the path to the deleted node, needed to purge - // and/or path compress nodes after the deletion of a prefix + // Record ancestor nodes as we descend; PurgeAndCompress uses this stack to + // walk back up and clean up empty or re-compressible nodes after deletion. stack := [MaxTreeDepth]*LiteNode[V]{} - // find the trie node for depth, octet := range octets { - depth = depth & DepthMask // BCE, Delete must be fast + depth &= DepthMask // BCE hint; keep Delete on the fast path - // push current node on stack for path recording - stack[depth] = n + stack[depth] = n // record current node before descending - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - // try to delete prefix in trie node - if exists = n.DeletePrefix(art.PfxToIdx(octet, lastBits)); !exists { + // At the stride boundary, the prefix is stored directly in this node's + // prefix table. + if depth == strideCount { + if exists = n.DeletePrefix(art.PfxToIdx(octet, modBits)); !exists { return false } - // remove now-empty nodes and re-path-compress upwards + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) return true } + // no child node exists at this octet; the prefix is not in the trie if !n.Children.Test(octet) { return false } + + // A child node exists at this octet path, retrieve it. kid := n.MustGetChild(octet) - // kid is node or leaf or fringe at octet switch kid := kid.(type) { case *LiteNode[V]: - n = kid // descend down to next trie level + n = kid // descend to the next trie level case *FringeNode[V]: - // if pfx is no fringe at this depth, fast exit - if !IsFringe(depth, pfx) { + // A FringeNode holds a single stride-aligned prefix (/8, /16, ...). + // If pfx does not qualify as a fringe at this depth, it cannot be here. + if !IsFringe(depth, pfxLen) { return false } - // pfx is fringe at depth, delete fringe n.DeleteChild(octet) - // remove now-empty nodes and re-path-compress upwards + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true case *LeafNode[V]: - // Attention: pfx must be masked to be comparable! + // A LeafNode holds exactly one path-compressed prefix. + // Compare using the canonical (masked) form for an exact match. if kid.Prefix != pfx { return false } - // prefix is equal leaf, delete leaf n.DeleteChild(octet) - // remove now-empty nodes and re-path-compress upwards + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true default: @@ -359,142 +387,150 @@ func (n *LiteNode[V]) Delete(pfx netip.Prefix) (exists bool) { panic("unreachable") } -// DeletePersist is similar to delete but does not mutate the original trie. -// Assumes the caller has pre-cloned the root (COW). It clones the -// internal nodes along the descent path before mutating them. -func (n *LiteNode[V]) DeletePersist(cloneFn value.CloneFunc[V], pfx netip.Prefix) (exists bool) { - ip := pfx.Addr() // the pfx must be in canonical form +// DeletePersist removes the prefix from the trie rooted at n using Copy-On-Write (COW) semantics. +// It returns true if the prefix existed, false if it was not found. The prefix must be in +// canonical (masked) form. +// +// Like [Delete], this method uses path compression. However, DeletePersist ensures +// the structural integrity of the existing tree by cloning internal nodes along the +// descent path (COW) before mutation. +// +// After a successful deletion, PurgeAndCompress walks the ancestor stack to prune +// now-empty nodes and restore path compression upward. +func (n *LiteNode[V]) DeletePersist(cloneFn func(V) V, pfx netip.Prefix) (exists bool) { + ip := pfx.Addr() // pfx must be in canonical (masked) form + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // Stack to keep track of cloned nodes along the path, - // needed for purge and path compression after delete. + // Record ancestor nodes as we descend; PurgeAndCompress uses this stack to + // walk back up and clean up empty or re-compressible nodes after deletion. + // Since this is a COW operation, we store the cloned nodes here. stack := [MaxTreeDepth]*LiteNode[V]{} - // Traverse the trie to locate the prefix to delete. for depth, octet := range octets { - // Keep track of the cloned node at current depth. - stack[depth] = n + depth &= DepthMask // BCE hint; keep DeletePersist on the fast path + stack[depth] = n // record current node before descending - if depth == lastOctetPlusOne { - // Attempt to delete the prefix from the node's prefixes. - if exists = n.DeletePrefix(art.PfxToIdx(octet, lastBits)); !exists { - // Prefix not found, nothing deleted. + // At the stride boundary, the prefix is stored directly in this node's + // prefix table. + if depth == strideCount { + if exists = n.DeletePrefix(art.PfxToIdx(octet, modBits)); !exists { return false } - // After deletion, purge nodes and compress the path if needed. + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true } - addr := octet - - // If child node doesn't exist, no prefix to delete. - if !n.Children.Test(addr) { + // no child node exists at this octet; the prefix is not in the trie + if !n.Children.Test(octet) { return false } - // Fetch child node at current address. - kid := n.MustGetChild(addr) + // A child node exists at this octet path, retrieve it to either continue + // our descent or perform a persistent delete on a compressed node. + kid := n.MustGetChild(octet) switch kid := kid.(type) { case *LiteNode[V]: - // Clone the internal node for copy-on-write. + // Standard intermediate node: Clone the traversed path to maintain + // persistence (COW). We clone the child node before modifying it, + // then replace the current child slot. kid = kid.CloneFlat(cloneFn) - - // Replace child with cloned node. - n.InsertChild(addr, kid) - - // Descend to cloned child node. + n.InsertChild(octet, kid) n = kid continue case *FringeNode[V]: - // Reached a path compressed fringe. - if !IsFringe(depth, pfx) { - // Prefix to delete not found here. + // A FringeNode holds a single stride-aligned prefix (/8, /16, ...). + // If pfx does not qualify as a fringe at this depth, it cannot be here. + if !IsFringe(depth, pfxLen) { return false } - // Delete the fringe node. - n.DeleteChild(addr) + n.DeleteChild(octet) - // Purge and compress affected path. + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true case *LeafNode[V]: - // Reached a path compressed leaf node. + // A LeafNode holds exactly one path-compressed prefix. + // Compare using the canonical (masked) form for an exact match. if kid.Prefix != pfx { - // Leaf prefix does not match; nothing to delete. return false } - // Delete leaf node. - n.DeleteChild(addr) + n.DeleteChild(octet) - // Purge and compress affected path. + // prune now-empty nodes and re-compress the path upwards n.PurgeAndCompress(stack[:depth], octets, is4) - return true default: - // Unexpected node type indicates a logic error. panic("logic error, wrong node type") } } - // Should never happen: traversal always returns or panics inside loop. panic("unreachable") } // Get retrieves the value associated with the given network prefix. -// Returns the stored value and true if the prefix exists in this node, -// zero value and false if the prefix is not found. +// Traversal descends through the trie using the prefix's octets. +// +// The lookup handles path compression transparently: +// - If the path matches an internal node at the target stride, the value is retrieved +// from the node's prefix table. +// - If the path leads to a compressed LeafNode or FringeNode, the function verifies +// the prefix match before returning the value. // // Parameters: -// - pfx: The network prefix to look up (must be in canonical form) +// - pfx: The network prefix to look up (must be in canonical form). // // Returns: -// - val: The value associated with the prefix (zero value if not found) -// - exists: True if the prefix was found, false otherwise +// - val: The value associated with the prefix (zero value if not found). +// - exists: True if the prefix was found, false otherwise. func (n *LiteNode[V]) Get(pfx netip.Prefix) (val V, exists bool) { - // invariant, prefix must be masked - - // values derived from pfx + // The prefix must be provided in canonical (masked) form for correct trie traversal. ip := pfx.Addr() + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) - // find the trie node + // Traverse the trie octet by octet based on the prefix path. for depth, octet := range octets { - if depth == lastOctetPlusOne { - return n.GetPrefix(art.PfxToIdx(octet, lastBits)) + // At the target stride boundary, the prefix is expected in this node's + // prefix table. + if depth == strideCount { + return n.GetPrefix(art.PfxToIdx(octet, modBits)) } + // If no child exists at this path, the prefix is not in the trie. kidAny, ok := n.GetChild(octet) if !ok { return val, false } - // kid is node or leaf or fringe at octet + // Identify the node type at this path segment. switch kid := kidAny.(type) { case *LiteNode[V]: - n = kid // descend down to next trie level + // Standard intermediate node: descend to the next level. + n = kid case *FringeNode[V]: - // reached a path compressed fringe, stop traversing - if IsFringe(depth, pfx) { + // Reached a path-compressed FringeNode. + // Verify if the prefix qualifies as a fringe at this depth to return a match. + if IsFringe(depth, pfxLen) { return kid.Value, true } return val, false case *LeafNode[V]: - // reached a path compressed prefix, stop traversing + // Reached a path-compressed LeafNode. + // Check if the stored prefix matches the lookup prefix exactly. if kid.Prefix == pfx { return kid.Value, true } @@ -512,7 +548,7 @@ func (n *LiteNode[V]) Get(pfx netip.Prefix) (val V, exists bool) { // The callback receives the current value (if found) and existence flag, and returns // a new value and deletion flag. // -// modify returns the size delta (-1, 0, +1). +// Modify returns the size delta (-1, 0, +1). // This method handles path traversal, node creation for new paths, and automatic // purge/compress operations after deletions. // @@ -526,9 +562,10 @@ func (n *LiteNode[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V, var zero V ip := pfx.Addr() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) // record the nodes on the path to the deleted node, needed to purge // and/or path compress nodes after the deletion of a prefix @@ -536,16 +573,15 @@ func (n *LiteNode[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V, // find the proper trie node to update prefix for depth, octet := range octets { - depth = depth & DepthMask // BCE + depth &= DepthMask // BCE // push current node on stack for path recording stack[depth] = n - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx := art.PfxToIdx(octet, lastBits) + // The current depth matches the prefix's stride count, meaning this is the final + // stride for this prefix. Insert or update it directly in this node's prefix table. + if depth == strideCount { + idx := art.PfxToIdx(octet, modBits) oldVal, existed := n.GetPrefix(idx) newVal, del := cb(oldVal, existed) @@ -585,7 +621,7 @@ func (n *LiteNode[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V, } // insert - if IsFringe(depth, pfx) { + if IsFringe(depth, pfxLen) { n.InsertChild(octet, NewFringeNode(newVal)) } else { n.InsertChild(octet, NewLeafNode(pfx, newVal)) @@ -644,7 +680,7 @@ func (n *LiteNode[V]) Modify(pfx netip.Prefix, cb func(val V, found bool) (_ V, case *FringeNode[V]: // update existing value if prefix is fringe - if IsFringe(depth, pfx) { + if IsFringe(depth, pfxLen) { newVal, del := cb(kid.Value, true) if !del { kid.Value = newVal @@ -774,8 +810,8 @@ func (n *LiteNode[V]) EqualRec(o *LiteNode[V]) bool { // // It returns immediately if n is nil or empty. For each visited internal node // it calls dump to write the node's representation, then iterates its child -// addresses and recurses into children that implement nodeDumper[V] (internal -// subnodes). The path slice and depth together represent the byte-wise path +// addresses and recurses into children of type *LiteNode[V] (internal subnodes). +// The path slice and depth together represent the byte-wise path // from the root to the current node; depth is incremented for each recursion. // The is4 flag controls IPv4/IPv6 formatting used by dump. func (n *LiteNode[V]) DumpRec(w io.Writer, path StridePath, depth int, is4 bool) { @@ -803,8 +839,8 @@ func (n *LiteNode[V]) dump(w io.Writer, path StridePath, depth int, is4 bool) { bits := depth * strideLen indent := strings.Repeat(".", depth) - // printing values if V is not zero-sized - printValues := !value.IsZST[V]() + // printing values if V is not the empty struct{} + printValues := !value.IsEmptyStruct[V]() // node type with depth and octet path and bits. fmt.Fprintf(w, "\n%s[%s] depth: %d path: [%s] / %d\n", @@ -967,7 +1003,7 @@ func (n *LiteNode[V]) DumpString(octets []uint8, depth int, is4 bool) string { // - stopNode: has children but no subnodes (nodes == 0) // - halfNode: contains at least one leaf or fringe and also has subnodes, but // no prefixes -// - fullNode: has prefixes or leaves/fringes and also has subnodes +// - fullNode: has prefixes and also has subnodes // - pathNode: has subnodes only (no prefixes, leaves, or fringes) // // The order of these checks is significant to ensure the correct classification. @@ -1024,7 +1060,7 @@ func (n *LiteNode[V]) Stats() (s StatsT) { // It walks the node tree recursively and sums immediate counts (prefixes and // child slots) plus the number of nodes, leaves, and fringe nodes in the // subtree. If n is nil or empty, a zeroed stats is returned. The returned -// stats.nodes includes the current node. The function will panic if a child +// SubNodes count includes the current node. The function will panic if a child // has an unexpected concrete type. func (n *LiteNode[V]) StatsRec() (s StatsT) { if n == nil || n.IsEmpty() { @@ -1080,8 +1116,8 @@ func (n *LiteNode[V]) FprintRec(w io.Writer, parent TrieItem[V], pad string) err return CmpPrefix(a.Cidr, b.Cidr) }) - // printing values if V is not zero-sized - printValues := !value.IsZST[V]() + // printing values if V is not the empty struct{} + printValues := !value.IsEmptyStruct[V]() // for all direct item under this node ... for i, item := range directItems { @@ -1178,7 +1214,7 @@ func (n *LiteNode[V]) DirectItemsRec(parentIdx uint8, path StridePath, depth int path[depth] = addr directItems = append(directItems, kid.DirectItemsRec(0, path, depth+1, is4)...) - case *LeafNode[V]: // path-compressed child, stop's recursion for this child + case *LeafNode[V]: // path-compressed child, stops recursion for this child item := TrieItem[V]{ Node: nil, Is4: is4, @@ -1187,7 +1223,7 @@ func (n *LiteNode[V]) DirectItemsRec(parentIdx uint8, path StridePath, depth int } directItems = append(directItems, item) - case *FringeNode[V]: // path-compressed fringe, stop's recursion for this child + case *FringeNode[V]: // path-compressed fringe, stops recursion for this child item := TrieItem[V]{ Node: nil, Is4: is4, @@ -1221,9 +1257,9 @@ func (n *LiteNode[V]) DirectItemsRec(parentIdx uint8, path StridePath, depth int // The merge operation is destructive on the receiver n, but leaves the source node o unchanged. // // Returns the number of duplicate prefixes that were overwritten during merging. -func (n *LiteNode[V]) UnionRec(cloneFn value.CloneFunc[V], o *LiteNode[V], depth int) (duplicates int) { +func (n *LiteNode[V]) UnionRec(cloneFn func(V) V, o *LiteNode[V], depth int) (duplicates int) { if cloneFn == nil { - cloneFn = value.CopyVal + cloneFn = func(v V) V { return v } } buf := [256]uint8{} @@ -1254,9 +1290,9 @@ func (n *LiteNode[V]) UnionRec(cloneFn value.CloneFunc[V], o *LiteNode[V], depth } // UnionRecPersist is similar to unionRec but performs an immutable union of nodes. -func (n *LiteNode[V]) UnionRecPersist(cloneFn value.CloneFunc[V], o *LiteNode[V], depth int) (duplicates int) { +func (n *LiteNode[V]) UnionRecPersist(cloneFn func(V) V, o *LiteNode[V], depth int) (duplicates int) { if cloneFn == nil { - cloneFn = value.CopyVal + cloneFn = func(v V) V { return v } } buf := [256]uint8{} @@ -1305,7 +1341,7 @@ func (n *LiteNode[V]) UnionRecPersist(cloneFn value.CloneFunc[V], o *LiteNode[V] // fringe, node <-- insert new node, push this fringe down, union rec-descent // fringe, leaf <-- insert new node, push this fringe down, insert other leaf at depth+1 // fringe, fringe <-- just overwrite value -func (n *LiteNode[V]) handleMatrix(cloneFn value.CloneFunc[V], thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { +func (n *LiteNode[V]) handleMatrix(cloneFn func(V) V, thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { // Do ALL type assertions upfront - reduces line noise var ( thisNode, thisIsNode = thisChild.(*LiteNode[V]) @@ -1422,7 +1458,7 @@ func (n *LiteNode[V]) handleMatrix(cloneFn value.CloneFunc[V], thisExists bool, // fringe, node <-- insert new node, push this fringe down, union rec-descent // fringe, leaf <-- insert new node, push this fringe down, insert other leaf at depth+1 // fringe, fringe <-- just overwrite value -func (n *LiteNode[V]) handleMatrixPersist(cloneFn value.CloneFunc[V], thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { +func (n *LiteNode[V]) handleMatrixPersist(cloneFn func(V) V, thisExists bool, thisChild, otherChild any, addr uint8, depth int) int { // Do ALL type assertions upfront - reduces line noise var ( thisNode, thisIsNode = thisChild.(*LiteNode[V]) @@ -1654,9 +1690,7 @@ func (n *LiteNode[V]) AllRecSorted(path StridePath, depth int, is4 bool, yield f } case *FringeNode[V]: fringePfx := CidrForFringe(path[:], depth, is4, childAddr) - // callback for this fringe if !yield(fringePfx, kid.Value) { - // early exit return false } @@ -1691,9 +1725,7 @@ func (n *LiteNode[V]) AllRecSorted(path StridePath, depth int, is4 bool, yield f } case *FringeNode[V]: fringePfx := CidrForFringe(path[:], depth, is4, addr) - // callback for this fringe if !yield(fringePfx, kid.Value) { - // early exit return false } @@ -1879,9 +1911,10 @@ func (n *LiteNode[V]) EachSubnet(octets []byte, depth int, is4 bool, pfxIdx uint // the iteration early. func (n *LiteNode[V]) Supernets(pfx netip.Prefix, yield func(netip.Prefix, V) bool) { ip := pfx.Addr() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) // stack of the traversed nodes for reverse ordering of supernets stack := [MaxTreeDepth]*LiteNode[V]{} @@ -1894,7 +1927,7 @@ func (n *LiteNode[V]) Supernets(pfx netip.Prefix, yield func(netip.Prefix, V) bo LOOP: for depth, octet = range octets { // stepped one past the last stride of interest; back up to last and exit - if depth > lastOctetPlusOne { + if depth > strideCount { depth-- break } @@ -1955,11 +1988,10 @@ LOOP: // all others are just host routes var idx uint8 octet = octets[depth] - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx = art.PfxToIdx(octet, lastBits) + // Last “octet” from prefix + // Note: For /32 and /128, depth never reaches strideCount (4/16), + if depth == strideCount { + idx = art.PfxToIdx(octet, modBits) } else { idx = art.OctetToIdx(octet) } @@ -1996,17 +2028,18 @@ LOOP: func (n *LiteNode[V]) Subnets(pfx netip.Prefix, yield func(netip.Prefix, V) bool) { // values derived from pfx ip := pfx.Addr() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) // find the trie node for depth, octet := range octets { - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4/16), + // Last “octet” from prefix + // Note: For /32 and /128, depth never reaches strideCount (4/16), // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx := art.PfxToIdx(octet, lastBits) + if depth == strideCount { + idx := art.PfxToIdx(octet, modBits) n.EachSubnet(octets, depth, is4, idx, yield) return } @@ -2124,7 +2157,7 @@ func (n *LiteNode[V]) Overlaps(o *LiteNode[V], depth int) bool { // OverlapsRoutes compares the prefix sets of two nodes (n and o). // // It first checks for direct bitset intersection (identical indices), -// then walks both prefix sets using lpmTest to detect if any +// then walks both prefix sets using the Contains method to detect if any // of the n-prefixes is contained in o, or vice versa. func (n *LiteNode[V]) OverlapsRoutes(o *LiteNode[V]) bool { // some prefixes are identical, trivial overlap @@ -2267,19 +2300,20 @@ func (n *LiteNode[V]) OverlapsSameChildren(o *LiteNode[V], depth int) bool { // trie traversal across varying prefix lengths and compression levels. func (n *LiteNode[V]) OverlapsPrefixAtDepth(pfx netip.Prefix, depth int) bool { ip := pfx.Addr() + pfxLen := pfx.Bits() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := DivMod8(pfxLen) for ; depth < len(octets); depth++ { - if depth > lastOctetPlusOne { + if depth > strideCount { break } octet := octets[depth] // full octet path in node trie, check overlap with last prefix octet - if depth == lastOctetPlusOne { - return n.OverlapsIdx(art.PfxToIdx(octet, lastBits)) + if depth == strideCount { + return n.OverlapsIdx(art.PfxToIdx(octet, modBits)) } // test if any route overlaps prefix´ so far diff --git a/vendor/github.com/gaissmai/bart/internal/nodes/nodebasics.go b/vendor/github.com/gaissmai/bart/internal/nodes/nodebasics.go index a2f1088dd8..2439de1535 100644 --- a/vendor/github.com/gaissmai/bart/internal/nodes/nodebasics.go +++ b/vendor/github.com/gaissmai/bart/internal/nodes/nodebasics.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package nodes @@ -11,7 +11,6 @@ import ( "strings" "github.com/gaissmai/bart/internal/art" - "github.com/gaissmai/bart/internal/value" ) // strideLen represents the byte stride length for the multibit trie. @@ -159,41 +158,23 @@ func NewFringeNode[V any](val V) *FringeNode[V] { return &FringeNode[V]{Value: val} } -// IsFringe determines whether a prefix qualifies as a "fringe node" - -// that is, a special kind of path-compressed leaf inserted at the final -// possible trie level (depth == lastOctet). +// IsFringe determines whether a prefix qualifies as a "FringeNode". +// Only prefixes that are stride-aligned (i.e., /8, /16, ..., /128) +// can be fringe-compressed. If these prefixes are inserted at a position +// where depth == (strideCount-1), they are treated as FringeNodes; +// at positions where depth < (strideCount-1), they are treated as LeafNodes. // -// Both "leaves" and "fringes" are path-compressed terminal entries; -// the distinction lies in their position within the trie: +// Example for a stride-aligned prefix like 192.168.1.0/24 (strideCount = 3, modBits = 0): // -// - A leaf is inserted at any intermediate level if no further stride -// boundary matches (depth < lastOctet). -// -// - A fringe is inserted at the last possible stride level -// (depth == lastOctet) before a prefix would otherwise land -// as a direct prefix (depth == lastOctet+1). -// -// Special property: -// - A fringe acts as a default route for all downstream bit patterns -// extending beyond its prefix. -// -// Examples: -// -// e.g. prefix is addr/8, or addr/16, or ... addr/128 -// depth < lastOctet : a leaf, path-compressed -// depth == lastOctet : a fringe, path-compressed -// depth == lastOctet+1: a prefix with octet/pfx == 0/0 => idx == 1, a strides default route -// -// Logic: -// - A prefix qualifies as a fringe if: -// depth == lastOctet && lastBits == 0 -// (i.e., aligned on stride boundary, /8, /16, ... /128 bits) -func IsFringe(depth int, pfx netip.Prefix) bool { - lastOctetPlusOne, lastBits := LastOctetPlusOneAndLastBits(pfx) - return depth == lastOctetPlusOne-1 && lastBits == 0 +// depth = 3, depth == strideCount : A direct prefix with 0/0 (default route for subtrie). +// depth = 2, depth == (strideCount-1) : A path-compressed fringe. +// depth < 2, depth < (strideCount-1) : A path-compressed leaf. +func IsFringe(depth int, pfxLen int) bool { + strideCount, modBits := DivMod8(pfxLen) + return depth == strideCount-1 && modBits == 0 } -// cmpIndexRank, sort indexes in prefix sort order. +// CmpIndexRank, sort indexes in prefix sort order. func CmpIndexRank(aIdx, bIdx uint8) int { // convert idx [1..255] to prefix aOctet, aBits := art.IdxToPfx(aIdx) @@ -218,7 +199,7 @@ func CmpIndexRank(aIdx, bIdx uint8) int { // // Returns the reconstructed netip.Prefix. func CidrFromPath(path StridePath, depth int, is4 bool, idx uint8) netip.Prefix { - depth = depth & DepthMask // BCE + depth &= DepthMask // BCE // retrieve the last octet and pfxLen octet, pfxLen := art.IdxToPfx(idx) @@ -247,21 +228,21 @@ func CidrFromPath(path StridePath, depth int, is4 bool, idx uint8) netip.Prefix // CidrForFringe reconstructs a CIDR prefix for a fringe node from the traversal path. // Since fringe nodes don't store their prefix explicitly, it's derived entirely -// from the node's position in the trie. +// from the node's position in the trie and its final byte value. // // Parameters: -// - octets: The path of octets leading to the fringe -// - depth: Current depth in the trie -// - is4: True for IPv4 processing, false for IPv6 -// - lastOctet: The final octet where the fringe is located +// - octets: The path of previous bytes leading up to the fringe. +// - depth: Current depth in the trie (which equals strideCount - 1). +// - is4: True for IPv4 processing, false for IPv6. +// - fringeByte: The actual 8-bit value (0-255) of the prefix at this final stride. // // Returns the reconstructed netip.Prefix for the fringe. -func CidrForFringe(octets []byte, depth int, is4 bool, lastOctet uint8) netip.Prefix { - depth = depth & DepthMask // BCE +func CidrForFringe(octets []byte, depth int, is4 bool, fringeByte uint8) netip.Prefix { + depth &= DepthMask // BCE var path StridePath copy(path[:], octets) - path[depth] = lastOctet + path[depth] = fringeByte // canonicalize, fringe bit boundaries are always a multiple of a byte clear(path[depth+1:]) @@ -274,7 +255,7 @@ func CidrForFringe(octets []byte, depth int, is4 bool, lastOctet uint8) netip.Pr ip = netip.AddrFrom16(path) } - // it's a fringe, bits are always /8, /16, /24, ... + // it's a fringe, bits are always aligned on stride boundaries (/8, /16, /24, ...) bits := (depth + 1) << 3 // PrefixFrom does not allocate and does not mask off the host bits of ip. @@ -282,31 +263,28 @@ func CidrForFringe(octets []byte, depth int, is4 bool, lastOctet uint8) netip.Pr return netip.PrefixFrom(ip, bits) } -// LastOctetPlusOneAndLastBits returns the count of full 8‑bit strides (bits/8) -// and the leftover bits in the final stride (bits%8) for pfx. +// DivMod8 returns the count of full 8‑bit strides (bits/8) +// and the remaining bits in the final stride (bits%8) for pfxLen. // -// lastOctetPlusOne is the count of full 8‑bit strides (bits/8). -// lastBits is the remaining bit count in the final stride (bits%8), -// -// ATTENTION: Split the IP prefixes at 8bit borders, count from 0. +// ATTENTION: Split the IP prefixes at 8-bit borders, count from 0. // // /7, /15, /23, /31, ..., /127 // // BitPos: [0-7],[8-15],[16-23],[24-31],[32] // BitPos: [0-7],[8-15],[16-23],[24-31],[32-39],[40-47],[48-55],[56-63],...,[120-127],[128] // -// 0.0.0.0/0 => lastOctetPlusOne: 0, lastBits: 0 (default route) -// 0.0.0.0/7 => lastOctetPlusOne: 0, lastBits: 7 -// 0.0.0.0/8 => lastOctetPlusOne: 1, lastBits: 0 (possible fringe) -// 10.0.0.0/8 => lastOctetPlusOne: 1, lastBits: 0 (possible fringe) -// 10.0.0.0/22 => lastOctetPlusOne: 2, lastBits: 6 -// 10.0.0.0/29 => lastOctetPlusOne: 3, lastBits: 5 -// 10.0.0.0/32 => lastOctetPlusOne: 4, lastBits: 0 (possible fringe) +// 0.0.0.0/0 => strideCount: 0, modBits: 0 (default route) +// 0.0.0.0/7 => strideCount: 0, modBits: 7 +// 0.0.0.0/8 => strideCount: 1, modBits: 0 (fringe candidate) +// 10.0.0.0/8 => strideCount: 1, modBits: 0 (fringe candidate) +// 10.0.0.0/22 => strideCount: 2, modBits: 6 +// 10.0.0.0/29 => strideCount: 3, modBits: 5 +// 10.0.0.0/32 => strideCount: 4, modBits: 0 (fringe candidate) // -// ::/0 => lastOctetPlusOne: 0, lastBits: 0 (default route) -// ::1/128 => lastOctetPlusOne: 16, lastBits: 0 (possible fringe) -// 2001:db8::/42 => lastOctetPlusOne: 5, lastBits: 2 -// 2001:db8::/56 => lastOctetPlusOne: 7, lastBits: 0 (possible fringe) +// ::/0 => strideCount: 0, modBits: 0 (default route) +// ::1/128 => strideCount: 16, modBits: 0 (fringe candidate) +// 2001:db8::/42 => strideCount: 5, modBits: 2 +// 2001:db8::/56 => strideCount: 7, modBits: 0 (fringe candidate) // // /32 and /128 prefixes are special, they never form a new node, // At the end of the trie (IPv4: depth 4, IPv6: depth 16) they are always @@ -315,27 +293,25 @@ func CidrForFringe(octets []byte, depth int, is4 bool, lastOctet uint8) netip.Pr // We are not splitting at /8, /16, ..., because this would mean that the // first node would have 512 prefixes, 9 bits from [0-8]. All remaining nodes // would then only have 8 bits from [9-16], [17-24], [25..32], ... -// but the algorithm would then require a variable length bitset. +// but the algorithm would then require a variable length bitset +// or imply a double-sized bitset. // // If you can commit to a fixed size of [4]uint64, then the algorithm is // much faster due to modern CPUs. // // Perhaps a future Go version that supports SIMD instructions for the [4]uint64 vectors // will make the algorithm even faster on suitable hardware. -func LastOctetPlusOneAndLastBits(pfx netip.Prefix) (lastOctetPlusOne int, lastBits uint8) { - // lastOctetPlusOne: range from 0..4 or 0..16 !ATTENTION: not 0..3 or 0..15 - // lastBits: range from 0..7 - bits := pfx.Bits() - - //nolint:gosec // G115: narrowing conversion is safe here (bits in [0..128]) - return bits >> 3, uint8(bits & 7) +func DivMod8(pfxLen int) (strideCount int, modBits uint8) { + // strideCount: range from 0..4 or 0..16 + // modBits: range from 0..7 + return pfxLen >> 3, uint8(pfxLen & 7) } // CloneLeaf creates and returns a copy of the leafNode receiver. // If cloneFn is nil, the value is copied directly without modification. // Otherwise, cloneFn is applied to the value for deep cloning. // The prefix field is always copied as is. -func (l *LeafNode[V]) CloneLeaf(cloneFn value.CloneFunc[V]) *LeafNode[V] { +func (l *LeafNode[V]) CloneLeaf(cloneFn func(V) V) *LeafNode[V] { if l == nil { return nil } @@ -346,10 +322,10 @@ func (l *LeafNode[V]) CloneLeaf(cloneFn value.CloneFunc[V]) *LeafNode[V] { return &LeafNode[V]{Prefix: l.Prefix, Value: cloneFn(l.Value)} } -// cloneFringe creates and returns a copy of the fringeNode receiver. +// CloneFringe creates and returns a copy of the FringeNode receiver. // If cloneFn is nil, the value is copied directly without modification. // Otherwise, cloneFn is applied to the value for deep cloning. -func (l *FringeNode[V]) CloneFringe(cloneFn value.CloneFunc[V]) *FringeNode[V] { +func (l *FringeNode[V]) CloneFringe(cloneFn func(V) V) *FringeNode[V] { if l == nil { return nil } diff --git a/vendor/github.com/gaissmai/bart/internal/sparse/array256.go b/vendor/github.com/gaissmai/bart/internal/sparse/array256.go index 76a6e722a6..5c215cf305 100644 --- a/vendor/github.com/gaissmai/bart/internal/sparse/array256.go +++ b/vendor/github.com/gaissmai/bart/internal/sparse/array256.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT // Package sparse provides a compact and efficient sparse array @@ -51,12 +51,12 @@ type Array256[T any] struct { // Set panics. The bitset is internally coupled with Items[]. // Use InsertAt to add or overwrite at index i. -func (a *Array256[T]) Set(uint) { +func (a *Array256[T]) Set(uint8) { panic("forbidden, use InsertAt") } // Clear panics. The bitset is internally coupled with Items[]. -func (a *Array256[T]) Clear(uint) { +func (a *Array256[T]) Clear(uint8) { panic("forbidden, use DeleteAt") } @@ -72,7 +72,7 @@ func (a *Array256[T]) Clear(uint) { // ⬆ // // BitSet256.Test(5): true -// BitSet256.Rank(5): 2, +// BitSet256.Rank(5): 2 func (a *Array256[T]) Get(i uint8) (value T, ok bool) { if a.Test(i) { return a.Items[a.Rank(i)-1], true @@ -108,24 +108,37 @@ func (a *Array256[T]) Copy() *Array256[T] { return c } -// InsertAt adds the value to the index i. If a value already exists there, -// it is overwritten and true is returned. +// InsertAt inserts or overwrites the value at sparse index i. // -// Otherwise, the value is inserted, the bit is marked, and false returned. -func (a *Array256[T]) InsertAt(i uint8, value T) (exists bool) { - // slot exists, overwrite value +// If the slot at i already exists, its value is overwritten in-place and +// (rank0, true) is returned, where rank0 = Rank(i)-1 is the 0-based index +// into Items[]. +// +// If the slot is new, the bit for i is set, the value is inserted into Items[] +// at the correct packed position, and (rank0, false) is returned. +// +// rank0 can be cached by the caller to directly access Items[rank0] +// without a second Rank() call. +func (a *Array256[T]) InsertAt(i uint8, value T) (rank0 int, exists bool) { + + // slot exists, just overwrite value if a.Test(i) { - a.Items[a.Rank(i)-1] = value - return true + rank0 = a.Rank(i) - 1 + a.Items[rank0] = value + return rank0, true } + // Since i is not set yet, Rank(i) before Set(i) is exactly + // the index where the new item should be inserted (equivalent to Rank(i)-1 after Set(i)). + rank0 = a.Rank(i) + // new, insert into bitset ... a.BitSet256.Set(i) - // ... and slice - a.insertItem(a.Rank(i)-1, value) + // ... and insert value into slice + a.insertItem(rank0, value) - return false + return rank0, false } // DeleteAt removes the value at index i from the sparse array, @@ -154,7 +167,7 @@ func (a *Array256[T]) DeleteAt(i uint8) (value T, exists bool) { // shifting all following elements one position to the right to make space. // // This method must be called with the correct insertion index - that is, -// the rank-0 value of the corresponding bit index i in BitSet256 once it's set. +// the rank-0 value of the corresponding bit index i in BitSet256 (calculated before Set(i)). // // The slice will be extended by one element. If the capacity allows, this is done // without reallocation (fast path); otherwise slice growth occurs (slow path). diff --git a/vendor/github.com/gaissmai/bart/internal/tests/golden/table.go b/vendor/github.com/gaissmai/bart/internal/tests/golden/table.go index 421287e549..727b3a9b65 100644 --- a/vendor/github.com/gaissmai/bart/internal/tests/golden/table.go +++ b/vendor/github.com/gaissmai/bart/internal/tests/golden/table.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package golden diff --git a/vendor/github.com/gaissmai/bart/internal/tests/random/random.go b/vendor/github.com/gaissmai/bart/internal/tests/random/random.go index 5dcd32dbd7..7ee67a65d8 100644 --- a/vendor/github.com/gaissmai/bart/internal/tests/random/random.go +++ b/vendor/github.com/gaissmai/bart/internal/tests/random/random.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package random @@ -41,6 +41,7 @@ func Prefix6(prng *rand.Rand) netip.Prefix { func IP4(prng *rand.Rand) netip.Addr { var b [4]byte for i := range b { + //nolint:gosec // G115: integer overflow conversion uint -> byte b[i] = byte(prng.UintN(256)) } return netip.AddrFrom4(b) @@ -49,6 +50,7 @@ func IP4(prng *rand.Rand) netip.Addr { func IP6(prng *rand.Rand) netip.Addr { var b [16]byte for i := range b { + //nolint:gosec // G115: integer overflow conversion uint -> byte b[i] = byte(prng.UintN(256)) } return netip.AddrFrom16(b) diff --git a/vendor/github.com/gaissmai/bart/internal/value/value.go b/vendor/github.com/gaissmai/bart/internal/value/value.go index 8fa7bd0b7a..9ebe1ae90a 100644 --- a/vendor/github.com/gaissmai/bart/internal/value/value.go +++ b/vendor/github.com/gaissmai/bart/internal/value/value.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT // Package value provides runtime utilities for working with generic type @@ -6,134 +6,72 @@ // // The package offers three main categories of utilities: // -// # Zero-Sized Type (ZST) Detection +// # V as type struct{} Detection // -// IsZST[V] detects whether a type parameter V is a zero-sized type (such as -// struct{} or [0]byte). This serves two purposes: -// - Runtime validation: Fast[V] cannot work correctly with zero-sized types -// and must reject them. PanicOnZST enables a safety check that panics -// during Fast.Insert and Fast.InsertPersist operations. -// - Debug output clarity: Zero-sized types carry no information in their -// values. Omitting them from dumps and prints reduces line noise and -// improves readability. +// IsEmptyStruct[V] detects whether a type parameter V is a `struct{}`. These +// types carry no information in their values. Omitting them from dumps +// and prints reduces line noise and improves readability. // // # Value Equality // -// The Equaler[V] interface and Equal function enable custom equality logic -// for payload values. When V implements Equaler[V], the Equal function uses -// that implementation, avoiding the potentially expensive reflect.DeepEqual. +// The [Equal] function enables custom equality logic for payload values. +// When V implements an `Equal(V) bool` method, [Equal] uses that implementation, +// avoiding the potentially expensive [reflect.DeepEqual] fallback. // // # Value Cloning // -// The Cloner[V] interface and associated functions (CloneFnFactory, CloneVal, -// CopyVal) support deep copying of payload values for persistent operations. -// When V implements Cloner[V], bart methods like InsertPersist, DeletePersist, -// and UnionPersist use the Clone method to create independent copies. +// The [CloneFnFactory] supports copying of payload values for persistent +// operations. When V implements a `Clone() V` method, +// bart methods like InsertPersist, DeletePersist, and UnionPersist use the +// generated clone function to create independent copies. // // This is an internal package used by the bart data structure implementation. package value -import ( - "fmt" - "reflect" -) - -// IsZST reports whether type V is a zero-sized type (ZST). -// -// Zero-sized types such as struct{}, [0]byte, or structs/arrays with no fields -// occupy no memory. The Go runtime optimizes allocations of ZSTs by returning -// pointers to the same memory address (typically runtime.zerobase). -// -// This function exploits that optimization: it allocates two instances of V -// and compares their addresses. If the addresses are equal, V must be a ZST, -// since distinct non-zero-sized allocations would have different addresses. -// -// The helper escapeToHeap ensures both allocations reach the heap and prevents -// the compiler from proving address equality at compile time, which would -// invalidate the runtime check. -func IsZST[V any]() bool { - a, b := escapeToHeap[V]() - return a == b +// IsEmptyStruct reports whether type V is the unnamed empty struct type `struct{}`. +func IsEmptyStruct[V any]() bool { + var zero V + _, ok := any(zero).(struct{}) + return ok } -// escapeToHeap forces two allocations of type V to escape to the heap. +// Equal compares two values of type V for equality. // -// The go:noinline directive is critical: it prevents the compiler from inlining -// this function and optimizing away the allocations or proving that a == b at -// compile time. Without it, the compiler could elide one allocation or determine -// the result statically, breaking the ZST detection heuristic. +// If V implements an `Equal(V) bool` method, its custom equality logic is used. +// Otherwise, values are compared directly using the == operator. // -//go:noinline -func escapeToHeap[V any]() (*V, *V) { - return new(V), new(V) -} - -// PanicOnZST panics if V is a zero sized type. -// bart.Fast must reject zero-sized types as payload. -func PanicOnZST[V any]() { - if IsZST[V]() { - panic(fmt.Errorf("%T is a zero-sized type, not allowed as payload for bart.Fast", *new(V))) - } -} - -// Equaler is a generic interface for types that can decide their own -// equality logic. It can be used to override the potentially expensive -// default comparison with [reflect.DeepEqual]. -type Equaler[V any] interface { - Equal(other V) bool -} - -// Equal compares two values of type V for equality. -// If V implements Equaler[V], that custom equality method is used, -// avoiding the potentially expensive reflect.DeepEqual. -// Otherwise, reflect.DeepEqual is used as a fallback. +// If V is not comparable at runtime (such as a slice or map without an Equal +// method), a runtime panic will occur. +// +// Note: If V implements Equal(V) bool with a pointer receiver, the Equal +// method should handle nil receivers gracefully. func Equal[V any](v1, v2 V) bool { - // you can't assert directly on a type parameter - if v1, ok := any(v1).(Equaler[V]); ok { - return v1.Equal(v2) + if eq, ok := any(v1).(interface{ Equal(V) bool }); ok { + return eq.Equal(v2) } - // fallback - return reflect.DeepEqual(v1, v2) -} -// Cloner is an interface that enables deep cloning of values of type V. -// If a value implements Cloner[V], Table methods such as InsertPersist, -// ModifyPersist, DeletePersist, UnionPersist, Union and Clone will use -// its Clone method to perform deep copies. -type Cloner[V any] interface { - Clone() V + return any(v1) == any(v2) } -// CloneFunc is a type definition for a function that takes a value of type V -// and returns the (possibly cloned) value of type V. -type CloneFunc[V any] func(V) V - -// CloneFnFactory returns a CloneFunc. -// If V implements Cloner[V], the returned function should perform -// a deep copy using Clone(), otherwise it returns nil. -func CloneFnFactory[V any]() CloneFunc[V] { +// CloneFnFactory returns a function that takes a value of type V and returns +// a copy by calling its `Clone` method. +// +// If V does not implement a `Clone() V` method, it returns nil. +// +// Note: If V implements `Clone() V` with a pointer receiver, the `Clone` +// method should handle nil receivers gracefully. +func CloneFnFactory[V any]() func(V) V { var zero V - // you can't assert directly on a type parameter - if _, ok := any(zero).(Cloner[V]); ok { - return CloneVal[V] - } - return nil -} -// CloneVal returns a deep clone of val by calling Clone when -// val implements Cloner[V]. If val does not implement -// Cloner[V] or the Cloner receiver is nil (val is a nil pointer), -// CloneVal returns val unchanged. -func CloneVal[V any](val V) V { - // you can't assert directly on a type parameter - c, ok := any(val).(Cloner[V]) - if !ok || c == nil { - return val + // Safely check if V implements the clone method using an inline interface. + if _, ok := any(zero).(interface{ Clone() V }); ok { + // Return an anonymous closure directly. + // Since we already proved V implements the interface above, + // the direct type assertion here is guaranteed to succeed. + return func(val V) V { + return any(val).(interface{ Clone() V }).Clone() + } } - return c.Clone() -} -// CopyVal just copies the value of any type V. -func CopyVal[V any](val V) V { - return val + return nil } diff --git a/vendor/github.com/gaissmai/bart/lite.go b/vendor/github.com/gaissmai/bart/lite.go index a266c558af..92ff17a8c8 100644 --- a/vendor/github.com/gaissmai/bart/lite.go +++ b/vendor/github.com/gaissmai/bart/lite.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package bart @@ -21,6 +21,7 @@ import ( // The zero value is ready to use. // // A Lite table must not be copied by value; always pass by pointer. +// Nil pointers as receivers or arguments are forbidden and will panic. // // Performance note: Do not pass IPv4-in-IPv6 addresses (e.g., ::ffff:192.0.2.1) // as input. The methods do not perform automatic unmapping to avoid unnecessary @@ -73,7 +74,7 @@ func (l *Lite) LookupPrefix(pfx netip.Prefix) bool { // match any address in the provided prefix range. // // This is functionally identical to LookupPrefix but returns the -// matching prefix (lpmPfx) itself. +// matching LPM prefix itself. // // This method is slower than LookupPrefix and should only be used if the // matching lpm entry is also required for other reasons. @@ -165,11 +166,12 @@ func (l *Lite) Modify(pfx netip.Prefix, cb func(exists bool) (del bool)) { // ModifyPersist is similar to Modify but the receiver isn't modified and // a new *Lite is returned. func (l *Lite) ModifyPersist(pfx netip.Prefix, cb func(exists bool) (del bool)) *Lite { - wrappedFn := func(_ struct{}, exists bool) (_ struct{}, del bool) { + // wrap callback to match the signature of liteTable.ModifyPersist + cbWrapper := func(_ struct{}, exists bool) (_ struct{}, del bool) { return struct{}{}, cb(exists) } - lp := l.liteTable.ModifyPersist(pfx, wrappedFn) + lp := l.liteTable.ModifyPersist(pfx, cbWrapper) //nolint:govet // copy of *lp is here by intention return &Lite{*lp} } @@ -185,9 +187,6 @@ func dropSeq2[V any](seq2 iter.Seq2[netip.Prefix, V]) iter.Seq[netip.Prefix] { // Clone returns a copy of the routing table. func (l *Lite) Clone() *Lite { - if l == nil { - return nil - } return &Lite{*l.liteTable.Clone()} } @@ -195,112 +194,58 @@ func (l *Lite) Clone() *Lite { // // All prefixes from the other table (o) are inserted into the receiver. func (l *Lite) Union(o *Lite) { - if o == nil { - return - } l.liteTable.Union(&o.liteTable) } // UnionPersist is similar to [Union] but the receiver isn't modified. // // All nodes touched during union are cloned and a new *Lite is returned. -// If o is nil or empty, no nodes are touched and the receiver may be +// If o is empty, no nodes are touched and the receiver may be // returned unchanged. func (l *Lite) UnionPersist(o *Lite) *Lite { - if o == nil || (o.size4 == 0 && o.size6 == 0) { + lp := l.liteTable.UnionPersist(&o.liteTable) + if lp == &l.liteTable { return l } - lp := l.liteTable.UnionPersist(&o.liteTable) //nolint:govet // copy of *lp is here by intention return &Lite{*lp} } // All returns an iterator over all prefixes in the table. // -// The entries from both IPv4 and IPv6 subtries are yielded using an internal recursive traversal. -// The iteration order is unspecified and may vary between calls; for a stable order, use AllSorted. -// -// You can use All directly in a for-range loop without providing a yield function. -// The Go compiler automatically synthesizes the yield callback for you: -// -// for prefix := range t.All() { -// fmt.Println(prefix) -// } -// -// Under the hood, the loop body is passed as a yield function to the iterator. -// If you break or return from the loop, iteration stops early as expected. +// The iteration order is unspecified and may vary between calls; for a stable order, +// use [Lite.AllSorted]. // -// IMPORTANT: Modifying or deleting entries during iteration is not allowed, +// IMPORTANT: Modifying the table during iteration is not allowed, // as this would interfere with the internal traversal and may corrupt or -// prematurely terminate the iteration. If mutation of the table during -// traversal is required use persistent table methods, e.g. -// pl := l -// for pfx := range l.All() { -// if cond(pfx) { -// pl = pl.DeletePersist(pfx) -// } -// } - +// prematurely terminate the iteration. func (l *Lite) All() iter.Seq[netip.Prefix] { - if l == nil { - return func(func(netip.Prefix) bool) {} - } return dropSeq2(l.liteTable.All()) } // All4 is like [Lite.All] but only for the v4 routing table. func (l *Lite) All4() iter.Seq[netip.Prefix] { - if l == nil { - return func(func(netip.Prefix) bool) {} - } return dropSeq2(l.liteTable.All4()) } // All6 is like [Lite.All] but only for the v6 routing table. func (l *Lite) All6() iter.Seq[netip.Prefix] { - if l == nil { - return func(func(netip.Prefix) bool) {} - } return dropSeq2(l.liteTable.All6()) } -// AllSorted returns an iterator over all prefixes in the table, -// ordered in canonical CIDR prefix sort order. -// -// This can be used directly with a for-range loop; -// the Go compiler provides the yield function implicitly. -// -// for prefix := range t.AllSorted() { -// fmt.Println(prefix) -// } -// -// The traversal is stable and predictable across calls. -// Iteration stops early if you break out of the loop. -// -// IMPORTANT: Deleting entries during iteration is not allowed, -// as this would interfere with the internal traversal and may corrupt or -// prematurely terminate the iteration. If mutation of the table during -// traversal is required use persistent table methods. +// AllSorted is like [Lite.All] but the iteration is ordered in canonical +// CIDR prefix sort order. func (l *Lite) AllSorted() iter.Seq[netip.Prefix] { - if l == nil { - return func(func(netip.Prefix) bool) {} - } return dropSeq2(l.liteTable.AllSorted()) } // AllSorted4 is like [Lite.AllSorted] but only for the v4 routing table. func (l *Lite) AllSorted4() iter.Seq[netip.Prefix] { - if l == nil { - return func(func(netip.Prefix) bool) {} - } return dropSeq2(l.liteTable.AllSorted4()) } // AllSorted6 is like [Lite.AllSorted] but only for the v6 routing table. func (l *Lite) AllSorted6() iter.Seq[netip.Prefix] { - if l == nil { - return func(func(netip.Prefix) bool) {} - } return dropSeq2(l.liteTable.AllSorted6()) } @@ -318,9 +263,6 @@ func (l *Lite) AllSorted6() iter.Seq[netip.Prefix] { // The iteration can be stopped early by breaking from the range loop. // Returns an empty iterator if the prefix is invalid. func (l *Lite) Subnets(pfx netip.Prefix) iter.Seq[netip.Prefix] { - if l == nil { - return func(func(netip.Prefix) bool) {} - } return dropSeq2(l.liteTable.Subnets(pfx)) } @@ -345,9 +287,6 @@ func (l *Lite) Subnets(pfx netip.Prefix) iter.Seq[netip.Prefix] { // fmt.Println("Matched covering route:", supernet) // } func (l *Lite) Supernets(pfx netip.Prefix) iter.Seq[netip.Prefix] { - if l == nil { - return func(func(netip.Prefix) bool) {} - } return dropSeq2(l.liteTable.Supernets(pfx)) } @@ -367,25 +306,16 @@ func (l *Lite) Supernets(pfx netip.Prefix) iter.Seq[netip.Prefix] { // It is intentionally not nil-receiver safe: calling with a nil // receiver will panic by design. func (l *Lite) Overlaps(o *Lite) bool { - if o == nil { - return false - } return l.liteTable.Overlaps(&o.liteTable) } // Overlaps4 is like [Lite.Overlaps] but for the v4 routing table only. func (l *Lite) Overlaps4(o *Lite) bool { - if o == nil { - return false - } return l.liteTable.Overlaps4(&o.liteTable) } // Overlaps6 is like [Lite.Overlaps] but for the v6 routing table only. func (l *Lite) Overlaps6(o *Lite) bool { - if o == nil { - return false - } return l.liteTable.Overlaps6(&o.liteTable) } @@ -395,27 +325,18 @@ func (l *Lite) Overlaps6(o *Lite) bool { // // Note: Lite has no payload values, so this only checks structural equality. func (l *Lite) Equal(o *Lite) bool { - if o == nil || l.size4 != o.size4 || l.size6 != o.size6 { - return false - } return l.liteTable.Equal(&o.liteTable) } // DumpList4 dumps the ipv4 tree into a list of roots and their subnets. // It can be used to analyze the tree or build the text or JSON serialization. func (l *Lite) DumpList4() []DumpListNode[struct{}] { - if l == nil { - return nil - } return l.liteTable.DumpList4() } // DumpList6 dumps the ipv6 tree into a list of roots and their subnets. // It can be used to analyze the tree or build custom JSON representation. func (l *Lite) DumpList6() []DumpListNode[struct{}] { - if l == nil { - return nil - } return l.liteTable.DumpList6() } @@ -442,27 +363,18 @@ func (l *Lite) DumpList6() []DumpListNode[struct{}] { // │ └─ 2001:db8::/32 (V) // └─ fe80::/10 (V) func (l *Lite) Fprint(w io.Writer) error { - if l == nil { - return nil - } return l.liteTable.Fprint(w) } // MarshalJSON dumps the table into two sorted lists: for ipv4 and ipv6. // Every root and subnet is an array, not a map, because the order matters. func (l *Lite) MarshalJSON() ([]byte, error) { - if l == nil { - return []byte("null"), nil - } return l.liteTable.MarshalJSON() } // MarshalText implements the [encoding.TextMarshaler] interface, // just a wrapper for [liteTable.Fprint]. func (l *Lite) MarshalText() ([]byte, error) { - if l == nil { - return []byte{}, nil - } return l.liteTable.MarshalText() } @@ -631,7 +543,7 @@ func (l *liteTable[V]) LookupPrefix(pfx netip.Prefix) (val V, exists bool) { // match any address in the provided prefix range. // // This is functionally identical to LookupPrefix but additionally returns the -// matching prefix (lpmPfx) itself along with the value. +// matching LPM prefix itself along with the value. // // This method is slower than LookupPrefix and should only be used if the // matching lpm entry is also required for other reasons. @@ -656,10 +568,10 @@ func (l *liteTable[V]) lookupPrefixLPM(pfx netip.Prefix, withLPM bool) (lpmPfx n pfx = pfx.Masked() ip := pfx.Addr() - bits := pfx.Bits() + pfxLen := pfx.Bits() is4 := ip.Is4() octets := ip.AsSlice() - lastOctetPlusOne, lastBits := nodes.LastOctetPlusOneAndLastBits(pfx) + strideCount, modBits := nodes.DivMod8(pfxLen) n := l.rootNodeByVersion(is4) @@ -672,10 +584,10 @@ func (l *liteTable[V]) lookupPrefixLPM(pfx netip.Prefix, withLPM bool) (lpmPfx n LOOP: // find the last node on the octets path in the trie, for depth, octet = range octets { - depth = depth & nodes.DepthMask // BCE + depth &= nodes.DepthMask // BCE // stepped one past the last stride of interest; back up to last and break - if depth > lastOctetPlusOne { + if depth > strideCount { depth-- break } @@ -696,7 +608,7 @@ LOOP: case *nodes.LeafNode[V]: // reached a path compressed prefix, stop traversing - if kid.Prefix.Bits() > bits || !kid.Prefix.Contains(ip) { + if kid.Prefix.Bits() > pfxLen || !kid.Prefix.Contains(ip) { break LOOP } return kid.Prefix, true @@ -705,7 +617,7 @@ LOOP: // the bits of the fringe are defined by the depth // maybe the LPM isn't needed, saves some cycles fringeBits := (depth + 1) << 3 - if fringeBits > bits { + if fringeBits > pfxLen { break LOOP } @@ -723,7 +635,7 @@ LOOP: // start backtracking, unwind the stack for ; depth >= 0; depth-- { - depth = depth & nodes.DepthMask // BCE + depth &= nodes.DepthMask // BCE n = stack[depth] @@ -732,15 +644,13 @@ LOOP: continue } - // only the lastOctet may have a different prefix len - // all others are just host routes var idx uint8 octet = octets[depth] - // Last “octet” from prefix, update/insert prefix into node. - // Note: For /32 and /128, depth never reaches lastOctetPlusOne (4 or 16), - // so those are handled below via the fringe/leaf path. - if depth == lastOctetPlusOne { - idx = art.PfxToIdx(octet, lastBits) + + // only the final stride may have a different prefix len + // all others are just host routes + if depth == strideCount { + idx = art.PfxToIdx(octet, modBits) } else { idx = art.OctetToIdx(octet) } diff --git a/vendor/github.com/gaissmai/bart/litemethodsgenerated.go b/vendor/github.com/gaissmai/bart/litemethodsgenerated.go index 98375180b5..e417f7be7f 100644 --- a/vendor/github.com/gaissmai/bart/litemethodsgenerated.go +++ b/vendor/github.com/gaissmai/bart/litemethodsgenerated.go @@ -1,6 +1,6 @@ // Code generated from file "commonmethods_tmpl.go"; DO NOT EDIT. -// Copyright (c) 2025 Karl Gaissmaier +// Copyright (c) 2026 Karl Gaissmaier // SPDX-License-Identifier: MIT package bart @@ -266,9 +266,6 @@ func (t *liteTable[V]) ModifyPersist(pfx netip.Prefix, cb func(_ V, ok bool) (_ // Returns an empty iterator if the prefix is invalid. func (t *liteTable[V]) Supernets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } if !pfx.IsValid() { return } @@ -298,9 +295,6 @@ func (t *liteTable[V]) Supernets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] { // Returns an empty iterator if the prefix is invalid. func (t *liteTable[V]) Subnets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } if !pfx.IsValid() { return } @@ -351,15 +345,12 @@ func (t *liteTable[V]) OverlapsPrefix(pfx netip.Prefix) bool { // This is useful for conflict detection, policy enforcement, // or validating mutually exclusive routing domains. func (t *liteTable[V]) Overlaps(o *liteTable[V]) bool { - if o == nil { - return false - } return t.Overlaps4(o) || t.Overlaps6(o) } // Overlaps4 is like [liteTable.Overlaps] but for the v4 routing table only. func (t *liteTable[V]) Overlaps4(o *liteTable[V]) bool { - if o == nil || t.size4 == 0 || o.size4 == 0 { + if t.size4 == 0 || o.size4 == 0 { return false } return t.root4.Overlaps(&o.root4, 0) @@ -367,7 +358,7 @@ func (t *liteTable[V]) Overlaps4(o *liteTable[V]) bool { // Overlaps6 is like [liteTable.Overlaps] but for the v6 routing table only. func (t *liteTable[V]) Overlaps6(o *liteTable[V]) bool { - if o == nil || t.size6 == 0 || o.size6 == 0 { + if t.size6 == 0 || o.size6 == 0 { return false } return t.root6.Overlaps(&o.root6, 0) @@ -380,7 +371,15 @@ func (t *liteTable[V]) Overlaps6(o *liteTable[V]) bool { // This duplicate is shallow-copied by default, but if the value type V implements the // Clone method, the value is deeply cloned before insertion. See also liteTable.Clone. func (t *liteTable[V]) Union(o *liteTable[V]) { - if o == nil || o == t || (o.size4 == 0 && o.size6 == 0) { + // panics on nil receiver + _ = t.size4 + + // panics on nil argument + if o.size4 == 0 && o.size6 == 0 { + return + } + // t is unchanged + if o == t { return } @@ -398,10 +397,17 @@ func (t *liteTable[V]) Union(o *liteTable[V]) { // UnionPersist is similar to [Union] but the receiver isn't modified. // // All nodes touched during union are cloned and a new *liteTable is returned. -// If o is nil or empty, no nodes are touched and the receiver may be +// If o is empty, no nodes are touched and the receiver may be // returned unchanged. func (t *liteTable[V]) UnionPersist(o *liteTable[V]) *liteTable[V] { - if o == nil || o == t || (o.size4 == 0 && o.size6 == 0) { + // panics on nil receiver + _ = t.size4 + + // panics on nil argument + if o.size4 == 0 && o.size6 == 0 { + return t + } + if o == t { return t } @@ -439,21 +445,16 @@ func (t *liteTable[V]) UnionPersist(o *liteTable[V]) *liteTable[V] { // It ensures both trees (IPv4-based and IPv6-based) have the same sizes and // recursively compares their root nodes. // -// Value comparisons use reflect.DeepEqual by default. To avoid the potentially -// expensive reflect.DeepEqual, the payload type V can provide custom equality -// by implementing the following method: +// If V implements an `Equal(V) bool` method, its custom equality logic is used. +// Otherwise, values are compared directly using the == operator. // -// Equal(other V) bool +// Note: If V implements `Equal(V) bool` with a pointer receiver, the Equal +// method should handle nil receivers gracefully. // -// Example: -// -// type MyValue struct { ID int } -// func (v MyValue) Equal(other MyValue) bool { return v.ID == other.ID } -// -// The bart package will automatically detect and use this method via Go's -// structural typing. +// ATTENTION: If V is not comparable at runtime (such as a slice or map without an `Equal` +// method), a runtime panic will occur. func (t *liteTable[V]) Equal(o *liteTable[V]) bool { - if o == nil || t.size4 != o.size4 || t.size6 != o.size6 { + if t.size4 != o.size4 || t.size6 != o.size6 { return false } if o == t { @@ -478,11 +479,10 @@ func (t *liteTable[V]) Equal(o *liteTable[V]) bool { // // The bart package will automatically detect and use this method via Go's // structural typing. +// +// Note: If V implements Clone() V with a pointer receiver, the Clone +// method should handle nil receivers gracefully. func (t *liteTable[V]) Clone() *liteTable[V] { - if t == nil { - return nil - } - c := new(liteTable[V]) cloneFn := value.CloneFnFactory[V]() @@ -513,35 +513,14 @@ func (t *liteTable[V]) Size6() int { // All returns an iterator over all prefix–value pairs in the table. // -// The entries from both IPv4 and IPv6 subtries are yielded using an internal recursive traversal. -// The iteration order is unspecified and may vary between calls; for a stable order, use AllSorted. -// -// You can use All directly in a for-range loop without providing a yield function. -// The Go compiler automatically synthesizes the yield callback for you: -// -// for prefix, value := range t.All() { -// fmt.Println(prefix, value) -// } +// The iteration order is unspecified and may vary between calls; for a stable order, +// use [liteTable.AllSorted]. // -// Under the hood, the loop body is passed as a yield function to the iterator. -// If you break or return from the loop, iteration stops early as expected. -// -// IMPORTANT: Modifying or deleting entries during iteration is not allowed, +// IMPORTANT: Modifying the table during iteration is not allowed, // as this would interfere with the internal traversal and may corrupt or -// prematurely terminate the iteration. If mutation of the table during -// traversal is required use persistent table methods, e.g. -// -// pt := t // shallow copy of t -// for pfx, val := range t.All() { -// if cond(pfx, val) { -// pt = pt.DeletePersist(pfx) -// } -// } +// prematurely terminate the iteration. func (t *liteTable[V]) All() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRec(stridePath{}, 0, true, yield) && t.root6.AllRec(stridePath{}, 0, false, yield) } } @@ -549,9 +528,6 @@ func (t *liteTable[V]) All() iter.Seq2[netip.Prefix, V] { // All4 is like [liteTable.All] but only for the v4 routing table. func (t *liteTable[V]) All4() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRec(stridePath{}, 0, true, yield) } } @@ -559,35 +535,14 @@ func (t *liteTable[V]) All4() iter.Seq2[netip.Prefix, V] { // All6 is like [liteTable.All] but only for the v6 routing table. func (t *liteTable[V]) All6() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root6.AllRec(stridePath{}, 0, false, yield) } } -// AllSorted returns an iterator over all prefix–value pairs in the table, -// ordered in canonical CIDR prefix sort order. -// -// This can be used directly with a for-range loop; -// the Go compiler provides the yield function implicitly: -// -// for prefix, value := range t.AllSorted() { -// fmt.Println(prefix, value) -// } -// -// The traversal is stable and predictable across calls. -// Iteration stops early if you break out of the loop. -// -// IMPORTANT: Deleting entries during iteration is not allowed, -// as this would interfere with the internal traversal and may corrupt or -// prematurely terminate the iteration. If mutation of the table during -// traversal is required use persistent table methods. +// AllSorted is like [liteTable.All] but the iteration is ordered in canonical +// CIDR prefix sort order. func (t *liteTable[V]) AllSorted() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRecSorted(stridePath{}, 0, true, yield) && t.root6.AllRecSorted(stridePath{}, 0, false, yield) } @@ -596,9 +551,6 @@ func (t *liteTable[V]) AllSorted() iter.Seq2[netip.Prefix, V] { // AllSorted4 is like [liteTable.AllSorted] but only for the v4 routing table. func (t *liteTable[V]) AllSorted4() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root4.AllRecSorted(stridePath{}, 0, true, yield) } } @@ -606,9 +558,6 @@ func (t *liteTable[V]) AllSorted4() iter.Seq2[netip.Prefix, V] { // AllSorted6 is like [liteTable.AllSorted] but only for the v6 routing table. func (t *liteTable[V]) AllSorted6() iter.Seq2[netip.Prefix, V] { return func(yield func(netip.Prefix, V) bool) { - if t == nil { - return - } _ = t.root6.AllRecSorted(stridePath{}, 0, false, yield) } } @@ -636,12 +585,9 @@ func (t *liteTable[V]) AllSorted6() iter.Seq2[netip.Prefix, V] { // │ └─ 2001:db8::/32 (V) // └─ fe80::/10 (V) func (t *liteTable[V]) Fprint(w io.Writer) error { - if w == nil { + if w == nil && t != nil { return fmt.Errorf("nil writer") } - if t == nil { - return nil - } // v4 if err := t.fprint(w, true); err != nil { @@ -691,10 +637,6 @@ func (t *liteTable[V]) MarshalText() ([]byte, error) { // MarshalJSON dumps the table into two sorted lists: for ipv4 and ipv6. // Every root and subnet is an array, not a map, because the order matters. func (t *liteTable[V]) MarshalJSON() ([]byte, error) { - if t == nil { - return []byte("null"), nil - } - result := struct { Ipv4 []DumpListNode[V] `json:"ipv4,omitempty"` Ipv6 []DumpListNode[V] `json:"ipv6,omitempty"` @@ -714,18 +656,12 @@ func (t *liteTable[V]) MarshalJSON() ([]byte, error) { // DumpList4 dumps the ipv4 tree into a list of roots and their subnets. // It can be used to analyze the tree or build the text or JSON serialization. func (t *liteTable[V]) DumpList4() []DumpListNode[V] { - if t == nil { - return nil - } return t.dumpListRec(&t.root4, 0, stridePath{}, 0, true) } // DumpList6 dumps the ipv6 tree into a list of roots and their subnets. // It can be used to analyze the tree or build custom JSON representation. func (t *liteTable[V]) DumpList6() []DumpListNode[V] { - if t == nil { - return nil - } return t.dumpListRec(&t.root6, 0, stridePath{}, 0, false) } @@ -775,10 +711,6 @@ func (t *liteTable[V]) dumpString() string { // dump the table structure and all the nodes to w. func (t *liteTable[V]) dump(w io.Writer) { - if t == nil { - return - } - if t.size4 > 0 { stats := t.root4.StatsRec() fmt.Fprintln(w) diff --git a/vendor/github.com/gokrazy/updater/updater.go b/vendor/github.com/gokrazy/updater/updater.go index 9be0d04d47..2221c39363 100644 --- a/vendor/github.com/gokrazy/updater/updater.go +++ b/vendor/github.com/gokrazy/updater/updater.go @@ -16,7 +16,6 @@ import ( "log" "net/http" "net/url" - "slices" "strings" ) @@ -76,7 +75,12 @@ const ( // Supports returns whether the target is known to support the specified update // protocol feature. func (t *Target) Supports(feature ProtocolFeature) bool { - return slices.Contains(t.supports, string(feature)) + for _, f := range t.supports { + if f == string(feature) { + return true + } + } + return false } // StreamTo streams from the specified io.Reader to the specified destination: @@ -118,7 +122,7 @@ func (t *Target) StreamTo(ctx context.Context, dest string, r io.Reader) error { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("unexpected HTTP status code: got %v, want %v (body %q)", resp.Status, want, string(body)) } - remoteHash, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + remoteHash, err := io.ReadAll(resp.Body) if err != nil { return err } @@ -162,7 +166,7 @@ func (t *Target) Put(ctx context.Context, dest string, r io.Reader) error { // Switch changes the active root partition from the currently running root // partition to the currently inactive root partition. func (t *Target) Switch(ctx context.Context) error { - req, err := http.NewRequestWithContext(ctx, "POST", t.baseURL+"update/switch", http.NoBody) + req, err := http.NewRequestWithContext(ctx, "POST", t.baseURL+"update/switch", nil) if err != nil { return err } @@ -181,7 +185,7 @@ func (t *Target) Switch(ctx context.Context) error { // Testboot marks the inactive root partition to be tested upon the next boot, // and made active if the test boot succeeds. func (t *Target) Testboot(ctx context.Context) error { - req, err := http.NewRequestWithContext(ctx, "POST", t.baseURL+"update/testboot", http.NoBody) + req, err := http.NewRequestWithContext(ctx, "POST", t.baseURL+"update/testboot", nil) if err != nil { return err } @@ -245,7 +249,7 @@ func (t *Target) Reboot(ctx context.Context, opts ...RebootOption) error { url += "?" + strings.Join(params, "&") } - req, err := http.NewRequestWithContext(ctx, "POST", url, http.NoBody) + req, err := http.NewRequestWithContext(ctx, "POST", url, nil) if err != nil { return err } @@ -335,7 +339,7 @@ func (t *Target) InstalledEEPROM() EEPROMVersion { } func (t *Target) requestFeatures(ctx context.Context) error { - req, err := http.NewRequestWithContext(ctx, "GET", t.baseURL+"update/features", http.NoBody) + req, err := http.NewRequestWithContext(ctx, "GET", t.baseURL+"update/features", nil) if err != nil { return err } @@ -357,7 +361,7 @@ func (t *Target) requestFeatures(ctx context.Context) error { return fmt.Errorf("unexpected HTTP status code: got %d, want %d (body %q)", got, want, string(body)) } - body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + body, err := io.ReadAll(resp.Body) if err != nil { return err } @@ -399,7 +403,7 @@ type EEPROMVersion struct { } func (t *Target) getEEPROMFromStatus(ctx context.Context) (*EEPROMVersion, error) { - req, err := http.NewRequestWithContext(ctx, "GET", t.baseURL, http.NoBody) + req, err := http.NewRequestWithContext(ctx, "GET", t.baseURL, nil) if err != nil { return nil, err } diff --git a/vendor/github.com/lucasb-eyer/go-colorful/CHANGELOG.md b/vendor/github.com/lucasb-eyer/go-colorful/CHANGELOG.md index a10d3fc8dc..3879f14aa9 100644 --- a/vendor/github.com/lucasb-eyer/go-colorful/CHANGELOG.md +++ b/vendor/github.com/lucasb-eyer/go-colorful/CHANGELOG.md @@ -8,6 +8,12 @@ but only releases after v1.0.3 properly adhere to it. ## [Unreleased] +## [1.4.0] - 2026-03-28 +### Added +- Constructors, decomposers, and blend functions for the CSS Color Level 4 wide-gamut RGB color spaces `DisplayP3`, `A98Rgb`, `ProPhotoRgb`, and `Rec2020` (#81) +- `XyzD50`, `Color.XyzD50`, `D50ToD65`, and `D65ToD50` for working with D50-based color spaces (#81) +- `HexColor` now implements `fmt.Stringer` + ## [1.3.0] - 2025-09-08 ### Added - `BlendLinearRgb` (#50) @@ -19,7 +25,7 @@ but only releases after v1.0.3 properly adhere to it. - Functions BlendOkLab and BlendOkLch (#70) ## Changed -- `Hex()` parsing is much faster (#78) +- `Hex()` parsing is much faster (#78). However, it doesn't tolerate hex codes with alpha anymore (previously ignoring the alpha was unintentional). ### Fixed - Fix bug when doing HSV/HCL blending between a gray color and non-gray color (#60) diff --git a/vendor/github.com/lucasb-eyer/go-colorful/README.md b/vendor/github.com/lucasb-eyer/go-colorful/README.md index b3bb545cf6..1da19f703b 100644 --- a/vendor/github.com/lucasb-eyer/go-colorful/README.md +++ b/vendor/github.com/lucasb-eyer/go-colorful/README.md @@ -34,6 +34,8 @@ Go-Colorful stores colors in RGB and provides methods from converting these to v - **CIE LCh(uv):** Called `LuvLCh` in code, this is a cylindrical transformation of the CIE-L\*u\*v\* color space. Like HCL above: H° is in [0..360], C\* almost in [0..1] and L\* as in CIE-L\*u\*v\*. - **HSLuv:** The better alternative to HSL, see [here](https://www.hsluv.org/) and [here](https://www.kuon.ch/post/2020-03-08-hsluv/). Hue in [0..360], Saturation and Luminance in [0..1]. - **HPLuv:** A variant of HSLuv. The color space is smoother, but only pastel colors can be included. Because the valid colors are limited, it's easy to get invalid Saturation values way above 1.0, indicating the color can't be represented in HPLuv because it's not pastel. +- **Oklab:** A perceptual color space by Björn Ottosson that improves on CIE-L\*a\*b\* with better perceptual uniformity, especially for blue hues. L in [0..1], a and b roughly in [-0.5..0.5]. See [Oklab](https://bottosson.github.io/posts/oklab/). +- **Oklch:** The cylindrical (polar) representation of Oklab, similar to HCL. L in [0..1], C roughly in [0..0.5], h° in [0..360]. For the colorspaces where it makes sense (XYZ, Lab, Luv, HCl), the [D65](http://en.wikipedia.org/wiki/Illuminant_D65) is used as reference white @@ -96,6 +98,8 @@ c = colorful.Xyy(0.219895, 0.221839, 0.190837) c = colorful.Lab(0.507850, 0.040585,-0.370945) c = colorful.Luv(0.507849,-0.194172,-0.567924) c = colorful.Hcl(276.2440, 0.373160, 0.507849) +c = colorful.OkLab(0.577227, -0.021391, -0.104541) +c = colorful.OkLch(0.577227, 0.106707, 258.435657) fmt.Printf("RGB values: %v, %v, %v", c.R, c.G, c.B) ``` @@ -109,6 +113,8 @@ x, y, Y := c.Xyy() l, a, b := c.Lab() l, u, v := c.Luv() h, c, l := c.Hcl() +l, a, b = c.OkLab() +l, c, h = c.OkLch() ``` Note that, because of Go's unfortunate choice of requiring an initial uppercase, @@ -190,7 +196,7 @@ it only if you really know what you're doing. It will eat your cat. Blending is highly connected to distance, since it basically "walks through" the colorspace thus, if the colorspace maps distances well, the walk is "smooth". -Colorful comes with blending functions in RGB, HSV and any of the LAB spaces. +Colorful comes with blending functions in RGB, HSV, Oklab, Oklch, and any of the CIE-LAB spaces. Of course, you'd rather want to use the blending functions of the LAB spaces since these spaces map distances well but, just in case, here is an example showing you how the blendings (`#fdffcc` to `#242a42`) are done in the various spaces: @@ -472,11 +478,12 @@ section above. Who? ==== -This library was developed by Lucas Beyer with contributions from -Bastien Dejean (@baskerville), Phil Kulak (@pkulak), Christian Muehlhaeuser (@muesli), and Scott Pakin (@spakin). - -It is now maintained by makeworld (@makew0rld). +This library was originally developed by Lucas Beyer, with notable +contributions from Bastien Dejean (@baskerville), Phil Kulak (@pkulak), +Christian Muehlhaeuser (@muesli), Scott Pakin (@spakin), and many others. +See the [contributors list](https://github.com/lucasb-eyer/go-colorful/graphs/contributors) for the full roster. +It is currently maintained by makeworld (@makew0rld). ## License diff --git a/vendor/github.com/lucasb-eyer/go-colorful/hexcolor.go b/vendor/github.com/lucasb-eyer/go-colorful/hexcolor.go index ad8b06cc94..26f3573043 100644 --- a/vendor/github.com/lucasb-eyer/go-colorful/hexcolor.go +++ b/vendor/github.com/lucasb-eyer/go-colorful/hexcolor.go @@ -34,6 +34,10 @@ func (hc *HexColor) Value() (driver.Value, error) { return Color(*hc).Hex(), nil } +func (hc HexColor) String() string { + return Color(hc).Hex() +} + func (e errUnsupportedType) Error() string { return fmt.Sprintf("unsupported type: got %v, want a %s", e.got, e.want) } diff --git a/vendor/github.com/lucasb-eyer/go-colorful/widegamut.go b/vendor/github.com/lucasb-eyer/go-colorful/widegamut.go new file mode 100644 index 0000000000..6805a2b96f --- /dev/null +++ b/vendor/github.com/lucasb-eyer/go-colorful/widegamut.go @@ -0,0 +1,290 @@ +package colorful + +import "math" + +// Wide-gamut RGB color spaces from CSS Color Level 4. +// https://www.w3.org/TR/css-color-4/#color-conversion-code + +/// Bradford /// +//////////////// +// Bradford chromatic adaptation between D50 and D65 illuminants. + +func D50ToD65(x, y, z float64) (xo, yo, zo float64) { + xo = 0.9555766*x - 0.0230393*y + 0.0631636*z + yo = -0.0282895*x + 1.0099416*y + 0.0210077*z + zo = 0.0122982*x - 0.0204830*y + 1.3299098*z + return +} + +func D65ToD50(x, y, z float64) (xo, yo, zo float64) { + xo = 1.0479298208405488*x + 0.022946793341019088*y - 0.05019222954313557*z + yo = 0.029627815688159344*x + 0.990434484573249*y - 0.01707382502938514*z + zo = -0.009243058152591178*x + 0.015055144896577895*y + 0.7518742899580008*z + return +} + +/// XYZ D50 /// +/////////////// + +func XyzD50(x, y, z float64) Color { + return Xyz(D50ToD65(x, y, z)) +} + +func (col Color) XyzD50() (x, y, z float64) { + return D65ToD50(col.Xyz()) +} + +/// Display P3 /// +////////////////// +// Uses the sRGB transfer function with DCI-P3 primaries. + +func DisplayP3ToLinearRgb(r, g, b float64) (rl, gl, bl float64) { + rl = linearize(r) + gl = linearize(g) + bl = linearize(b) + return +} + +func LinearDisplayP3ToXyz(r, g, b float64) (x, y, z float64) { + x = 0.4865709486482162*r + 0.26566769316909306*g + 0.1982172852343625*b + y = 0.2289745640697488*r + 0.6917385218365064*g + 0.079286914093745*b + z = 0.04511338185890264*g + 1.043944368900976*b + return +} + +func XyzToLinearDisplayP3(x, y, z float64) (r, g, b float64) { + r = 2.493496911941425*x - 0.9313836179191239*y - 0.40271078445071684*z + g = -0.8294889695615747*x + 1.7626640603183463*y + 0.023624685841943577*z + b = 0.035845830243784335*x - 0.07617238926804182*y + 0.9568845240076872*z + return +} + +func DisplayP3(r, g, b float64) Color { + rl, gl, bl := DisplayP3ToLinearRgb(r, g, b) + x, y, z := LinearDisplayP3ToXyz(rl, gl, bl) + return Xyz(x, y, z) +} + +func (col Color) DisplayP3() (r, g, b float64) { + x, y, z := col.Xyz() + rl, gl, bl := XyzToLinearDisplayP3(x, y, z) + r = delinearize(rl) + g = delinearize(gl) + b = delinearize(bl) + return +} + +// BlendDisplayP3 blends two colors in the Display P3 color-space. +// t == 0 results in c1, t == 1 results in c2 +func (c1 Color) BlendDisplayP3(c2 Color, t float64) Color { + r1, g1, b1 := c1.DisplayP3() + r2, g2, b2 := c2.DisplayP3() + return DisplayP3( + r1+t*(r2-r1), + g1+t*(g2-g1), + b1+t*(b2-b1)) +} + +/// A98 RGB /// +/////////////// +// Adobe RGB (1998) color space. + +func linearizeA98(v float64) float64 { + sign := 1.0 + if v < 0 { + sign = -1.0 + v = -v + } + return sign * math.Pow(v, 563.0/256.0) +} + +func delinearizeA98(v float64) float64 { + sign := 1.0 + if v < 0 { + sign = -1.0 + v = -v + } + return sign * math.Pow(v, 256.0/563.0) +} + +func A98RgbToLinearRgb(r, g, b float64) (rl, gl, bl float64) { + rl = linearizeA98(r) + gl = linearizeA98(g) + bl = linearizeA98(b) + return +} + +func LinearA98RgbToXyz(r, g, b float64) (x, y, z float64) { + x = 0.5766690429101305*r + 0.1855582379065463*g + 0.1882286462349947*b + y = 0.29734497525053605*r + 0.6273635662554661*g + 0.07529145849399788*b + z = 0.02703136138641234*r + 0.07068885253582723*g + 0.9913375368376388*b + return +} + +func XyzToLinearA98Rgb(x, y, z float64) (r, g, b float64) { + r = 2.0415879038107327*x - 0.5650069742788597*y - 0.34473135077832956*z + g = -0.9692436362808795*x + 1.8759675015077202*y + 0.04155505740717559*z + b = 0.013444280632031142*x - 0.11836239223101838*y + 1.0151749943912054*z + return +} + +func A98Rgb(r, g, b float64) Color { + rl, gl, bl := A98RgbToLinearRgb(r, g, b) + x, y, z := LinearA98RgbToXyz(rl, gl, bl) + return Xyz(x, y, z) +} + +func (col Color) A98Rgb() (r, g, b float64) { + x, y, z := col.Xyz() + rl, gl, bl := XyzToLinearA98Rgb(x, y, z) + r = delinearizeA98(rl) + g = delinearizeA98(gl) + b = delinearizeA98(bl) + return +} + +// BlendA98Rgb blends two colors in the A98 RGB color-space. +// t == 0 results in c1, t == 1 results in c2 +func (c1 Color) BlendA98Rgb(c2 Color, t float64) Color { + r1, g1, b1 := c1.A98Rgb() + r2, g2, b2 := c2.A98Rgb() + return A98Rgb( + r1+t*(r2-r1), + g1+t*(g2-g1), + b1+t*(b2-b1)) +} + +/// ProPhoto RGB /// +//////////////////// +// ProPhoto RGB (ROMM RGB) uses D50 illuminant. + +func linearizeProPhoto(v float64) float64 { + if v <= 16.0/512.0 { + return v / 16.0 + } + return math.Pow(v, 1.8) +} + +func delinearizeProPhoto(v float64) float64 { + if v < 1.0/512.0 { + return 16.0 * v + } + return math.Pow(v, 1.0/1.8) +} + +func ProPhotoRgbToLinearRgb(r, g, b float64) (rl, gl, bl float64) { + rl = linearizeProPhoto(r) + gl = linearizeProPhoto(g) + bl = linearizeProPhoto(b) + return +} + +func LinearProPhotoRgbToXyzD50(r, g, b float64) (x, y, z float64) { + x = 0.7977604896723027*r + 0.13518583717574031*g + 0.0313493495815248*b + y = 0.2880711282292934*r + 0.7118432178101014*g + 0.00008565396060525902*b + z = 0.8251046025104602 * b + return +} + +func XyzD50ToLinearProPhotoRgb(x, y, z float64) (r, g, b float64) { + r = 1.3457989731028281*x - 0.25558010007997534*y - 0.05110628506753401*z + g = -0.5446224939028347*x + 1.5082327413132781*y + 0.02053603239147973*z + b = 1.2119675456389454 * z + return +} + +func ProPhotoRgb(r, g, b float64) Color { + rl, gl, bl := ProPhotoRgbToLinearRgb(r, g, b) + x, y, z := LinearProPhotoRgbToXyzD50(rl, gl, bl) + return XyzD50(x, y, z) +} + +func (col Color) ProPhotoRgb() (r, g, b float64) { + x, y, z := col.XyzD50() + rl, gl, bl := XyzD50ToLinearProPhotoRgb(x, y, z) + r = delinearizeProPhoto(rl) + g = delinearizeProPhoto(gl) + b = delinearizeProPhoto(bl) + return +} + +// BlendProPhotoRgb blends two colors in the ProPhoto RGB color-space. +// t == 0 results in c1, t == 1 results in c2 +func (c1 Color) BlendProPhotoRgb(c2 Color, t float64) Color { + r1, g1, b1 := c1.ProPhotoRgb() + r2, g2, b2 := c2.ProPhotoRgb() + return ProPhotoRgb( + r1+t*(r2-r1), + g1+t*(g2-g1), + b1+t*(b2-b1)) +} + +/// Rec. 2020 /// +///////////////// +// ITU-R BT.2020 color space. + +const ( + rec2020Alpha = 1.09929682680944 + rec2020Beta = 0.018053968510807 +) + +func linearizeRec2020(v float64) float64 { + if v < rec2020Beta*4.5 { + return v / 4.5 + } + return math.Pow((v+rec2020Alpha-1)/rec2020Alpha, 1.0/0.45) +} + +func delinearizeRec2020(v float64) float64 { + if v < rec2020Beta { + return 4.5 * v + } + return rec2020Alpha*math.Pow(v, 0.45) - (rec2020Alpha - 1) +} + +func Rec2020ToLinearRgb(r, g, b float64) (rl, gl, bl float64) { + rl = linearizeRec2020(r) + gl = linearizeRec2020(g) + bl = linearizeRec2020(b) + return +} + +func LinearRec2020ToXyz(r, g, b float64) (x, y, z float64) { + x = 0.6369580483012914*r + 0.14461690358620832*g + 0.1688809751641721*b + y = 0.2627002120112671*r + 0.6779980715188708*g + 0.05930171646986196*b + z = 0.028072693049087428*g + 1.0609850577107909*b + return +} + +func XyzToLinearRec2020(x, y, z float64) (r, g, b float64) { + r = 1.7166511879712674*x - 0.35567078377639233*y - 0.25336628137365974*z + g = -0.666684351832489*x + 1.616481236634939*y + 0.0157685458139402*z + b = 0.017639857445310783*x - 0.042770613257808524*y + 0.9421031212354738*z + return +} + +func Rec2020(r, g, b float64) Color { + rl, gl, bl := Rec2020ToLinearRgb(r, g, b) + x, y, z := LinearRec2020ToXyz(rl, gl, bl) + return Xyz(x, y, z) +} + +func (col Color) Rec2020() (r, g, b float64) { + x, y, z := col.Xyz() + rl, gl, bl := XyzToLinearRec2020(x, y, z) + r = delinearizeRec2020(rl) + g = delinearizeRec2020(gl) + b = delinearizeRec2020(bl) + return +} + +// BlendRec2020 blends two colors in the Rec. 2020 color-space. +// t == 0 results in c1, t == 1 results in c2 +func (c1 Color) BlendRec2020(c2 Color, t float64) Color { + r1, g1, b1 := c1.Rec2020() + r2, g2, b2 := c2.Rec2020() + return Rec2020( + r1+t*(r2-r1), + g1+t*(g2-g1), + b1+t*(b2-b1)) +} diff --git a/vendor/github.com/mattn/go-runewidth/SECURITY.md b/vendor/github.com/mattn/go-runewidth/SECURITY.md new file mode 100644 index 0000000000..a6898ee701 --- /dev/null +++ b/vendor/github.com/mattn/go-runewidth/SECURITY.md @@ -0,0 +1,25 @@ +# Security Policy + +## Supported Versions + +The following versions of go-runewidth are currently supported with +security updates. + +| Version | Supported | +| -------- | ------------------ | +| 0.0.23 | :white_check_mark: | +| < 0.0.23 | :x: | + +## Reporting a Vulnerability + +If you discover a security vulnerability in go-runewidth, please report it +privately via GitHub's "Report a vulnerability" feature on the Security tab +of the repository (https://github.com/mattn/go-runewidth/security), or by +emailing the maintainer at mattn.jp@gmail.com. + +Please include a description of the issue, reproduction steps, and the +affected version. You can expect an initial response within one week. If +the vulnerability is accepted, a fix will be prepared and a new release +will be published; you will be credited in the release notes unless you +request otherwise. If the report is declined, you will receive an +explanation of the reasoning. diff --git a/vendor/github.com/mattn/go-runewidth/runewidth.go b/vendor/github.com/mattn/go-runewidth/runewidth.go index a8c9c1fb10..6b958fdd49 100644 --- a/vendor/github.com/mattn/go-runewidth/runewidth.go +++ b/vendor/github.com/mattn/go-runewidth/runewidth.go @@ -2,7 +2,9 @@ package runewidth import ( "os" + "sort" "strings" + "unicode/utf8" "github.com/clipperhouse/uax29/v2/graphemes" ) @@ -23,10 +25,54 @@ var ( } ) +var ( + zerowidth table // combining + nonprint merged for faster zero-width lookup + widewidth table // ambiguous + doublewidth merged for EA path + eastAsianWidth widthTable + eastAsianWidth0 [0x300]byte +) + func init() { + zerowidth = mergeIntervals(combining, nonprint) + widewidth = mergeIntervals(ambiguous, doublewidth) + eastAsianWidth = makeWidthTable(zerowidth, widewidth) + for r := range eastAsianWidth0 { + eastAsianWidth0[r] = byte(runeWidthEastAsian(rune(r))) + } handleEnv() } +func mergeIntervals(t1, t2 table) table { + merged := make(table, 0, len(t1)+len(t2)) + i, j := 0, 0 + for i < len(t1) && j < len(t2) { + if t1[i].first <= t2[j].first { + merged = append(merged, t1[i]) + i++ + } else { + merged = append(merged, t2[j]) + j++ + } + } + merged = append(merged, t1[i:]...) + merged = append(merged, t2[j:]...) + if len(merged) == 0 { + return merged + } + result := merged[:1] + for _, iv := range merged[1:] { + last := &result[len(result)-1] + if iv.first <= last.last+1 { + if iv.last > last.last { + last.last = iv.last + } + } else { + result = append(result, iv) + } + } + return result +} + func handleEnv() { env := os.Getenv("RUNEWIDTH_EASTASIAN") if env == "" { @@ -51,15 +97,14 @@ type interval struct { type table []interval -func inTables(r rune, ts ...table) bool { - for _, t := range ts { - if inTable(r, t) { - return true - } - } - return false +type widthInterval struct { + first rune + last rune + width byte } +type widthTable []widthInterval + func inTable(r rune, t table) bool { if r < t[0].first { return false @@ -86,6 +131,71 @@ func inTable(r rune, t table) bool { return false } +func makeWidthTable(zero, two table) widthTable { + wt := make(widthTable, 0, len(zero)+len(two)) + zi := 0 + for _, iv := range two { + start := iv.first + for zi < len(zero) && zero[zi].last < start { + zi++ + } + for i := zi; i < len(zero) && zero[i].first <= iv.last; i++ { + if start < zero[i].first { + wt = append(wt, widthInterval{start, zero[i].first - 1, 2}) + } + if start <= zero[i].last { + start = zero[i].last + 1 + } + if start > iv.last { + break + } + } + if start <= iv.last { + wt = append(wt, widthInterval{start, iv.last, 2}) + } + } + for _, iv := range zero { + wt = append(wt, widthInterval{iv.first, iv.last, 0}) + } + sort.Slice(wt, func(i, j int) bool { + return wt[i].first < wt[j].first + }) + return wt +} + +func inWidthTable(r rune, t widthTable) (int, bool) { + if r < t[0].first { + return 0, false + } + if r > t[len(t)-1].last { + return 0, false + } + + bot := 0 + top := len(t) - 1 + for top >= bot { + mid := (bot + top) >> 1 + + switch { + case t[mid].last < r: + bot = mid + 1 + case t[mid].first > r: + top = mid - 1 + default: + return int(t[mid].width), true + } + } + + return 0, false +} + +func runeWidthEastAsian(r rune) int { + if w, ok := inWidthTable(r, eastAsianWidth); ok { + return w + } + return 1 +} + var private = table{ {0x00E000, 0x00F8FF}, {0x0F0000, 0x0FFFFD}, {0x100000, 0x10FFFD}, } @@ -123,36 +233,35 @@ func (c *Condition) RuneWidth(r rune) int { } // optimized version, verified by TestRuneWidthChecksums() if !c.EastAsianWidth { - switch { - case r < 0x20: - return 0 - case (r >= 0x7F && r <= 0x9F) || r == 0xAD: // nonprint + if r < 0x20 { return 0 - case r < 0x300: - return 1 - case inTable(r, narrow): - return 1 - case inTables(r, nonprint, combining): + } + if (r >= 0x7F && r <= 0x9F) || r == 0xAD { // nonprint return 0 - case inTable(r, doublewidth): - return 2 - default: + } + if r < 0x300 { return 1 } - } else { switch { - case inTables(r, nonprint, combining): + case inTable(r, zerowidth): return 0 - case inTable(r, narrow): - return 1 - case inTables(r, ambiguous, doublewidth): - return 2 - case !c.StrictEmojiNeutral && inTables(r, ambiguous, emoji, narrow): + case inTable(r, doublewidth): return 2 default: return 1 } } + + if r < 0x300 { + return int(eastAsianWidth0[r]) + } + if w, ok := inWidthTable(r, eastAsianWidth); ok { + return w + } + if !c.StrictEmojiNeutral && inTable(r, emoji) { + return 2 + } + return 1 } // CreateLUT will create an in-memory lookup table of 557056 bytes for faster operation. @@ -178,6 +287,33 @@ func (c *Condition) CreateLUT() { // StringWidth return width as you can see func (c *Condition) StringWidth(s string) (width int) { + if len(s) == 1 { + b := s[0] + if b < 0x20 || b == 0x7F { + return 0 + } + return 1 + } + if len(s) > 0 && len(s) <= utf8.UTFMax { + r, size := utf8.DecodeRuneInString(s) + if size == len(s) { + return c.RuneWidth(r) + } + } + // ASCII fast path: no grapheme clustering needed for pure ASCII + for i := 0; i < len(s); i++ { + b := s[i] + if b >= 0x80 { + goto graphemes + } + if b >= 0x20 && b != 0x7F { + width++ + } + } + return + +graphemes: + width = 0 g := graphemes.FromString(s) for g.Next() { var chWidth int @@ -257,24 +393,25 @@ func (c *Condition) TruncateLeft(s string, w int, prefix string) string { // Wrap return string wrapped with w cells func (c *Condition) Wrap(s string, w int) string { width := 0 - out := "" + var out strings.Builder + out.Grow(len(s) + len(s)/w + 1) for _, r := range s { cw := c.RuneWidth(r) if r == '\n' { - out += string(r) + out.WriteRune(r) width = 0 continue } else if width+cw > w { - out += "\n" + out.WriteByte('\n') width = 0 - out += string(r) + out.WriteRune(r) width += cw continue } - out += string(r) + out.WriteRune(r) width += cw } - return out + return out.String() } // FillLeft return string filled in left by spaces in w cells @@ -313,7 +450,7 @@ func RuneWidth(r rune) int { // IsAmbiguousWidth returns whether is ambiguous width or not. func IsAmbiguousWidth(r rune) bool { - return inTables(r, private, ambiguous) + return inTable(r, private) || inTable(r, ambiguous) } // IsCombiningWidth returns whether is combining width or not. diff --git a/vendor/github.com/mdlayher/genetlink/.golangci.yml b/vendor/github.com/mdlayher/genetlink/.golangci.yml new file mode 100644 index 0000000000..1f10166aab --- /dev/null +++ b/vendor/github.com/mdlayher/genetlink/.golangci.yml @@ -0,0 +1,16 @@ +version: "2" +linters: + enable: + - misspell + - modernize + - revive + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling +formatters: + exclusions: + generated: lax diff --git a/vendor/github.com/mdlayher/genetlink/family_linux.go b/vendor/github.com/mdlayher/genetlink/family_linux.go index 8c496f5b72..98273d6205 100644 --- a/vendor/github.com/mdlayher/genetlink/family_linux.go +++ b/vendor/github.com/mdlayher/genetlink/family_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux package genetlink diff --git a/vendor/github.com/mdlayher/netlink/CHANGELOG.md b/vendor/github.com/mdlayher/netlink/CHANGELOG.md index eac8e924c1..64589d14f0 100644 --- a/vendor/github.com/mdlayher/netlink/CHANGELOG.md +++ b/vendor/github.com/mdlayher/netlink/CHANGELOG.md @@ -1,5 +1,25 @@ # CHANGELOG +## 1.9.0 + +**This is the first release of package netlink that only supports Go 1.24+.** + +- [Improvement]: updated dependencies and Go version to 1.24; tests now run on + Go 1.24-1.26. +- [New API]: [#236](https://github.com/mdlayher/netlink/pull/236) introduced + `Sequence` field to `OpError` for better error correlation. +- [New API]: [#237](https://github.com/mdlayher/netlink/pull/237) added + `netlink.Conn.PID` method to retrieve the connection's PID, also known as port + ID. +- [Improvement]: [#228](https://github.com/mdlayher/netlink/pull/228) fixed + skipping of specific tests on big-endian hosts. + +## 1.8.0 + +- [Improvement]: Updated dependencies, test with Go 1.23 to 1.25. +- [Improvement]: Use Go 1.21's binary.NativeEndian (#220) +- [Improvement]: Expose socket ReadBuffer & WriteBuffer functions (#223) + ## v1.7.2 - [Improvement]: updated dependencies, test with Go 1.20. @@ -29,7 +49,7 @@ Users on older versions of Go must use v1.6.2.** ## v1.6.1 -- [Deprecation] [commit](https://github.com/mdlayher/netlink/commit/d1b69ea8697d721415c259ef8513ab699c6d3e96): +- [Deprecation] [commit](https://github.com/mdlayher/netlink/commit/d1b69ea8697d721415c259ef8513ab699c6d3e96): the `netlink.Socket` interface has been marked as deprecated. The abstraction is awkward to use properly and disables much of the functionality of the Conn type when the basic interface is implemented. Do not use. diff --git a/vendor/github.com/mdlayher/netlink/LICENSE.md b/vendor/github.com/mdlayher/netlink/LICENSE.md index 12f7105853..1eea1a5346 100644 --- a/vendor/github.com/mdlayher/netlink/LICENSE.md +++ b/vendor/github.com/mdlayher/netlink/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -Copyright (C) 2016-2022 Matt Layher +Copyright (C) 2016-2026 Matt Layher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/vendor/github.com/mdlayher/netlink/conn.go b/vendor/github.com/mdlayher/netlink/conn.go index 7138665bce..a88efee92e 100644 --- a/vendor/github.com/mdlayher/netlink/conn.go +++ b/vendor/github.com/mdlayher/netlink/conn.go @@ -434,17 +434,20 @@ func (c *Conn) SetOption(option ConnOption, enable bool) error { return newOpError("set-option", conn.SetOption(option, enable)) } -// A bufferSetter is a Socket that supports setting connection buffer sizes. -type bufferSetter interface { +// A bufferedSocket is a Socket that supports getting & setting connection +// buffer sizes. +type bufferedSocket interface { Socket SetReadBuffer(bytes int) error SetWriteBuffer(bytes int) error + ReadBuffer() (int, error) + WriteBuffer() (int, error) } // SetReadBuffer sets the size of the operating system's receive buffer // associated with the Conn. func (c *Conn) SetReadBuffer(bytes int) error { - conn, ok := c.sock.(bufferSetter) + conn, ok := c.sock.(bufferedSocket) if !ok { return notSupported("set-read-buffer") } @@ -455,7 +458,7 @@ func (c *Conn) SetReadBuffer(bytes int) error { // SetWriteBuffer sets the size of the operating system's transmit buffer // associated with the Conn. func (c *Conn) SetWriteBuffer(bytes int) error { - conn, ok := c.sock.(bufferSetter) + conn, ok := c.sock.(bufferedSocket) if !ok { return notSupported("set-write-buffer") } @@ -463,6 +466,44 @@ func (c *Conn) SetWriteBuffer(bytes int) error { return newOpError("set-write-buffer", conn.SetWriteBuffer(bytes)) } +// ReadBuffer reads the size of the operating system's receive buffer +// associated with the Conn. +func (c *Conn) ReadBuffer() (int, error) { + conn, ok := c.sock.(bufferedSocket) + if !ok { + return 0, notSupported("get-read-buffer") + } + + buff, err := conn.ReadBuffer() + if err != nil { + return 0, newOpError("get-read-buffer", err) + } + return buff, nil +} + +// WriteBuffer reads the size of the operating system's transmit buffer +// associated with the Conn. +func (c *Conn) WriteBuffer() (int, error) { + conn, ok := c.sock.(bufferedSocket) + if !ok { + return 0, notSupported("get-write-buffer") + } + + buff, err := conn.WriteBuffer() + if err != nil { + return 0, newOpError("get-write-buffer", err) + } + + return buff, nil +} + +// PID returns the PID associated with the Conn. It is also known as +// the port ID in netlink terminology. +// https://docs.kernel.org/userspace-api/netlink/intro.html#nlmsg-pid +func (c *Conn) PID() uint32 { + return c.pid +} + // A syscallConner is a Socket that supports syscall.Conn. type syscallConner interface { Socket diff --git a/vendor/github.com/mdlayher/netlink/conn_linux.go b/vendor/github.com/mdlayher/netlink/conn_linux.go index 4af18c99a3..1eab74af5f 100644 --- a/vendor/github.com/mdlayher/netlink/conn_linux.go +++ b/vendor/github.com/mdlayher/netlink/conn_linux.go @@ -212,6 +212,14 @@ func (c *conn) SetReadBuffer(bytes int) error { return c.s.SetReadBuffer(bytes) // associated with the Conn. func (c *conn) SetWriteBuffer(bytes int) error { return c.s.SetWriteBuffer(bytes) } +// ReadBuffer reads the size of the operating system's receive buffer +// associated with the Conn. +func (c *conn) ReadBuffer() (int, error) { return c.s.ReadBuffer() } + +// WriteBuffer reads the size of the operating system's transmit buffer +// associated with the Conn. +func (c *conn) WriteBuffer() (int, error) { return c.s.WriteBuffer() } + // SyscallConn returns a raw network connection. func (c *conn) SyscallConn() (syscall.RawConn, error) { return c.s.SyscallConn() } diff --git a/vendor/github.com/mdlayher/netlink/errors.go b/vendor/github.com/mdlayher/netlink/errors.go index 8c0fce7e4d..e1fe0ab349 100644 --- a/vendor/github.com/mdlayher/netlink/errors.go +++ b/vendor/github.com/mdlayher/netlink/errors.go @@ -69,6 +69,11 @@ type OpError struct { // is not set, both of these fields will be empty. Message string Offset int + + // Sequence is the netlink message sequence number associated with the + // error. This field is only populated if the error was produced as the + // result of a netlink message; otherwise, it will be zero. + Sequence uint32 } // newOpError is a small wrapper for creating an OpError. As a convenience, it diff --git a/vendor/github.com/mdlayher/netlink/message.go b/vendor/github.com/mdlayher/netlink/message.go index 57277165a3..6cfefd59c8 100644 --- a/vendor/github.com/mdlayher/netlink/message.go +++ b/vendor/github.com/mdlayher/netlink/message.go @@ -293,7 +293,8 @@ func checkMessage(m Message) error { // Error code is a negative integer, convert it into an OS-specific raw // system call error, but do not wrap with os.NewSyscallError to signify // that this error was produced by a netlink message; not a system call. - Err: newError(-1 * int(c)), + Err: newError(-1 * int(c)), + Sequence: m.Header.Sequence, } // TODO(mdlayher): investigate the Capped flag. diff --git a/vendor/github.com/mdlayher/socket/.golangci.yml b/vendor/github.com/mdlayher/socket/.golangci.yml new file mode 100644 index 0000000000..1f10166aab --- /dev/null +++ b/vendor/github.com/mdlayher/socket/.golangci.yml @@ -0,0 +1,16 @@ +version: "2" +linters: + enable: + - misspell + - modernize + - revive + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling +formatters: + exclusions: + generated: lax diff --git a/vendor/github.com/mdlayher/socket/CHANGELOG.md b/vendor/github.com/mdlayher/socket/CHANGELOG.md index b83418532d..b94818e7ca 100644 --- a/vendor/github.com/mdlayher/socket/CHANGELOG.md +++ b/vendor/github.com/mdlayher/socket/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## v0.5.2 + +- [Improvement]: Bump build to Go 1.23.0. Note this is required for the latest + Go extended library versions. + ## v0.5.1 - [Improvement]: revert `go.mod` to Go 1.20 to [resolve an issue around Go diff --git a/vendor/github.com/mdlayher/socket/accept4.go b/vendor/github.com/mdlayher/socket/accept4.go index e1016b2063..48be812ed8 100644 --- a/vendor/github.com/mdlayher/socket/accept4.go +++ b/vendor/github.com/mdlayher/socket/accept4.go @@ -1,5 +1,4 @@ //go:build dragonfly || freebsd || illumos || linux -// +build dragonfly freebsd illumos linux package socket diff --git a/vendor/github.com/mdlayher/socket/conn.go b/vendor/github.com/mdlayher/socket/conn.go index 5be502f5a2..6057a2e0a8 100644 --- a/vendor/github.com/mdlayher/socket/conn.go +++ b/vendor/github.com/mdlayher/socket/conn.go @@ -120,7 +120,7 @@ func (c *Conn) ReadContext(ctx context.Context, b []byte) (int, error) { b = b[:maxRW] } - n, err := readT(c, ctx, "read", func(fd int) (int, error) { + n, err := readT(ctx, c, "read", func(fd int) (int, error) { return unix.Read(fd, b) }) if n == 0 && err == nil && c.facts.zeroReadIsEOF { @@ -142,12 +142,12 @@ func (c *Conn) WriteContext(ctx context.Context, b []byte) (int, error) { ) doErr := c.write(ctx, "write", func(fd int) error { - max := len(b) - if c.facts.isStream && max-nn > maxRW { - max = nn + maxRW + lenb := len(b) + if c.facts.isStream && lenb-nn > maxRW { + lenb = nn + maxRW } - n, err = unix.Write(fd, b[nn:max]) + n, err = unix.Write(fd, b[nn:lenb]) if n > 0 { nn += n } @@ -418,7 +418,7 @@ func (c *Conn) Accept(ctx context.Context, flags int) (*Conn, unix.Sockaddr, err sa unix.Sockaddr } - r, err := readT(c, ctx, sysAccept, func(fd int) (ret, error) { + r, err := readT(ctx, c, sysAccept, func(fd int) (ret, error) { // Either accept(2) or accept4(2) depending on the OS. nfd, sa, err := accept(fd, flags|socketFlags) return ret{nfd, sa}, err @@ -464,7 +464,7 @@ func (c *Conn) Connect(ctx context.Context, sa unix.Sockaddr) (unix.Sockaddr, er // have an explicit WaitWrite call like internal/poll does, so we have // to wait until the runtime calls the closure again to indicate we can // write. - progress uint32 + progress atomic.Uint32 // Capture closure sockaddr and error. rsa unix.Sockaddr @@ -472,7 +472,7 @@ func (c *Conn) Connect(ctx context.Context, sa unix.Sockaddr) (unix.Sockaddr, er ) doErr := c.write(ctx, op, func(fd int) error { - if atomic.AddUint32(&progress, 1) == 1 { + if progress.Add(1) == 1 { // First call: initiate connect. return unix.Connect(fd, sa) } @@ -569,7 +569,7 @@ func (c *Conn) Recvmsg(ctx context.Context, p, oob []byte, flags int) (int, int, from unix.Sockaddr } - r, err := readT(c, ctx, "recvmsg", func(fd int) (ret, error) { + r, err := readT(ctx, c, "recvmsg", func(fd int) (ret, error) { n, oobn, recvflags, from, err := unix.Recvmsg(fd, p, oob, flags) return ret{n, oobn, recvflags, from}, err }) @@ -587,7 +587,7 @@ func (c *Conn) Recvfrom(ctx context.Context, p []byte, flags int) (int, unix.Soc addr unix.Sockaddr } - out, err := readT(c, ctx, "recvfrom", func(fd int) (ret, error) { + out, err := readT(ctx, c, "recvfrom", func(fd int) (ret, error) { n, addr, err := unix.Recvfrom(fd, p, flags) return ret{n, addr}, err }) @@ -600,7 +600,7 @@ func (c *Conn) Recvfrom(ctx context.Context, p []byte, flags int) (int, unix.Soc // Sendmsg wraps sendmsg(2). func (c *Conn) Sendmsg(ctx context.Context, p, oob []byte, to unix.Sockaddr, flags int) (int, error) { - return writeT(c, ctx, "sendmsg", func(fd int) (int, error) { + return writeT(ctx, c, "sendmsg", func(fd int) (int, error) { return unix.SendmsgN(fd, p, oob, to, flags) }) } @@ -645,7 +645,7 @@ func (c *Conn) Shutdown(how int) error { // read wraps readT to execute a function and capture its error result. This is // a convenience wrapper for functions which don't return any extra values. func (c *Conn) read(ctx context.Context, op string, f func(fd int) error) error { - _, err := readT(c, ctx, op, func(fd int) (struct{}, error) { + _, err := readT(ctx, c, op, func(fd int) (struct{}, error) { return struct{}{}, f(fd) }) return err @@ -654,7 +654,7 @@ func (c *Conn) read(ctx context.Context, op string, f func(fd int) error) error // write executes f, a write function, against the associated file descriptor. // op is used to create an *os.SyscallError if the file descriptor is closed. func (c *Conn) write(ctx context.Context, op string, f func(fd int) error) error { - _, err := writeT(c, ctx, op, func(fd int) (struct{}, error) { + _, err := writeT(ctx, c, op, func(fd int) (struct{}, error) { return struct{}{}, f(fd) }) return err @@ -662,7 +662,7 @@ func (c *Conn) write(ctx context.Context, op string, f func(fd int) error) error // readT executes c.rc.Read for op using the input function, returning a newly // allocated result T. -func readT[T any](c *Conn, ctx context.Context, op string, f func(fd int) (T, error)) (T, error) { +func readT[T any](ctx context.Context, c *Conn, op string, f func(fd int) (T, error)) (T, error) { return rwT(c, rwContext[T]{ Context: ctx, Type: read, @@ -673,7 +673,7 @@ func readT[T any](c *Conn, ctx context.Context, op string, f func(fd int) (T, er // writeT executes c.rc.Write for op using the input function, returning a newly // allocated result T. -func writeT[T any](c *Conn, ctx context.Context, op string, f func(fd int) (T, error)) (T, error) { +func writeT[T any](ctx context.Context, c *Conn, op string, f func(fd int) (T, error)) (T, error) { return rwT(c, rwContext[T]{ Context: ctx, Type: write, @@ -771,9 +771,7 @@ func rwT[T any](c *Conn, rw rwContext[T]) (T, error) { // // TODO(mdlayher): is it possible to detect a background context vs a // context with possible future cancel? - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { select { case <-rw.Context.Done(): @@ -784,7 +782,7 @@ func rwT[T any](c *Conn, rw rwContext[T]) (T, error) { case <-doneC: // Nothing to do. } - }() + }) } var ( diff --git a/vendor/github.com/mdlayher/socket/conn_linux.go b/vendor/github.com/mdlayher/socket/conn_linux.go index 081194f327..b1cdffbdc6 100644 --- a/vendor/github.com/mdlayher/socket/conn_linux.go +++ b/vendor/github.com/mdlayher/socket/conn_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux package socket diff --git a/vendor/github.com/mdlayher/socket/netns_linux.go b/vendor/github.com/mdlayher/socket/netns_linux.go index b29115ad1c..9f37b77029 100644 --- a/vendor/github.com/mdlayher/socket/netns_linux.go +++ b/vendor/github.com/mdlayher/socket/netns_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux package socket @@ -65,7 +64,7 @@ func withNetNS(fd int, fn func() (*Conn, error)) (*Conn, error) { // No more thread-local state manipulation; return the new Conn. runtime.UnlockOSThread() conn = c - return nil + return err }) if err := eg.Wait(); err != nil { diff --git a/vendor/github.com/mdlayher/socket/setbuffer_linux.go b/vendor/github.com/mdlayher/socket/setbuffer_linux.go index 0d4aa4417c..ae631893a6 100644 --- a/vendor/github.com/mdlayher/socket/setbuffer_linux.go +++ b/vendor/github.com/mdlayher/socket/setbuffer_linux.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux package socket diff --git a/vendor/github.com/mdlayher/socket/typ_cloexec_nonblock.go b/vendor/github.com/mdlayher/socket/typ_cloexec_nonblock.go index 40e834310b..f4a7e559b3 100644 --- a/vendor/github.com/mdlayher/socket/typ_cloexec_nonblock.go +++ b/vendor/github.com/mdlayher/socket/typ_cloexec_nonblock.go @@ -1,5 +1,4 @@ //go:build !darwin -// +build !darwin package socket diff --git a/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header/header.go b/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header/header.go index 8547c8dfd1..820bf436ab 100644 --- a/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header/header.go +++ b/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header/header.go @@ -90,7 +90,7 @@ loop: s = skipSpace(s[1:]) } } - return + return specs } func skipSpace(s string) (rest string) { diff --git a/vendor/github.com/prometheus/client_golang/prometheus/collectors/go_collector_go116.go b/vendor/github.com/prometheus/client_golang/prometheus/collectors/go_collector_go116.go deleted file mode 100644 index effc57840a..0000000000 --- a/vendor/github.com/prometheus/client_golang/prometheus/collectors/go_collector_go116.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2021 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !go1.17 -// +build !go1.17 - -package collectors - -import "github.com/prometheus/client_golang/prometheus" - -// NewGoCollector returns a collector that exports metrics about the current Go -// process. This includes memory stats. To collect those, runtime.ReadMemStats -// is called. This requires to “stop the world”, which usually only happens for -// garbage collection (GC). Take the following implications into account when -// deciding whether to use the Go collector: -// -// 1. The performance impact of stopping the world is the more relevant the more -// frequently metrics are collected. However, with Go1.9 or later the -// stop-the-world time per metrics collection is very short (~25µs) so that the -// performance impact will only matter in rare cases. However, with older Go -// versions, the stop-the-world duration depends on the heap size and can be -// quite significant (~1.7 ms/GiB as per -// https://go-review.googlesource.com/c/go/+/34937). -// -// 2. During an ongoing GC, nothing else can stop the world. Therefore, if the -// metrics collection happens to coincide with GC, it will only complete after -// GC has finished. Usually, GC is fast enough to not cause problems. However, -// with a very large heap, GC might take multiple seconds, which is enough to -// cause scrape timeouts in common setups. To avoid this problem, the Go -// collector will use the memstats from a previous collection if -// runtime.ReadMemStats takes more than 1s. However, if there are no previously -// collected memstats, or their collection is more than 5m ago, the collection -// will block until runtime.ReadMemStats succeeds. -// -// NOTE: The problem is solved in Go 1.15, see -// https://github.com/golang/go/issues/19812 for the related Go issue. -func NewGoCollector() prometheus.Collector { - return prometheus.NewGoCollector() -} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/collectors/go_collector_latest.go b/vendor/github.com/prometheus/client_golang/prometheus/collectors/go_collector_latest.go index cc4ef1077e..d29ade654f 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/collectors/go_collector_latest.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/collectors/go_collector_latest.go @@ -90,13 +90,13 @@ func WithGoCollectorMemStatsMetricsDisabled() func(options *internal.GoCollector // GoRuntimeMetricsRule allow enabling and configuring particular group of runtime/metrics. // TODO(bwplotka): Consider adding ability to adjust buckets. type GoRuntimeMetricsRule struct { - // Matcher represents RE2 expression will match the runtime/metrics from https://golang.bg/src/runtime/metrics/description.go + // Matcher represents RE2 expression will match the runtime/metrics from https://pkg.go.dev/runtime/metrics // Use `regexp.MustCompile` or `regexp.Compile` to create this field. Matcher *regexp.Regexp } // WithGoCollectorRuntimeMetrics allows enabling and configuring particular group of runtime/metrics. -// See the list of metrics https://golang.bg/src/runtime/metrics/description.go (pick the Go version you use there!). +// See the list of metrics https://pkg.go.dev/runtime/metrics (pick the Go version you use there!). // You can use this option in repeated manner, which will add new rules. The order of rules is important, the last rule // that matches particular metrics is applied. func WithGoCollectorRuntimeMetrics(rules ...GoRuntimeMetricsRule) func(options *internal.GoCollectorOptions) { diff --git a/vendor/github.com/prometheus/client_golang/prometheus/counter.go b/vendor/github.com/prometheus/client_golang/prometheus/counter.go index 4ce84e7a80..7d963d3afb 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/counter.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/counter.go @@ -85,11 +85,12 @@ type CounterVecOpts struct { // Both internal tracking values are added up in the Write method. This has to // be taken into account when it comes to precision and overflow behavior. func NewCounter(opts CounterOpts) Counter { - desc := NewDesc( + desc := V2.NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, - nil, + UnconstrainedLabels(nil), opts.ConstLabels, + WithUnit(opts.Unit), ) if opts.now == nil { opts.now = time.Now @@ -205,6 +206,7 @@ func (v2) NewCounterVec(opts CounterVecOpts) *CounterVec { opts.Help, opts.VariableLabels, opts.ConstLabels, + WithUnit(opts.Unit), ) if opts.now == nil { opts.now = time.Now @@ -349,10 +351,11 @@ type CounterFunc interface { // // Check out the ExampleGaugeFunc examples for the similar GaugeFunc. func NewCounterFunc(opts CounterOpts, function func() float64) CounterFunc { - return newValueFunc(NewDesc( + return newValueFunc(V2.NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, - nil, + UnconstrainedLabels(nil), opts.ConstLabels, + WithUnit(opts.Unit), ), CounterValue, function) } diff --git a/vendor/github.com/prometheus/client_golang/prometheus/desc.go b/vendor/github.com/prometheus/client_golang/prometheus/desc.go index 2331b8b4f3..a3c92e7a4c 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/desc.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/desc.go @@ -47,6 +47,8 @@ type Desc struct { fqName string // help provides some helpful information about this metric. help string + // unit provides the unit of this metric. + unit string // constLabelPairs contains precalculated DTO label pairs based on // the constant labels. constLabelPairs []*dto.LabelPair @@ -66,6 +68,16 @@ type Desc struct { err error } +// DescOpt allows setting optional fields for NewDesc. +type DescOpt func(*Desc) + +// WithUnit sets the unit for a Desc. +func WithUnit(unit string) DescOpt { + return func(d *Desc) { + d.unit = unit + } +} + // NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc // and will be reported on registration time. variableLabels and constLabels can // be nil if no such labels should be set. fqName must not be empty. @@ -89,14 +101,17 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) * // // For constLabels, the label values are constant. Therefore, they are fully // specified in the Desc. See the Collector example for a usage pattern. -func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, constLabels Labels) *Desc { +func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, constLabels Labels, opts ...DescOpt) *Desc { d := &Desc{ fqName: fqName, help: help, variableLabels: variableLabels.compile(), } - //nolint:staticcheck // TODO: Don't use deprecated model.NameValidationScheme. - if !model.NameValidationScheme.IsValidMetricName(fqName) { + + for _, opt := range opts { + opt(d) + } + if !model.UTF8Validation.IsValidMetricName(fqName) { d.err = fmt.Errorf("%q is not a valid metric name", fqName) return d } @@ -150,11 +165,13 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const d.id = xxh.Sum64() // Sort labelNames so that order doesn't matter for the hash. sort.Strings(labelNames) - // Now hash together (in this order) the help string and the sorted + // Now hash together (in this order) the help string, the unit string and the sorted // label names. xxh.Reset() xxh.WriteString(help) xxh.Write(separatorByteSlice) + xxh.WriteString(d.unit) + xxh.Write(separatorByteSlice) for _, labelName := range labelNames { xxh.WriteString(labelName) xxh.Write(separatorByteSlice) @@ -182,6 +199,15 @@ func NewInvalidDesc(err error) *Desc { } } +// Err returns an error that occurred during construction, if any. +// +// Calling this method is optional. It can be used to detect construction +// errors early, before invoking other methods on the Desc. If an error is +// present, later operations may not behave as expected. +func (d *Desc) Err() error { + return d.err +} + func (d *Desc) String() string { lpStrings := make([]string, 0, len(d.constLabelPairs)) for _, lp := range d.constLabelPairs { @@ -202,9 +228,10 @@ func (d *Desc) String() string { } } return fmt.Sprintf( - "Desc{fqName: %q, help: %q, constLabels: {%s}, variableLabels: {%s}}", + "Desc{fqName: %q, help: %q, unit: %q, constLabels: {%s}, variableLabels: {%s}}", d.fqName, d.help, + d.unit, strings.Join(lpStrings, ","), strings.Join(vlStrings, ","), ) diff --git a/vendor/github.com/prometheus/client_golang/prometheus/expvar_collector.go b/vendor/github.com/prometheus/client_golang/prometheus/expvar_collector.go index de5a856293..327746f433 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/expvar_collector.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/expvar_collector.go @@ -47,14 +47,14 @@ func (e *expvarCollector) Collect(ch chan<- Metric) { if expVar == nil { continue } - var v interface{} + var v any labels := make([]string, len(desc.variableLabels.names)) if err := json.Unmarshal([]byte(expVar.String()), &v); err != nil { ch <- NewInvalidMetric(desc, err) continue } - var processValue func(v interface{}, i int) - processValue = func(v interface{}, i int) { + var processValue func(v any, i int) + processValue = func(v any, i int) { if i >= len(labels) { copiedLabels := append(make([]string, 0, len(labels)), labels...) switch v := v.(type) { @@ -72,7 +72,7 @@ func (e *expvarCollector) Collect(ch chan<- Metric) { ch <- m return } - vm, ok := v.(map[string]interface{}) + vm, ok := v.(map[string]any) if !ok { return } diff --git a/vendor/github.com/prometheus/client_golang/prometheus/gauge.go b/vendor/github.com/prometheus/client_golang/prometheus/gauge.go index dd2eac9406..41e54bf270 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/gauge.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/gauge.go @@ -76,11 +76,12 @@ type GaugeVecOpts struct { // scenarios for Gauges and Counters, where the former tends to be Set-heavy and // the latter Inc-heavy. func NewGauge(opts GaugeOpts) Gauge { - desc := NewDesc( + desc := V2.NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, - nil, + UnconstrainedLabels(nil), opts.ConstLabels, + WithUnit(opts.Unit), ) result := &gauge{desc: desc, labelPairs: desc.constLabelPairs} result.init(result) // Init self-collection. @@ -163,6 +164,7 @@ func (v2) NewGaugeVec(opts GaugeVecOpts) *GaugeVec { opts.Help, opts.VariableLabels, opts.ConstLabels, + WithUnit(opts.Unit), ) return &GaugeVec{ MetricVec: NewMetricVec(desc, func(lvs ...string) Metric { @@ -302,10 +304,11 @@ type GaugeFunc interface { // value of 1. Example: // https://github.com/prometheus/common/blob/8558a5b7db3c84fa38b4766966059a7bd5bfa2ee/version/info.go#L36-L56 func NewGaugeFunc(opts GaugeOpts, function func() float64) GaugeFunc { - return newValueFunc(NewDesc( + return newValueFunc(V2.NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, - nil, + UnconstrainedLabels(nil), opts.ConstLabels, + WithUnit(opts.Unit), ), GaugeValue, function) } diff --git a/vendor/github.com/prometheus/client_golang/prometheus/go_collector_go116.go b/vendor/github.com/prometheus/client_golang/prometheus/go_collector_go116.go deleted file mode 100644 index 897a6e906b..0000000000 --- a/vendor/github.com/prometheus/client_golang/prometheus/go_collector_go116.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2021 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !go1.17 -// +build !go1.17 - -package prometheus - -import ( - "runtime" - "sync" - "time" -) - -type goCollector struct { - base baseGoCollector - - // ms... are memstats related. - msLast *runtime.MemStats // Previously collected memstats. - msLastTimestamp time.Time - msMtx sync.Mutex // Protects msLast and msLastTimestamp. - msMetrics memStatsMetrics - msRead func(*runtime.MemStats) // For mocking in tests. - msMaxWait time.Duration // Wait time for fresh memstats. - msMaxAge time.Duration // Maximum allowed age of old memstats. -} - -// NewGoCollector is the obsolete version of collectors.NewGoCollector. -// See there for documentation. -// -// Deprecated: Use collectors.NewGoCollector instead. -func NewGoCollector() Collector { - msMetrics := goRuntimeMemStats() - msMetrics = append(msMetrics, struct { - desc *Desc - eval func(*runtime.MemStats) float64 - valType ValueType - }{ - // This metric is omitted in Go1.17+, see https://github.com/prometheus/client_golang/issues/842#issuecomment-861812034 - desc: NewDesc( - memstatNamespace("gc_cpu_fraction"), - "The fraction of this program's available CPU time used by the GC since the program started.", - nil, nil, - ), - eval: func(ms *runtime.MemStats) float64 { return ms.GCCPUFraction }, - valType: GaugeValue, - }) - return &goCollector{ - base: newBaseGoCollector(), - msLast: &runtime.MemStats{}, - msRead: runtime.ReadMemStats, - msMaxWait: time.Second, - msMaxAge: 5 * time.Minute, - msMetrics: msMetrics, - } -} - -// Describe returns all descriptions of the collector. -func (c *goCollector) Describe(ch chan<- *Desc) { - c.base.Describe(ch) - for _, i := range c.msMetrics { - ch <- i.desc - } -} - -// Collect returns the current state of all metrics of the collector. -func (c *goCollector) Collect(ch chan<- Metric) { - var ( - ms = &runtime.MemStats{} - done = make(chan struct{}) - ) - // Start reading memstats first as it might take a while. - go func() { - c.msRead(ms) - c.msMtx.Lock() - c.msLast = ms - c.msLastTimestamp = time.Now() - c.msMtx.Unlock() - close(done) - }() - - // Collect base non-memory metrics. - c.base.Collect(ch) - - timer := time.NewTimer(c.msMaxWait) - select { - case <-done: // Our own ReadMemStats succeeded in time. Use it. - timer.Stop() // Important for high collection frequencies to not pile up timers. - c.msCollect(ch, ms) - return - case <-timer.C: // Time out, use last memstats if possible. Continue below. - } - c.msMtx.Lock() - if time.Since(c.msLastTimestamp) < c.msMaxAge { - // Last memstats are recent enough. Collect from them under the lock. - c.msCollect(ch, c.msLast) - c.msMtx.Unlock() - return - } - // If we are here, the last memstats are too old or don't exist. We have - // to wait until our own ReadMemStats finally completes. For that to - // happen, we have to release the lock. - c.msMtx.Unlock() - <-done - c.msCollect(ch, ms) -} - -func (c *goCollector) msCollect(ch chan<- Metric, ms *runtime.MemStats) { - for _, i := range c.msMetrics { - ch <- MustNewConstMetric(i.desc, i.valType, i.eval(ms)) - } -} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go b/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go index 6b8684731c..1db1c4be09 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go @@ -98,7 +98,7 @@ type goCollector struct { // snapshot is always produced by Collect. mu sync.Mutex - // Contains all samples that has to retrieved from runtime/metrics (not all of them will be exposed). + // Contains all samples that have to be retrieved from runtime/metrics (not all of them will be exposed). sampleBuf []metrics.Sample // sampleMap allows lookup for MemStats metrics and runtime/metrics histograms for exact sums. sampleMap map[string]*metrics.Sample @@ -210,16 +210,26 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector { sampleBuf = append(sampleBuf, metrics.Sample{Name: d.Name}) sampleMap[d.Name] = &sampleBuf[len(sampleBuf)-1] + // Extract unit from the runtime/metrics name (e.g., "/gc/heap/allocs:bytes" -> "bytes") + // and sanitize to match Prometheus naming conventions (e.g., "cpu-seconds" -> "cpu_seconds") + var unit string + if idx := strings.IndexRune(d.Name, ':'); idx >= 0 { + unit = d.Name[idx+1:] + unit = strings.ReplaceAll(unit, "-", "_") + unit = strings.ReplaceAll(unit, "*", "_") + unit = strings.ReplaceAll(unit, "/", "_per_") + } + var m collectorMetric if d.Kind == metrics.KindFloat64Histogram { _, hasSum := opt.RuntimeMetricSumForHist[d.Name] - unit := d.Name[strings.IndexRune(d.Name, ':')+1:] m = newBatchHistogram( - NewDesc( + V2.NewDesc( BuildFQName(namespace, subsystem, name), help, + UnconstrainedLabels(nil), nil, - nil, + WithUnit(unit), ), internal.RuntimeMetricsBucketsForUnit(bucketsMap[d.Name], unit), hasSum, @@ -230,6 +240,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector { Subsystem: subsystem, Name: name, Help: help, + Unit: unit, }, ) } else { @@ -238,6 +249,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector { Subsystem: subsystem, Name: name, Help: help, + Unit: unit, }) } metricSet = append(metricSet, m) diff --git a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go index c453b754a7..88bae3b32c 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go @@ -378,6 +378,9 @@ type HistogramOpts struct { // string. Help string + // Unit provides the unit of this Histogram. + Unit string + // ConstLabels are used to attach fixed labels to this metric. Metrics // with the same fully-qualified name must have the same label names in // their ConstLabels. @@ -522,11 +525,12 @@ type HistogramVecOpts struct { // for each bucket. func NewHistogram(opts HistogramOpts) Histogram { return newHistogram( - NewDesc( + V2.NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, - nil, + UnconstrainedLabels(nil), opts.ConstLabels, + WithUnit(opts.Unit), ), opts, ) @@ -966,7 +970,7 @@ func (h *histogram) maybeReset( // We are using the possibly mocked h.now() rather than // time.Since(h.lastResetTime) to enable testing. if h.nativeHistogramMinResetDuration == 0 || // No reset configured. - h.resetScheduled || // Do not interefere if a reset is already scheduled. + h.resetScheduled || // Do not interfere if a reset is already scheduled. h.now().Sub(h.lastResetTime) < h.nativeHistogramMinResetDuration { return false } @@ -1053,8 +1057,8 @@ func (h *histogram) maybeWidenZeroBucket(hot, cold *histogramCounts) bool { atomic.StoreUint64(&cold.nativeHistogramZeroThresholdBits, math.Float64bits(newZeroThreshold)) // ...and then merge the newly deleted buckets into the wider zero // bucket. - mergeAndDeleteOrAddAndReset := func(hotBuckets, coldBuckets *sync.Map) func(k, v interface{}) bool { - return func(k, v interface{}) bool { + mergeAndDeleteOrAddAndReset := func(hotBuckets, coldBuckets *sync.Map) func(k, v any) bool { + return func(k, v any) bool { key := k.(int) bucket := v.(*int64) if key == smallestKey { @@ -1107,8 +1111,8 @@ func (h *histogram) doubleBucketWidth(hot, cold *histogramCounts) { // ...adjust the schema in the cold counts, too... atomic.StoreInt32(&cold.nativeHistogramSchema, coldSchema) // ...and then merge the cold buckets into the wider hot buckets. - merge := func(hotBuckets *sync.Map) func(k, v interface{}) bool { - return func(k, v interface{}) bool { + merge := func(hotBuckets *sync.Map) func(k, v any) bool { + return func(k, v any) bool { key := k.(int) bucket := v.(*int64) // Adjust key to match the bucket to merge into. @@ -1190,6 +1194,7 @@ func (v2) NewHistogramVec(opts HistogramVecOpts) *HistogramVec { opts.Help, opts.VariableLabels, opts.ConstLabels, + WithUnit(opts.Unit), ) return &HistogramVec{ MetricVec: NewMetricVec(desc, func(lvs ...string) Metric { @@ -1476,7 +1481,7 @@ func pickSchema(bucketFactor float64) int32 { func makeBuckets(buckets *sync.Map) ([]*dto.BucketSpan, []int64) { var ii []int - buckets.Range(func(k, v interface{}) bool { + buckets.Range(func(k, v any) bool { ii = append(ii, k.(int)) return true }) @@ -1553,8 +1558,8 @@ func addToBucket(buckets *sync.Map, key int, increment int64) bool { // according to the buckets ranged through. It then resets all buckets ranged // through to 0 (but leaves them in place so that they don't need to get // recreated on the next scrape). -func addAndReset(hotBuckets *sync.Map, bucketNumber *uint32) func(k, v interface{}) bool { - return func(k, v interface{}) bool { +func addAndReset(hotBuckets *sync.Map, bucketNumber *uint32) func(k, v any) bool { + return func(k, v any) bool { bucket := v.(*int64) if addToBucket(hotBuckets, k.(int), atomic.LoadInt64(bucket)) { atomic.AddUint32(bucketNumber, 1) @@ -1565,7 +1570,7 @@ func addAndReset(hotBuckets *sync.Map, bucketNumber *uint32) func(k, v interface } func deleteSyncMap(m *sync.Map) { - m.Range(func(k, v interface{}) bool { + m.Range(func(k, v any) bool { m.Delete(k) return true }) @@ -1573,7 +1578,7 @@ func deleteSyncMap(m *sync.Map) { func findSmallestKey(m *sync.Map) int { result := math.MaxInt32 - m.Range(func(k, v interface{}) bool { + m.Range(func(k, v any) bool { key := k.(int) if key < result { result = key diff --git a/vendor/github.com/prometheus/client_golang/prometheus/internal/difflib.go b/vendor/github.com/prometheus/client_golang/prometheus/internal/difflib.go index 7bac0da33d..2db270f216 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/internal/difflib.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/internal/difflib.go @@ -78,7 +78,7 @@ type OpCode struct { // notion, pairing up elements that appear uniquely in each sequence. // That, and the method here, appear to yield more intuitive difference // reports than does diff. This method appears to be the least vulnerable -// to synching up on blocks of "junk lines", though (like blank lines in +// to syncing up on blocks of "junk lines", though (like blank lines in // ordinary text files, or maybe "

" lines in HTML files). That may be // because this is the only method of the 3 that has a *concept* of // "junk" . @@ -567,7 +567,7 @@ type UnifiedDiff struct { func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error { buf := bufio.NewWriter(writer) defer buf.Flush() - wf := func(format string, args ...interface{}) error { + wf := func(format string, args ...any) error { _, err := fmt.Fprintf(buf, format, args...) return err } diff --git a/vendor/github.com/prometheus/client_golang/prometheus/labels.go b/vendor/github.com/prometheus/client_golang/prometheus/labels.go index 5fe8d3b4d2..a0285489a0 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/labels.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/labels.go @@ -184,6 +184,5 @@ func validateLabelValues(vals []string, expectedNumberOfValues int) error { } func checkLabelName(l string) bool { - //nolint:staticcheck // TODO: Don't use deprecated model.NameValidationScheme. - return model.NameValidationScheme.IsValidLabelName(l) && !strings.HasPrefix(l, reservedLabelPrefix) + return model.UTF8Validation.IsValidLabelName(l) && !strings.HasPrefix(l, reservedLabelPrefix) } diff --git a/vendor/github.com/prometheus/client_golang/prometheus/metric.go b/vendor/github.com/prometheus/client_golang/prometheus/metric.go index 76e59f1288..c5cb90adf8 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/metric.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/metric.go @@ -81,6 +81,9 @@ type Opts struct { // string. Help string + // Unit provides the unit of this metric as per https://prometheus.io/docs/specs/om + Unit string + // ConstLabels are used to attach fixed labels to this metric. Metrics // with the same fully-qualified name must have the same label names in // their ConstLabels. diff --git a/vendor/github.com/prometheus/client_golang/prometheus/process_collector_darwin.go b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_darwin.go index b32c95fa3f..2b16298f40 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/process_collector_darwin.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_darwin.go @@ -72,7 +72,13 @@ func getOpenFileCount() (float64, error) { } func (c *processCollector) processCollect(ch chan<- Metric) { - if procs, err := unix.SysctlKinfoProcSlice("kern.proc.pid", os.Getpid()); err == nil { + pid, err := c.pidFn() + if err != nil { + c.reportError(ch, nil, err) + return + } + + if procs, err := unix.SysctlKinfoProcSlice("kern.proc.pid", pid); err == nil { if len(procs) == 1 { startTime := float64(procs[0].Proc.P_starttime.Nano() / 1e9) ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime) @@ -84,6 +90,11 @@ func (c *processCollector) processCollect(ch chan<- Metric) { c.reportError(ch, c.startTime, err) } + if pid != os.Getpid() { + c.reportError(ch, nil, fmt.Errorf("collecting metrics for pid %d is not supported on darwin: process metrics collection is limited to the current process (pid %d)", pid, os.Getpid())) + return + } + // The proc structure returned by kern.proc.pid above has an Rusage member, // but it is not filled in, so it needs to be fetched by getrusage(2). For // that call, the UTime, STime, and Maxrss members are filled out, but not diff --git a/vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go index fa474289ef..c08dd05f03 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go @@ -30,6 +30,10 @@ var ( procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") procGetProcessHandleCount = modkernel32.NewProc("GetProcessHandleCount") + + openProcess = windows.OpenProcess + closeHandle = windows.CloseHandle + getProcessTimes = windows.GetProcessTimes ) type processMemoryCounters struct { @@ -79,10 +83,21 @@ func getProcessHandleCount(handle windows.Handle) (uint32, error) { } func (c *processCollector) processCollect(ch chan<- Metric) { - h := windows.CurrentProcess() + pid, err := c.pidFn() + if err != nil { + c.reportError(ch, nil, err) + return + } + + h, err := openProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ, false, uint32(pid)) + if err != nil { + c.reportError(ch, nil, err) + return + } + defer closeHandle(h) var startTime, exitTime, kernelTime, userTime windows.Filetime - err := windows.GetProcessTimes(h, &startTime, &exitTime, &kernelTime, &userTime) + err = getProcessTimes(h, &startTime, &exitTime, &kernelTime, &userTime) if err != nil { c.reportError(ch, nil, err) return diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go index 763d99e362..368625c6a8 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go @@ -37,10 +37,12 @@ import ( "fmt" "io" "net/http" + "slices" "strconv" "sync" "time" + dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" "github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil" @@ -74,11 +76,118 @@ func defaultCompressionFormats() []Compression { } var gzipPool = sync.Pool{ - New: func() interface{} { + New: func() any { return gzip.NewWriter(nil) }, } +// coalescingGatherer wraps a TransactionalGatherer to deduplicate concurrent +// Gather calls. When a Gather is already in flight, new callers join the +// existing cycle and receive the same result once it completes. The underlying +// done function is called exactly once, when the last joined caller releases. +// +// This prevents goroutine pile-up when the scrape rate is faster than the +// time collectors need to produce metrics. +type coalescingGatherer struct { + g prometheus.TransactionalGatherer + mu sync.Mutex + cycle *gatherCycle +} + +// gatherCycle tracks a single in-flight Gather and all HTTP handlers sharing it. +type gatherCycle struct { + ready chan struct{} // closed when Gather completes; happens-before reads of mfs/err/done + mfs []*dto.MetricFamily // canonical result, set before ready is closed; callers get a slices.Clone, the element values stay shared and must not be mutated + err error // set before ready is closed + done func() // underlying done callback; set before ready is closed + refs int // number of handlers using this cycle; protected by coalescingGatherer.mu +} + +var _ prometheus.TransactionalGatherer = (*coalescingGatherer)(nil) // compile-time interface check + +// errGatherPanicked is returned to callers that joined an in-flight coalesced +// Gather whose underlying gatherer panicked. See the panic guard in Gather for +// why joiners receive this error instead of the panic itself. +var errGatherPanicked = errors.New("coalesced gather panicked") + +func (c *coalescingGatherer) Gather() ([]*dto.MetricFamily, func(), error) { + c.mu.Lock() + if cy := c.cycle; cy != nil { + // c.cycle is non-nil while Gather runs or handlers are still consuming its results. + cy.refs++ + c.mu.Unlock() + <-cy.ready + // Each caller gets its own slice header so it can filter or reorder + // without racing other callers sharing this cycle. The *dto.MetricFamily + // values remain shared and must not be mutated in place. + return slices.Clone(cy.mfs), c.releaseFunc(cy), cy.err + } + cy := &gatherCycle{ + ready: make(chan struct{}), + done: func() {}, + refs: 1, + } + c.cycle = cy + c.mu.Unlock() + + // Guard against a panic in c.g.Gather. The common case, a panicking + // Collector, never reaches here: Registry.Gather recovers Collector panics + // and returns them as an error. This guard only covers the rare case where + // the wrapped gatherer itself panics. + // + // We deliberately do not recover: the leader's panic propagates and is + // handled by net/http exactly as it would be without coalescing. We only + // set cy.err before closing cy.ready so joiners waiting on <-cy.ready fail + // with that error instead of silently returning an empty, successful + // response, and we clear c.cycle so the next Gather starts a fresh cycle. + // + // The leader never runs its own releaseFunc on this path, so its ref is + // not decremented; that is harmless because the cycle is detached (c.cycle + // = nil) and cy.done is still the no-op set at construction (c.g.Gather + // panicked before assigning a real done). If cy.done is ever made non-nil + // before c.g.Gather runs, this path would need to release it. + panicked := true + defer func() { + if panicked { + c.mu.Lock() + if c.cycle == cy { + c.cycle = nil + } + c.mu.Unlock() + cy.err = errGatherPanicked // set before close: happens-before joiners' reads + close(cy.ready) + } + }() + cy.mfs, cy.done, cy.err = c.g.Gather() + panicked = false + close(cy.ready) // happens-before joiners' reads of cy.mfs/err/done + + // Clone here too so cy.mfs stays the write-once canonical slice: joiners + // read it concurrently via slices.Clone, so the leader must not hand out + // (and potentially reorder) the same backing array. + return slices.Clone(cy.mfs), c.releaseFunc(cy), cy.err +} + +// releaseFunc returns the done callback for one caller sharing cy. +// When the last caller releases, the underlying done is invoked and the +// cycle is cleared so the next Gather starts fresh. +func (c *coalescingGatherer) releaseFunc(cy *gatherCycle) func() { + return func() { + c.mu.Lock() + cy.refs-- + if cy.refs > 0 { + c.mu.Unlock() + return + } + // Last caller. + if c.cycle == cy { + c.cycle = nil + } + c.mu.Unlock() + cy.done() // called outside the lock to avoid holding it during done + } +} + // Handler returns an http.Handler for the prometheus.DefaultGatherer, using // default HandlerOpts, i.e. it reports the first error as an HTTP error, it has // no error logging, and it applies compression if requested by the client. @@ -89,6 +198,10 @@ var gzipPool = sync.Pool{ // metrics used for instrumentation will be shared between them, providing // global scrape counts. // +// The handler supports filtering metrics by name using the `name[]` query parameter. +// Multiple metric names can be specified by providing the parameter multiple times. +// When no name[] parameters are provided, all metrics are returned. +// // This function is meant to cover the bulk of basic use cases. If you are doing // anything that requires more customization (including using a non-default // Gatherer, different instrumentation, and non-default HandlerOpts), use the @@ -105,6 +218,10 @@ func Handler() http.Handler { // Gatherers, with non-default HandlerOpts, and/or with custom (or no) // instrumentation. Use the InstrumentMetricHandler function to apply the same // kind of instrumentation as it is used by the Handler function. +// +// The handler supports filtering metrics by name using the `name[]` query parameter. +// Multiple metric names can be specified by providing the parameter multiple times. +// When no name[] parameters are provided, all metrics are returned. func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler { return HandlerForTransactional(prometheus.ToTransactionalGatherer(reg), opts) } @@ -112,7 +229,15 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler { // HandlerForTransactional is like HandlerFor, but it uses transactional gather, which // can safely change in-place returned *dto.MetricFamily before call to `Gather` and after // call to `done` of that `Gather`. +// +// The handler supports filtering metrics by name using the `name[]` query parameter. +// Multiple metric names can be specified by providing the parameter multiple times. +// When no name[] parameters are provided, all metrics are returned. func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerOpts) http.Handler { + if opts.CoalesceGather { + reg = &coalescingGatherer{g: reg} + } + var ( inFlightSem chan struct{} errCnt = prometheus.NewCounterVec( @@ -214,12 +339,14 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO rsp.Header().Set(contentEncodingHeader, encodingHeader) } - var enc expfmt.Encoder + var ( + enc expfmt.Encoder + encOpts []expfmt.EncoderOption + ) if opts.EnableOpenMetricsTextCreatedSamples { - enc = expfmt.NewEncoder(w, contentType, expfmt.WithCreatedLines()) - } else { - enc = expfmt.NewEncoder(w, contentType) + encOpts = append(encOpts, expfmt.WithCreatedLines()) } + enc = expfmt.NewEncoder(w, contentType, encOpts...) // handleError handles the error according to opts.ErrorHandling // and returns true if we have to abort after the handling. @@ -245,7 +372,21 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO return false } + // Build metric name filter set from query params (if any) + var metricFilter map[string]struct{} + if metricNames := req.URL.Query()["name[]"]; len(metricNames) > 0 { + metricFilter = make(map[string]struct{}, len(metricNames)) + for _, name := range metricNames { + metricFilter[name] = struct{}{} + } + } + for _, mf := range mfs { + if metricFilter != nil { + if _, ok := metricFilter[mf.GetName()]; !ok { + continue + } + } if handleError(enc.Encode(mf)) { return } @@ -353,7 +494,7 @@ const ( // log.Logger from the standard library implements this interface, and it is // easy to implement by custom loggers, if they don't do so already anyway. type Logger interface { - Println(v ...interface{}) + Println(v ...any) } // HandlerOpts specifies options how to serve metrics via an http.Handler. The @@ -400,6 +541,40 @@ type HandlerOpts struct { // Service Unavailable and a suitable message in the body. If // MaxRequestsInFlight is 0 or negative, no limit is applied. MaxRequestsInFlight int + // CoalesceGather, if true, deduplicates concurrent Gather calls so that + // only one collection runs at a time. Additional requests that arrive + // while a Gather is in flight will receive the same result once it + // completes. This prevents goroutine pile-up when the scrape rate is + // faster than the time collectors need to produce metrics. + // + // When enabled, concurrent scrapers share a single metric snapshot per + // collection cycle. Each request receives its own copy of the returned + // slice, so filtering or reordering it (for example via name[] query + // parameters) is safe. The pointed-to MetricFamily values are still + // shared: the built-in handler only reads them, so this is safe in + // practice, but a custom TransactionalGatherer that mutates the returned + // families in place after Gather returns must not use this option. + // + // Because the snapshot is shared, a request that arrives while a cycle is + // in flight receives that cycle's result even though collection began + // before the request; two scrapers joined to one cycle observe the same + // timestamps rather than independently gathered data. + // + // Consider using CoalesceGather together with Timeout. Timeout bounds the + // client-facing response time and keeps at most one collection running at + // a time, but it does not cancel the underlying Gather: a joined request + // that times out still holds a MaxRequestsInFlight slot until the shared + // collection completes. + // + // Panic handling: a panicking Collector is already turned into an error by + // the registry, so joiners receive that error like any other. In the rare + // case where the wrapped gatherer itself panics, the panicking request's + // panic propagates as usual (handled by net/http), while requests that + // joined the same cycle receive an error rather than an empty response. + // + // NOTE: This option is experimental and may change or be removed in a + // future release. + CoalesceGather bool // If handling a request takes longer than Timeout, it is responded to // with 503 ServiceUnavailable and a suitable Message. No timeout is // applied if Timeout is 0 or negative. Note that with the current @@ -407,8 +582,9 @@ type HandlerOpts struct { // described above (and even that only if sending of the body hasn't // started yet), while the bulk work of gathering all the metrics keeps // running in the background (with the eventual result to be thrown - // away). Until the implementation is improved, it is recommended to - // implement a separate timeout in potentially slow Collectors. + // away). When CoalesceGather is enabled, only one such background Gather + // can be in flight at a time. It is also recommended to implement a + // separate timeout in potentially slow Collectors. Timeout time.Duration // If true, the experimental OpenMetrics encoding is added to the // possible options during content negotiation. Note that Prometheus @@ -460,7 +636,7 @@ func httpError(rsp http.ResponseWriter, err error) { // negotiateEncodingWriter reads the Accept-Encoding header from a request and // selects the right compression based on an allow-list of supported -// compressions. It returns a writer implementing the compression and an the +// compressions. It returns a writer implementing the compression and the // correct value that the caller can set in the response header. func negotiateEncodingWriter(r *http.Request, rw io.Writer, compressions []string) (_ io.Writer, encodingHeaderValue string, closeWriter func(), _ error) { if len(compressions) == 0 { diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go index d3482c40ca..0248579742 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go @@ -75,10 +75,10 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou resp, err := next.RoundTrip(r) if err == nil { l := labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...) - for label, resolve := range rtOpts.extraLabelsFromCtx { - l[label] = resolve(resp.Request.Context()) + for label, resolve := range rtOpts.extraLabelsFromRequest { + l[label] = resolve(resp.Request) } - addWithExemplar(counter.With(l), 1, rtOpts.getExemplarFn(r.Context())) + addWithExemplar(counter.With(l), 1, rtOpts.getExemplarFn(r)) } return resp, err } @@ -119,10 +119,10 @@ func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundT resp, err := next.RoundTrip(r) if err == nil { l := labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...) - for label, resolve := range rtOpts.extraLabelsFromCtx { - l[label] = resolve(resp.Request.Context()) + for label, resolve := range rtOpts.extraLabelsFromRequest { + l[label] = resolve(resp.Request) } - observeWithExemplar(obs.With(l), time.Since(start).Seconds(), rtOpts.getExemplarFn(r.Context())) + observeWithExemplar(obs.With(l), time.Since(start).Seconds(), rtOpts.getExemplarFn(r)) } return resp, err } diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go index 9332b0249a..9dec091ac7 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go @@ -28,24 +28,36 @@ import ( // magicString is used for the hacky label test in checkLabels. Remove once fixed. const magicString = "zZgWfBxLqvG8kc8IMv3POi2Bb0tZI3vAnBx+gBaFi9FyPzB/CzKUer1yufDa" -// observeWithExemplar is a wrapper for [prometheus.ExemplarAdder.ExemplarObserver], -// which falls back to [prometheus.Observer.Observe] if no labels are provided. +// observeWithExemplar records val on obs. If labels is non-nil and obs +// implements [prometheus.ExemplarObserver], the exemplar is attached via +// ObserveWithExemplar; otherwise the exemplar is dropped and the value is +// recorded with a plain [prometheus.Observer.Observe]. This mirrors the +// safe-cast pattern in [prometheus.Timer.ObserveDurationWithExemplar] and +// ensures we never panic when callers pass an ObserverVec backed by a +// summary, which cannot carry exemplars in the Prometheus exposition format. func observeWithExemplar(obs prometheus.Observer, val float64, labels map[string]string) { - if labels == nil { - obs.Observe(val) - return + if labels != nil { + if eo, ok := obs.(prometheus.ExemplarObserver); ok { + eo.ObserveWithExemplar(val, labels) + return + } } - obs.(prometheus.ExemplarObserver).ObserveWithExemplar(val, labels) + obs.Observe(val) } -// addWithExemplar is a wrapper for [prometheus.ExemplarAdder.AddWithExemplar], -// which falls back to [prometheus.Counter.Add] if no labels are provided. -func addWithExemplar(obs prometheus.Counter, val float64, labels map[string]string) { - if labels == nil { - obs.Add(val) - return +// addWithExemplar records val on c. If labels is non-nil and c implements +// [prometheus.ExemplarAdder], the exemplar is attached via AddWithExemplar; +// otherwise the exemplar is dropped and the value is recorded with a plain +// [prometheus.Counter.Add]. The safe-cast keeps the helper robust against +// custom Counter implementations that do not advertise exemplar support. +func addWithExemplar(c prometheus.Counter, val float64, labels map[string]string) { + if labels != nil { + if ea, ok := c.(prometheus.ExemplarAdder); ok { + ea.AddWithExemplar(val, labels) + return + } } - obs.(prometheus.ExemplarAdder).AddWithExemplar(val, labels) + c.Add(val) } // InstrumentHandlerInFlight is a middleware that wraps the provided @@ -97,10 +109,10 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, op next.ServeHTTP(d, r) l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...) - for label, resolve := range hOpts.extraLabelsFromCtx { - l[label] = resolve(r.Context()) + for label, resolve := range hOpts.extraLabelsFromRequest { + l[label] = resolve(r) } - observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context())) + observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r)) } } @@ -108,10 +120,10 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, op now := time.Now() next.ServeHTTP(w, r) l := labels(code, method, r.Method, 0, hOpts.extraMethods...) - for label, resolve := range hOpts.extraLabelsFromCtx { - l[label] = resolve(r.Context()) + for label, resolve := range hOpts.extraLabelsFromRequest { + l[label] = resolve(r) } - observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context())) + observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r)) } } @@ -147,10 +159,10 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, next.ServeHTTP(d, r) l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...) - for label, resolve := range hOpts.extraLabelsFromCtx { - l[label] = resolve(r.Context()) + for label, resolve := range hOpts.extraLabelsFromRequest { + l[label] = resolve(r) } - addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r.Context())) + addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r)) } } @@ -158,10 +170,10 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, next.ServeHTTP(w, r) l := labels(code, method, r.Method, 0, hOpts.extraMethods...) - for label, resolve := range hOpts.extraLabelsFromCtx { - l[label] = resolve(r.Context()) + for label, resolve := range hOpts.extraLabelsFromRequest { + l[label] = resolve(r) } - addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r.Context())) + addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r)) } } @@ -200,10 +212,10 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha now := time.Now() d := newDelegator(w, func(status int) { l := labels(code, method, r.Method, status, hOpts.extraMethods...) - for label, resolve := range hOpts.extraLabelsFromCtx { - l[label] = resolve(r.Context()) + for label, resolve := range hOpts.extraLabelsFromRequest { + l[label] = resolve(r) } - observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context())) + observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r)) }) next.ServeHTTP(d, r) } @@ -244,10 +256,10 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, size := computeApproximateRequestSize(r) l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...) - for label, resolve := range hOpts.extraLabelsFromCtx { - l[label] = resolve(r.Context()) + for label, resolve := range hOpts.extraLabelsFromRequest { + l[label] = resolve(r) } - observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r.Context())) + observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r)) } } @@ -256,10 +268,10 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, size := computeApproximateRequestSize(r) l := labels(code, method, r.Method, 0, hOpts.extraMethods...) - for label, resolve := range hOpts.extraLabelsFromCtx { - l[label] = resolve(r.Context()) + for label, resolve := range hOpts.extraLabelsFromRequest { + l[label] = resolve(r) } - observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r.Context())) + observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r)) } } @@ -296,10 +308,10 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler next.ServeHTTP(d, r) l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...) - for label, resolve := range hOpts.extraLabelsFromCtx { - l[label] = resolve(r.Context()) + for label, resolve := range hOpts.extraLabelsFromRequest { + l[label] = resolve(r) } - observeWithExemplar(obs.With(l), float64(d.Written()), hOpts.getExemplarFn(r.Context())) + observeWithExemplar(obs.With(l), float64(d.Written()), hOpts.getExemplarFn(r)) }) } @@ -366,7 +378,7 @@ func checkLabels(c prometheus.Collector) (code, method bool) { panic("metric partitioned with non-supported labels") } } - return + return code, method } func isLabelCurried(c prometheus.Collector, label string) bool { diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/option.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/option.go index 5d4383aa14..d4c0954f36 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/option.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/option.go @@ -15,6 +15,7 @@ package promhttp import ( "context" + "net/http" "github.com/prometheus/client_golang/prometheus" ) @@ -24,28 +25,31 @@ type Option interface { apply(*options) } +// LabelValueFromRequest is used to compute the label value from request. +type LabelValueFromRequest func(request *http.Request) string + // LabelValueFromCtx are used to compute the label value from request context. // Context can be filled with values from request through middleware. type LabelValueFromCtx func(ctx context.Context) string // options store options for both a handler or round tripper. type options struct { - extraMethods []string - getExemplarFn func(requestCtx context.Context) prometheus.Labels - extraLabelsFromCtx map[string]LabelValueFromCtx + extraMethods []string + getExemplarFn func(req *http.Request) prometheus.Labels + extraLabelsFromRequest map[string]LabelValueFromRequest } func defaultOptions() *options { return &options{ - getExemplarFn: func(ctx context.Context) prometheus.Labels { return nil }, - extraLabelsFromCtx: map[string]LabelValueFromCtx{}, + getExemplarFn: func(req *http.Request) prometheus.Labels { return nil }, + extraLabelsFromRequest: map[string]LabelValueFromRequest{}, } } func (o *options) emptyDynamicLabels() prometheus.Labels { labels := prometheus.Labels{} - for label := range o.extraLabelsFromCtx { + for label := range o.extraLabelsFromRequest { labels[label] = "" } @@ -66,19 +70,39 @@ func WithExtraMethods(methods ...string) Option { }) } -// WithExemplarFromContext allows to inject function that will get exemplar from context that will be put to counter and histogram metrics. +// WithExemplarFromRequest allows you to inject a function that will get exemplar from request that will be put to counter and histogram metrics. // If the function returns nil labels or the metric does not support exemplars, no exemplar will be added (noop), but // metric will continue to observe/increment. -func WithExemplarFromContext(getExemplarFn func(requestCtx context.Context) prometheus.Labels) Option { +func WithExemplarFromRequest(getExemplarFn func(req *http.Request) prometheus.Labels) Option { return optionApplyFunc(func(o *options) { o.getExemplarFn = getExemplarFn }) } +// WithExemplarFromContext allows you to inject a function that will get exemplar from context that will be put to counter and histogram metrics. +// If the function returns nil labels or the metric does not support exemplars, no exemplar will be added (noop), but +// metric will continue to observe/increment. +func WithExemplarFromContext(getExemplarFn func(requestCtx context.Context) prometheus.Labels) Option { + return optionApplyFunc(func(o *options) { + o.getExemplarFn = func(req *http.Request) prometheus.Labels { + return getExemplarFn(req.Context()) + } + }) +} + +// WithLabelFromRequest registers a label for dynamic resolution with access to the request. +func WithLabelFromRequest(name string, valueFn LabelValueFromRequest) Option { + return optionApplyFunc(func(o *options) { + o.extraLabelsFromRequest[name] = valueFn + }) +} + // WithLabelFromCtx registers a label for dynamic resolution with access to context. // See the example for ExampleInstrumentHandlerWithLabelResolver for example usage func WithLabelFromCtx(name string, valueFn LabelValueFromCtx) Option { return optionApplyFunc(func(o *options) { - o.extraLabelsFromCtx[name] = valueFn + o.extraLabelsFromRequest[name] = func(req *http.Request) string { + return valueFn(req.Context()) + } }) } diff --git a/vendor/github.com/prometheus/client_golang/prometheus/registry.go b/vendor/github.com/prometheus/client_golang/prometheus/registry.go index c6fd2f58b7..ed0681c8b4 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/registry.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/registry.go @@ -214,6 +214,19 @@ func (err AlreadyRegisteredError) Error() string { // by a Gatherer to report multiple errors during MetricFamily gathering. type MultiError []error +// SafeMultiError is a thread-safe wrapper around MultiError using a mutex. +type SafeMultiError struct { + mu sync.Mutex + errs MultiError +} + +// Appends the provided error to the contained MultiError in a thread-safe way. +func (s *SafeMultiError) Append(err error) { + s.mu.Lock() + s.errs.Append(err) + s.mu.Unlock() +} + // Error formats the contained errors as a bullet point list, preceded by the // total number of errors. Note that this results in a multi-line string. func (errs MultiError) Error() string { @@ -408,6 +421,16 @@ func (r *Registry) MustRegister(cs ...Collector) { } } +// MustGather implements Gatherer. +// Wraps around Gather and panics if Gather fails for any reason. +func (r *Registry) MustGather() []*dto.MetricFamily { + mfs, err := r.Gather() + if err != nil { + panic(err) + } + return mfs +} + // Gather implements Gatherer. func (r *Registry) Gather() ([]*dto.MetricFamily, error) { r.mtx.RLock() @@ -423,7 +446,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { uncheckedMetricChan = make(chan Metric, capMetricChan) metricHashes = map[uint64]struct{}{} wg sync.WaitGroup - errs MultiError // The collected errors to return in the end. + safeErrs = &SafeMultiError{} // To collect errors in a threadsafe way registeredDescIDs map[uint64]struct{} // Only used for pedantic checks ) @@ -453,9 +476,9 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { for { select { case collector := <-checkedCollectors: - collector.Collect(checkedMetricChan) + safeErrs.Append((safeCollect(collector, checkedMetricChan))) case collector := <-uncheckedCollectors: - collector.Collect(uncheckedMetricChan) + safeErrs.Append(safeCollect(collector, uncheckedMetricChan)) default: return } @@ -499,7 +522,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { cmc = nil break } - errs.Append(processMetric( + safeErrs.Append(processMetric( metric, metricFamiliesByName, metricHashes, registeredDescIDs, @@ -509,7 +532,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { umc = nil break } - errs.Append(processMetric( + safeErrs.Append(processMetric( metric, metricFamiliesByName, metricHashes, nil, @@ -526,7 +549,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { cmc = nil break } - errs.Append(processMetric( + safeErrs.Append(processMetric( metric, metricFamiliesByName, metricHashes, registeredDescIDs, @@ -536,7 +559,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { umc = nil break } - errs.Append(processMetric( + safeErrs.Append(processMetric( metric, metricFamiliesByName, metricHashes, nil, @@ -556,7 +579,8 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { break } } - return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap() + + return internal.NormalizeMetricFamilies(metricFamiliesByName), safeErrs.errs.MaybeUnwrap() } // Describe implements Collector. @@ -571,6 +595,24 @@ func (r *Registry) Describe(ch chan<- *Desc) { } } +// Helper wrapper around Collector.Collect. +// It tries to collect from the channel, recovers on panic and +// if it has recovered from a panic, then it sends an InvalidMetric into +// the channel with an InvalidDesc, and an error that includes a stack trace. +func safeCollect(c Collector, ch chan<- Metric) (err error) { + defer func() { + if r := recover(); r != nil { + buf := make([]byte, 64<<10) // 64 KB + n := runtime.Stack(buf, false) + err = fmt.Errorf("prometheus collector panic recovered: type=%T: error=%v\nstack trace=%s", c, r, buf[:n]) + ch <- NewInvalidMetric(NewInvalidDesc(err), err) + } + }() + c.Collect(ch) + + return err +} + // Collect implements Collector. func (r *Registry) Collect(ch chan<- Metric) { r.mtx.RLock() @@ -599,10 +641,12 @@ func WriteToTextfile(filename string, g Gatherer) error { mfs, err := g.Gather() if err != nil { + tmp.Close() return err } for _, mf := range mfs { if _, err := expfmt.MetricFamilyToText(tmp, mf); err != nil { + tmp.Close() return err } } @@ -685,6 +729,9 @@ func processMetric( metricFamily = &dto.MetricFamily{} metricFamily.Name = proto.String(desc.fqName) metricFamily.Help = proto.String(desc.help) + if desc.unit != "" { + metricFamily.Unit = proto.String(desc.unit) + } // TODO(beorn7): Simplify switch once Desc has type. switch { case dtoMetric.Gauge != nil: diff --git a/vendor/github.com/prometheus/client_golang/prometheus/summary.go b/vendor/github.com/prometheus/client_golang/prometheus/summary.go index ac5203c6fa..c12b8d13d4 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/summary.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/summary.go @@ -101,6 +101,9 @@ type SummaryOpts struct { // string. Help string + // Unit provides the unit of this Summary. + Unit string + // ConstLabels are used to attach fixed labels to this metric. Metrics // with the same fully-qualified name must have the same label names in // their ConstLabels. @@ -181,11 +184,12 @@ type SummaryVecOpts struct { // NewSummary creates a new Summary based on the provided SummaryOpts. func NewSummary(opts SummaryOpts) Summary { return newSummary( - NewDesc( + V2.NewDesc( BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, - nil, + UnconstrainedLabels(nil), opts.ConstLabels, + WithUnit(opts.Unit), ), opts, ) @@ -578,6 +582,7 @@ func (v2) NewSummaryVec(opts SummaryVecOpts) *SummaryVec { opts.Help, opts.VariableLabels, opts.ConstLabels, + WithUnit(opts.Unit), ) return &SummaryVec{ MetricVec: NewMetricVec(desc, func(lvs ...string) Metric { diff --git a/vendor/github.com/prometheus/client_golang/prometheus/timer.go b/vendor/github.com/prometheus/client_golang/prometheus/timer.go index 52344fef53..c1318ffb51 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/timer.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/timer.go @@ -37,10 +37,10 @@ type Timer struct { // or // // func TimeMeWithExemplar() { -// timer := NewTimer(myHistogram) -// defer timer.ObserveDurationWithExemplar(exemplar) -// // Do actual work. -// } +// timer := NewTimer(myHistogram) +// defer timer.ObserveDurationWithExemplar(exemplar) +// // Do actual work. +// } func NewTimer(o Observer) *Timer { return &Timer{ begin: time.Now(), @@ -66,7 +66,7 @@ func (t *Timer) ObserveDuration() time.Duration { // ObserveDurationWithExemplar is like ObserveDuration, but it will also // observe exemplar with the duration unless exemplar is nil or provided Observer can't -// be casted to ExemplarObserver. +// be cast to ExemplarObserver. func (t *Timer) ObserveDurationWithExemplar(exemplar Labels) time.Duration { d := time.Since(t.begin) eo, ok := t.observer.(ExemplarObserver) diff --git a/vendor/github.com/prometheus/client_golang/prometheus/vec.go b/vendor/github.com/prometheus/client_golang/prometheus/vec.go index 487b466563..121d2a9639 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/vec.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/vec.go @@ -193,9 +193,11 @@ func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) { // // Keeping the Metric for later use is possible (and should be considered if // performance is critical), but keep in mind that Reset, DeleteLabelValues and -// Delete can be used to delete the Metric from the MetricVec. In that case, the -// Metric will still exist, but it will not be exported anymore, even if a -// Metric with the same label values is created later. +// Delete can be used to delete the Metric from the MetricVec. In that case, if +// you have previously kept a reference to that Metric, the Metric object still +// exists and can be used, but it will not be exported anymore. If a Metric with +// the same label values is created later, updates to the old Metric reference +// will not be exported. // // An error is returned if the number of label values is not the same as the // number of variable labels in Desc (minus any curried labels). @@ -657,7 +659,7 @@ func inlineLabelValues(lvs []string, curry []curriedLabelValue) []string { } var labelsPool = &sync.Pool{ - New: func() interface{} { + New: func() any { return make(Labels) }, } diff --git a/vendor/github.com/prometheus/client_golang/prometheus/wrap.go b/vendor/github.com/prometheus/client_golang/prometheus/wrap.go index 2ed1285068..697f55558b 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/wrap.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/wrap.go @@ -230,6 +230,7 @@ func wrapDesc(desc *Desc, prefix string, labels Labels) *Desc { return &Desc{ fqName: desc.fqName, help: desc.help, + unit: desc.unit, variableLabels: desc.variableLabels, constLabelPairs: desc.constLabelPairs, err: fmt.Errorf("attempted wrapping with already existing label name %q", ln), @@ -238,8 +239,8 @@ func wrapDesc(desc *Desc, prefix string, labels Labels) *Desc { constLabels[ln] = lv } // NewDesc will do remaining validations. - newDesc := V2.NewDesc(prefix+desc.fqName, desc.help, desc.variableLabels, constLabels) - // Propagate errors if there was any. This will override any errer + newDesc := V2.NewDesc(prefix+desc.fqName, desc.help, desc.variableLabels, constLabels, WithUnit(desc.unit)) + // Propagate errors if there was any. This will override any error // created by NewDesc above, i.e. earlier errors get precedence. if desc.err != nil { newDesc.err = desc.err diff --git a/vendor/github.com/prometheus/common/expfmt/decode.go b/vendor/github.com/prometheus/common/expfmt/decode.go index 7b762370e2..8f8dc65d38 100644 --- a/vendor/github.com/prometheus/common/expfmt/decode.go +++ b/vendor/github.com/prometheus/common/expfmt/decode.go @@ -220,7 +220,7 @@ func extractSamples(f *dto.MetricFamily, o *DecodeOptions) (model.Vector, error) return extractSummary(o, f), nil case dto.MetricType_UNTYPED: return extractUntyped(o, f), nil - case dto.MetricType_HISTOGRAM: + case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: return extractHistogram(o, f), nil } return nil, fmt.Errorf("expfmt.extractSamples: unknown metric family type %v", f.GetType()) @@ -403,9 +403,13 @@ func extractHistogram(o *DecodeOptions, f *dto.MetricFamily) model.Vector { infSeen = true } + v := q.GetCumulativeCountFloat() + if v <= 0 { + v = float64(q.GetCumulativeCount()) + } samples = append(samples, &model.Sample{ Metric: model.Metric(lset), - Value: model.SampleValue(q.GetCumulativeCount()), + Value: model.SampleValue(v), Timestamp: timestamp, }) } @@ -428,9 +432,13 @@ func extractHistogram(o *DecodeOptions, f *dto.MetricFamily) model.Vector { } lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count") + v := m.Histogram.GetSampleCountFloat() + if v <= 0 { + v = float64(m.Histogram.GetSampleCount()) + } count := &model.Sample{ Metric: model.Metric(lset), - Value: model.SampleValue(m.Histogram.GetSampleCount()), + Value: model.SampleValue(v), Timestamp: timestamp, } samples = append(samples, count) diff --git a/vendor/github.com/prometheus/common/expfmt/expfmt.go b/vendor/github.com/prometheus/common/expfmt/expfmt.go index c34c7de432..10bf35708c 100644 --- a/vendor/github.com/prometheus/common/expfmt/expfmt.go +++ b/vendor/github.com/prometheus/common/expfmt/expfmt.go @@ -45,6 +45,7 @@ const ( // The Content-Type values for the different wire protocols. Do not do direct // comparisons to these constants, instead use the comparison functions. + // // Deprecated: Use expfmt.NewFormat(expfmt.TypeUnknown) instead. FmtUnknown Format = `` // Deprecated: Use expfmt.NewFormat(expfmt.TypeTextPlain) instead. @@ -121,7 +122,7 @@ func NewOpenMetricsFormat(version string) (Format, error) { // removed. func (f Format) WithEscapingScheme(s model.EscapingScheme) Format { var terms []string - for _, p := range strings.Split(string(f), ";") { + for p := range strings.SplitSeq(string(f), ";") { toks := strings.Split(p, "=") if len(toks) != 2 { trimmed := strings.TrimSpace(p) @@ -193,7 +194,7 @@ func (f Format) FormatType() FormatType { // "escaping" term exists, that will be used. Otherwise, the global default will // be returned. func (f Format) ToEscapingScheme() model.EscapingScheme { - for _, p := range strings.Split(string(f), ";") { + for p := range strings.SplitSeq(string(f), ";") { toks := strings.Split(p, "=") if len(toks) != 2 { continue diff --git a/vendor/github.com/prometheus/common/expfmt/fuzz.go b/vendor/github.com/prometheus/common/expfmt/fuzz.go index 0290f6abc4..872c0c15b4 100644 --- a/vendor/github.com/prometheus/common/expfmt/fuzz.go +++ b/vendor/github.com/prometheus/common/expfmt/fuzz.go @@ -13,7 +13,6 @@ // Build only when actually fuzzing //go:build gofuzz -// +build gofuzz package expfmt diff --git a/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go b/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go index 8dbf6d04ed..0480e7af5b 100644 --- a/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go +++ b/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go @@ -30,7 +30,6 @@ import ( type encoderOption struct { withCreatedLines bool - withUnit bool } type EncoderOption func(*encoderOption) @@ -51,17 +50,6 @@ func WithCreatedLines() EncoderOption { } } -// WithUnit is an EncoderOption enabling a set unit to be written to the output -// and to be added to the metric name, if it's not there already, as a suffix. -// Without opting in this way, the unit will not be added to the metric name and, -// on top of that, the unit will not be passed onto the output, even if it -// were declared in the *dto.MetricFamily struct, i.e. even if in.Unit !=nil. -func WithUnit() EncoderOption { - return func(t *encoderOption) { - t.withUnit = true - } -} - // MetricFamilyToOpenMetrics converts a MetricFamily proto message into the // OpenMetrics text format and writes the resulting lines to 'out'. It returns // the number of bytes written and any error encountered. The output will have @@ -99,15 +87,6 @@ func WithUnit() EncoderOption { // its type will be set to `unknown` in that case to avoid invalid OpenMetrics // output. // -// - According to the OM specs, the `# UNIT` line is optional, but if populated, -// the unit has to be present in the metric name as its suffix: -// (see https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#unit). -// However, in order to accommodate any potential scenario where such a change in the -// metric name is not desirable, the users are here given the choice of either explicitly -// opt in, in case they wish for the unit to be included in the output AND in the metric name -// as a suffix (see the description of the WithUnit function above), -// or not to opt in, in case they don't want for any of that to happen. -// // - No support for the following (optional) features: info type, // stateset type, gaugehistogram type. // @@ -151,47 +130,44 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E if metricType == dto.MetricType_COUNTER && strings.HasSuffix(compliantName, "_total") { compliantName = name[:len(name)-6] } - if toOM.withUnit && in.Unit != nil && !strings.HasSuffix(compliantName, "_"+*in.Unit) { - compliantName = compliantName + "_" + *in.Unit - } // Comments, first HELP, then TYPE. if in.Help != nil { n, err = w.WriteString("# HELP ") written += n if err != nil { - return + return written, err } n, err = writeName(w, compliantName) written += n if err != nil { - return + return written, err } err = w.WriteByte(' ') written++ if err != nil { - return + return written, err } n, err = writeEscapedString(w, *in.Help, true) written += n if err != nil { - return + return written, err } err = w.WriteByte('\n') written++ if err != nil { - return + return written, err } } n, err = w.WriteString("# TYPE ") written += n if err != nil { - return + return written, err } n, err = writeName(w, compliantName) written += n if err != nil { - return + return written, err } switch metricType { case dto.MetricType_COUNTER: @@ -208,39 +184,41 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E n, err = w.WriteString(" unknown\n") case dto.MetricType_HISTOGRAM: n, err = w.WriteString(" histogram\n") + case dto.MetricType_GAUGE_HISTOGRAM: + n, err = w.WriteString(" gaugehistogram\n") default: return written, fmt.Errorf("unknown metric type %s", metricType.String()) } written += n if err != nil { - return + return written, err } - if toOM.withUnit && in.Unit != nil { + if in.Unit != nil { n, err = w.WriteString("# UNIT ") written += n if err != nil { - return + return written, err } n, err = writeName(w, compliantName) written += n if err != nil { - return + return written, err } err = w.WriteByte(' ') written++ if err != nil { - return + return written, err } n, err = writeEscapedString(w, *in.Unit, true) written += n if err != nil { - return + return written, err } err = w.WriteByte('\n') written++ if err != nil { - return + return written, err } } @@ -304,7 +282,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E ) written += n if err != nil { - return + return written, err } } n, err = writeOpenMetricsSample( @@ -314,7 +292,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E ) written += n if err != nil { - return + return written, err } n, err = writeOpenMetricsSample( w, compliantName, "_count", metric, "", 0, @@ -325,7 +303,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "", metric, "", 0, metric.Summary.GetCreatedTimestamp()) n += createdTsBytesWritten } - case dto.MetricType_HISTOGRAM: + case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: if metric.Histogram == nil { return written, fmt.Errorf( "expected histogram in metric %s %s", compliantName, metric, @@ -333,6 +311,12 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E } infSeen := false for _, b := range metric.Histogram.Bucket { + if b.GetCumulativeCountFloat() > 0 { + return written, fmt.Errorf( + "OpenMetrics v1.0 does not support float histogram %s %s", + compliantName, metric, + ) + } n, err = writeOpenMetricsSample( w, compliantName, "_bucket", metric, model.BucketLabel, b.GetUpperBound(), @@ -341,7 +325,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E ) written += n if err != nil { - return + return written, err } if math.IsInf(b.GetUpperBound(), +1) { infSeen = true @@ -354,9 +338,12 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E 0, metric.Histogram.GetSampleCount(), true, nil, ) + // We do not check for a float sample count here + // because we will check for it below (and error + // out if needed). written += n if err != nil { - return + return written, err } } n, err = writeOpenMetricsSample( @@ -366,7 +353,13 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E ) written += n if err != nil { - return + return written, err + } + if metric.Histogram.GetSampleCountFloat() > 0 { + return written, fmt.Errorf( + "OpenMetrics v1.0 does not support float histogram %s %s", + compliantName, metric, + ) } n, err = writeOpenMetricsSample( w, compliantName, "_count", metric, "", 0, @@ -384,10 +377,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E } written += n if err != nil { - return + return written, err } } - return + return written, err } // FinalizeOpenMetrics writes the final `# EOF\n` line required by OpenMetrics. diff --git a/vendor/github.com/prometheus/common/expfmt/text_create.go b/vendor/github.com/prometheus/common/expfmt/text_create.go index c4e9c1bbc3..f4074ae9a3 100644 --- a/vendor/github.com/prometheus/common/expfmt/text_create.go +++ b/vendor/github.com/prometheus/common/expfmt/text_create.go @@ -42,12 +42,12 @@ const ( var ( bufPool = sync.Pool{ - New: func() interface{} { + New: func() any { return bufio.NewWriter(io.Discard) }, } numBufPool = sync.Pool{ - New: func() interface{} { + New: func() any { b := make([]byte, 0, initialNumBufSize) return &b }, @@ -108,38 +108,38 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e n, err = w.WriteString("# HELP ") written += n if err != nil { - return + return written, err } n, err = writeName(w, name) written += n if err != nil { - return + return written, err } err = w.WriteByte(' ') written++ if err != nil { - return + return written, err } n, err = writeEscapedString(w, *in.Help, false) written += n if err != nil { - return + return written, err } err = w.WriteByte('\n') written++ if err != nil { - return + return written, err } } n, err = w.WriteString("# TYPE ") written += n if err != nil { - return + return written, err } n, err = writeName(w, name) written += n if err != nil { - return + return written, err } metricType := in.GetType() switch metricType { @@ -151,14 +151,17 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e n, err = w.WriteString(" summary\n") case dto.MetricType_UNTYPED: n, err = w.WriteString(" untyped\n") - case dto.MetricType_HISTOGRAM: + case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: + // The classic Prometheus text format has no notion of a gauge + // histogram. We render a gauge histogram in the same way as a + // regular histogram. n, err = w.WriteString(" histogram\n") default: return written, fmt.Errorf("unknown metric type %s", metricType.String()) } written += n if err != nil { - return + return written, err } // Finally the samples, one line for each. @@ -208,7 +211,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e ) written += n if err != nil { - return + return written, err } } n, err = writeSample( @@ -217,13 +220,13 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e ) written += n if err != nil { - return + return written, err } n, err = writeSample( w, name, "_count", metric, "", 0, float64(metric.Summary.GetSampleCount()), ) - case dto.MetricType_HISTOGRAM: + case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: if metric.Histogram == nil { return written, fmt.Errorf( "expected histogram in metric %s %s", name, metric, @@ -231,28 +234,36 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e } infSeen := false for _, b := range metric.Histogram.Bucket { + v := b.GetCumulativeCountFloat() + if v == 0 { + v = float64(b.GetCumulativeCount()) + } n, err = writeSample( w, name, "_bucket", metric, model.BucketLabel, b.GetUpperBound(), - float64(b.GetCumulativeCount()), + v, ) written += n if err != nil { - return + return written, err } if math.IsInf(b.GetUpperBound(), +1) { infSeen = true } } if !infSeen { + v := metric.Histogram.GetSampleCountFloat() + if v == 0 { + v = float64(metric.Histogram.GetSampleCount()) + } n, err = writeSample( w, name, "_bucket", metric, model.BucketLabel, math.Inf(+1), - float64(metric.Histogram.GetSampleCount()), + v, ) written += n if err != nil { - return + return written, err } } n, err = writeSample( @@ -261,12 +272,13 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e ) written += n if err != nil { - return + return written, err } - n, err = writeSample( - w, name, "_count", metric, "", 0, - float64(metric.Histogram.GetSampleCount()), - ) + v := metric.Histogram.GetSampleCountFloat() + if v == 0 { + v = float64(metric.Histogram.GetSampleCount()) + } + n, err = writeSample(w, name, "_count", metric, "", 0, v) default: return written, fmt.Errorf( "unexpected type in metric %s %s", name, metric, @@ -274,10 +286,10 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e } written += n if err != nil { - return + return written, err } } - return + return written, err } // writeSample writes a single sample in text format to w, given the metric diff --git a/vendor/github.com/prometheus/common/expfmt/text_parse.go b/vendor/github.com/prometheus/common/expfmt/text_parse.go index 8f2edde324..4ce1f40b81 100644 --- a/vendor/github.com/prometheus/common/expfmt/text_parse.go +++ b/vendor/github.com/prometheus/common/expfmt/text_parse.go @@ -48,8 +48,10 @@ func (e ParseError) Error() string { return fmt.Sprintf("text format parsing error in line %d: %s", e.Line, e.Msg) } -// TextParser is used to parse the simple and flat text-based exchange format. Its -// zero value is ready to use. +// TextParser is used to parse the simple and flat text-based exchange format. +// +// TextParser instances must be created with NewTextParser, the zero value of +// TextParser is invalid. type TextParser struct { metricFamiliesByName map[string]*dto.MetricFamily buf *bufio.Reader // Where the parsed input is read through. @@ -129,9 +131,44 @@ func (p *TextParser) TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricF if p.err != nil && errors.Is(p.err, io.EOF) { p.parseError("unexpected end of input stream") } + for _, histogramMetric := range p.histograms { + normalizeHistogram(histogramMetric.GetHistogram()) + } return p.metricFamiliesByName, p.err } +// normalizeHistogram makes sure that all the buckets and the count in each +// histogram is either completely float or completely integer. +func normalizeHistogram(histogram *dto.Histogram) { + if histogram == nil { + return + } + anyFloats := false + if histogram.GetSampleCountFloat() != 0 { + anyFloats = true + } else { + for _, b := range histogram.GetBucket() { + if b.GetCumulativeCountFloat() != 0 { + anyFloats = true + break + } + } + } + if !anyFloats { + return + } + if histogram.GetSampleCountFloat() == 0 { + histogram.SampleCountFloat = proto.Float64(float64(histogram.GetSampleCount())) + histogram.SampleCount = nil + } + for _, b := range histogram.GetBucket() { + if b.GetCumulativeCountFloat() == 0 { + b.CumulativeCountFloat = proto.Float64(float64(b.GetCumulativeCount())) + b.CumulativeCount = nil + } + } +} + func (p *TextParser) reset(in io.Reader) { p.metricFamiliesByName = map[string]*dto.MetricFamily{} p.currentLabelPairs = nil @@ -281,7 +318,9 @@ func (p *TextParser) readingLabels() stateFn { // Summaries/histograms are special. We have to reset the // currentLabels map, currentQuantile and currentBucket before starting to // read labels. - if p.currentMF.GetType() == dto.MetricType_SUMMARY || p.currentMF.GetType() == dto.MetricType_HISTOGRAM { + if p.currentMF.GetType() == dto.MetricType_SUMMARY || + p.currentMF.GetType() == dto.MetricType_HISTOGRAM || + p.currentMF.GetType() == dto.MetricType_GAUGE_HISTOGRAM { p.currentLabels = map[string]string{} p.currentLabels[string(model.MetricNameLabel)] = p.currentMF.GetName() p.currentQuantile = math.NaN() @@ -300,6 +339,16 @@ func (p *TextParser) startLabelName() stateFn { return nil // Unexpected end of input. } if p.currentByte == '}' { + if p.currentMF == nil { + // The closing brace was reached before any metric name was read, + // e.g. for the input "{}". There is no metric to attach labels to, + // so this is a malformed exposition. This mirrors the guard in + // startLabelValue. currentMF (not currentMetric) is checked because + // reset only clears currentMF between parses. + p.parseError("invalid metric name") + p.currentLabelPairs = nil + return nil + } p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPairs...) p.currentLabelPairs = nil if p.skipBlankTab(); p.err != nil { @@ -374,7 +423,9 @@ func (p *TextParser) startLabelName() stateFn { // Special summary/histogram treatment. Don't add 'quantile' and 'le' // labels to 'real' labels. if (p.currentMF.GetType() != dto.MetricType_SUMMARY || p.currentLabelPair.GetName() != model.QuantileLabel) && - (p.currentMF.GetType() != dto.MetricType_HISTOGRAM || p.currentLabelPair.GetName() != model.BucketLabel) { + ((p.currentMF.GetType() != dto.MetricType_HISTOGRAM && + p.currentMF.GetType() != dto.MetricType_GAUGE_HISTOGRAM) || + p.currentLabelPair.GetName() != model.BucketLabel) { p.currentLabelPairs = append(p.currentLabelPairs, p.currentLabelPair) } // Check for duplicate label names. @@ -425,7 +476,7 @@ func (p *TextParser) startLabelValue() stateFn { } } // Similar special treatment of histograms. - if p.currentMF.GetType() == dto.MetricType_HISTOGRAM { + if p.currentMF.GetType() == dto.MetricType_HISTOGRAM || p.currentMF.GetType() == dto.MetricType_GAUGE_HISTOGRAM { if p.currentLabelPair.GetName() == model.BucketLabel { if p.currentBucket, p.err = parseFloat(p.currentLabelPair.GetValue()); p.err != nil { // Create a more helpful error message. @@ -476,7 +527,7 @@ func (p *TextParser) readingValue() stateFn { p.summaries[signature] = p.currentMetric p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric) } - case dto.MetricType_HISTOGRAM: + case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: signature := model.LabelsToSignature(p.currentLabels) if histogram := p.histograms[signature]; histogram != nil { p.currentMetric = histogram @@ -522,24 +573,38 @@ func (p *TextParser) readingValue() stateFn { }, ) } - case dto.MetricType_HISTOGRAM: + case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: // *sigh* if p.currentMetric.Histogram == nil { p.currentMetric.Histogram = &dto.Histogram{} } switch { case p.currentIsHistogramCount: - p.currentMetric.Histogram.SampleCount = proto.Uint64(uint64(value)) + if uintValue := uint64(value); value == float64(uintValue) { + p.currentMetric.Histogram.SampleCount = proto.Uint64(uintValue) + } else { + if value < 0 { + p.parseError(fmt.Sprintf("negative count for histogram %q", p.currentMF.GetName())) + return nil + } + p.currentMetric.Histogram.SampleCountFloat = proto.Float64(value) + } case p.currentIsHistogramSum: p.currentMetric.Histogram.SampleSum = proto.Float64(value) case !math.IsNaN(p.currentBucket): - p.currentMetric.Histogram.Bucket = append( - p.currentMetric.Histogram.Bucket, - &dto.Bucket{ - UpperBound: proto.Float64(p.currentBucket), - CumulativeCount: proto.Uint64(uint64(value)), - }, - ) + b := &dto.Bucket{ + UpperBound: proto.Float64(p.currentBucket), + } + if uintValue := uint64(value); value == float64(uintValue) { + b.CumulativeCount = proto.Uint64(uintValue) + } else { + if value < 0 { + p.parseError(fmt.Sprintf("negative bucket population for histogram %q", p.currentMF.GetName())) + return nil + } + b.CumulativeCountFloat = proto.Float64(value) + } + p.currentMetric.Histogram.Bucket = append(p.currentMetric.Histogram.Bucket, b) } default: p.err = fmt.Errorf("unexpected type for metric name %q", p.currentMF.GetName()) @@ -602,10 +667,18 @@ func (p *TextParser) readingType() stateFn { if p.readTokenUntilNewline(false); p.err != nil { return nil // Unexpected end of input. } - metricType, ok := dto.MetricType_value[strings.ToUpper(p.currentToken.String())] + typ := strings.ToUpper(p.currentToken.String()) // Tolerate any combination of upper and lower case. + metricType, ok := dto.MetricType_value[typ] // Tolerate "gauge_histogram" (not originally part of the text format). if !ok { - p.parseError(fmt.Sprintf("unknown metric type %q", p.currentToken.String())) - return nil + // We also want to tolerate "gaugehistogram" to mark a gauge + // histogram, because that string is used in OpenMetrics. Note, + // however, that gauge histograms do not officially exist in the + // classic text format. + if typ != "GAUGEHISTOGRAM" { + p.parseError(fmt.Sprintf("unknown metric type %q", p.currentToken.String())) + return nil + } + metricType = int32(dto.MetricType_GAUGE_HISTOGRAM) } p.currentMF.Type = dto.MetricType(metricType).Enum() return p.startOfLine @@ -855,7 +928,8 @@ func (p *TextParser) setOrCreateCurrentMF() { } histogramName := histogramMetricName(name) if p.currentMF = p.metricFamiliesByName[histogramName]; p.currentMF != nil { - if p.currentMF.GetType() == dto.MetricType_HISTOGRAM { + if p.currentMF.GetType() == dto.MetricType_HISTOGRAM || + p.currentMF.GetType() == dto.MetricType_GAUGE_HISTOGRAM { if isCount(name) { p.currentIsHistogramCount = true } diff --git a/vendor/github.com/prometheus/common/model/labels.go b/vendor/github.com/prometheus/common/model/labels.go index dfeb34be5f..29688a13c8 100644 --- a/vendor/github.com/prometheus/common/model/labels.go +++ b/vendor/github.com/prometheus/common/model/labels.go @@ -124,7 +124,7 @@ func (ln LabelName) IsValidLegacy() bool { } // UnmarshalYAML implements the yaml.Unmarshaler interface. -func (ln *LabelName) UnmarshalYAML(unmarshal func(interface{}) error) error { +func (ln *LabelName) UnmarshalYAML(unmarshal func(any) error) error { var s string if err := unmarshal(&s); err != nil { return err diff --git a/vendor/github.com/prometheus/common/model/labelset.go b/vendor/github.com/prometheus/common/model/labelset.go index 9de47b2568..6010b26a88 100644 --- a/vendor/github.com/prometheus/common/model/labelset.go +++ b/vendor/github.com/prometheus/common/model/labelset.go @@ -16,6 +16,7 @@ package model import ( "encoding/json" "fmt" + "maps" "sort" ) @@ -107,9 +108,7 @@ func (ls LabelSet) Before(o LabelSet) bool { // Clone returns a copy of the label set. func (ls LabelSet) Clone() LabelSet { lsn := make(LabelSet, len(ls)) - for ln, lv := range ls { - lsn[ln] = lv - } + maps.Copy(lsn, ls) return lsn } @@ -117,13 +116,9 @@ func (ls LabelSet) Clone() LabelSet { func (ls LabelSet) Merge(other LabelSet) LabelSet { result := make(LabelSet, len(ls)) - for k, v := range ls { - result[k] = v - } + maps.Copy(result, ls) - for k, v := range other { - result[k] = v - } + maps.Copy(result, other) return result } diff --git a/vendor/github.com/prometheus/common/model/metric.go b/vendor/github.com/prometheus/common/model/metric.go index 3feebf328a..2fe461511d 100644 --- a/vendor/github.com/prometheus/common/model/metric.go +++ b/vendor/github.com/prometheus/common/model/metric.go @@ -17,6 +17,7 @@ import ( "encoding/json" "errors" "fmt" + "maps" "regexp" "sort" "strconv" @@ -24,7 +25,6 @@ import ( "unicode/utf8" dto "github.com/prometheus/client_model/go" - "go.yaml.in/yaml/v2" "google.golang.org/protobuf/proto" ) @@ -78,14 +78,6 @@ const ( UTF8Validation ) -var _ interface { - yaml.Marshaler - yaml.Unmarshaler - json.Marshaler - json.Unmarshaler - fmt.Stringer -} = new(ValidationScheme) - // String returns the string representation of s. func (s ValidationScheme) String() string { switch s { @@ -267,9 +259,7 @@ func (m Metric) Before(o Metric) bool { // Clone returns a copy of the Metric. func (m Metric) Clone() Metric { clone := make(Metric, len(m)) - for k, v := range m { - clone[k] = v - } + maps.Copy(clone, m) return clone } diff --git a/vendor/github.com/prometheus/common/model/time.go b/vendor/github.com/prometheus/common/model/time.go index 1730b0fdc1..0854753f4a 100644 --- a/vendor/github.com/prometheus/common/model/time.go +++ b/vendor/github.com/prometheus/common/model/time.go @@ -123,44 +123,38 @@ func (t Time) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the json.Unmarshaler interface. func (t *Time) UnmarshalJSON(b []byte) error { - p := strings.Split(string(b), ".") - switch len(p) { - case 1: - v, err := strconv.ParseInt(p[0], 10, 64) + base, frac, found := strings.Cut(string(b), ".") + if !found { + v, err := strconv.ParseInt(base, 10, 64) if err != nil { return err } *t = Time(v * second) - - case 2: - v, err := strconv.ParseInt(p[0], 10, 64) + } else { + v, err := strconv.ParseInt(base, 10, 64) if err != nil { return err } - v *= second - prec := dotPrecision - len(p[1]) + prec := dotPrecision - len(frac) if prec < 0 { - p[1] = p[1][:dotPrecision] - } else if prec > 0 { - p[1] += strings.Repeat("0", prec) + frac = frac[:dotPrecision] } - - va, err := strconv.ParseInt(p[1], 10, 32) + va, err := strconv.ParseInt(frac, 10, 32) if err != nil { return err } - - // If the value was something like -0.1 the negative is lost in the - // parsing because of the leading zero, this ensures that we capture it. - if len(p[0]) > 0 && p[0][0] == '-' && v+va > 0 { - *t = Time(v+va) * -1 - } else { - *t = Time(v + va) + switch prec { + case 1: + va *= 10 + case 2: + va *= 100 } - default: - return fmt.Errorf("invalid time %q", string(b)) + if len(base) > 0 && base[0] == '-' { + va = -va + } + *t = Time(v*second + va) } return nil } @@ -340,12 +334,12 @@ func (d *Duration) UnmarshalText(text []byte) error { } // MarshalYAML implements the yaml.Marshaler interface. -func (d Duration) MarshalYAML() (interface{}, error) { +func (d Duration) MarshalYAML() (any, error) { return d.String(), nil } // UnmarshalYAML implements the yaml.Unmarshaler interface. -func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error { +func (d *Duration) UnmarshalYAML(unmarshal func(any) error) error { var s string if err := unmarshal(&s); err != nil { return err diff --git a/vendor/github.com/prometheus/common/model/value.go b/vendor/github.com/prometheus/common/model/value.go index a9995a37ee..8dffd9c4a5 100644 --- a/vendor/github.com/prometheus/common/model/value.go +++ b/vendor/github.com/prometheus/common/model/value.go @@ -259,13 +259,13 @@ func (s Scalar) String() string { // MarshalJSON implements json.Marshaler. func (s Scalar) MarshalJSON() ([]byte, error) { v := strconv.FormatFloat(float64(s.Value), 'f', -1, 64) - return json.Marshal([...]interface{}{s.Timestamp, v}) + return json.Marshal([...]any{s.Timestamp, v}) } // UnmarshalJSON implements json.Unmarshaler. func (s *Scalar) UnmarshalJSON(b []byte) error { var f string - v := [...]interface{}{&s.Timestamp, &f} + v := [...]any{&s.Timestamp, &f} if err := json.Unmarshal(b, &v); err != nil { return err @@ -291,12 +291,12 @@ func (s *String) String() string { // MarshalJSON implements json.Marshaler. func (s String) MarshalJSON() ([]byte, error) { - return json.Marshal([]interface{}{s.Timestamp, s.Value}) + return json.Marshal([]any{s.Timestamp, s.Value}) } // UnmarshalJSON implements json.Unmarshaler. func (s *String) UnmarshalJSON(b []byte) error { - v := [...]interface{}{&s.Timestamp, &s.Value} + v := [...]any{&s.Timestamp, &s.Value} return json.Unmarshal(b, &v) } diff --git a/vendor/github.com/prometheus/common/model/value_float.go b/vendor/github.com/prometheus/common/model/value_float.go index 6bfc757d18..b7d93615e2 100644 --- a/vendor/github.com/prometheus/common/model/value_float.go +++ b/vendor/github.com/prometheus/common/model/value_float.go @@ -79,7 +79,7 @@ func (s SamplePair) MarshalJSON() ([]byte, error) { if err != nil { return nil, err } - return []byte(fmt.Sprintf("[%s,%s]", t, v)), nil + return fmt.Appendf(nil, "[%s,%s]", t, v), nil } // UnmarshalJSON implements json.Unmarshaler. diff --git a/vendor/github.com/prometheus/common/model/value_histogram.go b/vendor/github.com/prometheus/common/model/value_histogram.go index 91ce5b7a45..f27856ccc4 100644 --- a/vendor/github.com/prometheus/common/model/value_histogram.go +++ b/vendor/github.com/prometheus/common/model/value_histogram.go @@ -67,11 +67,11 @@ func (s HistogramBucket) MarshalJSON() ([]byte, error) { if err != nil { return nil, err } - return []byte(fmt.Sprintf("[%s,%s,%s,%s]", b, l, u, c)), nil + return fmt.Appendf(nil, "[%s,%s,%s,%s]", b, l, u, c), nil } func (s *HistogramBucket) UnmarshalJSON(buf []byte) error { - tmp := []interface{}{&s.Boundaries, &s.Lower, &s.Upper, &s.Count} + tmp := []any{&s.Boundaries, &s.Lower, &s.Upper, &s.Count} wantLen := len(tmp) if err := json.Unmarshal(buf, &tmp); err != nil { return err @@ -152,11 +152,11 @@ func (s SampleHistogramPair) MarshalJSON() ([]byte, error) { if err != nil { return nil, err } - return []byte(fmt.Sprintf("[%s,%s]", t, v)), nil + return fmt.Appendf(nil, "[%s,%s]", t, v), nil } func (s *SampleHistogramPair) UnmarshalJSON(buf []byte) error { - tmp := []interface{}{&s.Timestamp, &s.Histogram} + tmp := []any{&s.Timestamp, &s.Histogram} wantLen := len(tmp) if err := json.Unmarshal(buf, &tmp); err != nil { return err diff --git a/vendor/github.com/prometheus/procfs/.golangci.yml b/vendor/github.com/prometheus/procfs/.golangci.yml index 3c3bf910fd..eac920ba80 100644 --- a/vendor/github.com/prometheus/procfs/.golangci.yml +++ b/vendor/github.com/prometheus/procfs/.golangci.yml @@ -1,7 +1,9 @@ version: "2" linters: enable: + - errorlint - forbidigo + - gocritic - godot - misspell - revive @@ -11,6 +13,20 @@ linters: forbid: - pattern: ^fmt\.Print.*$ msg: Do not commit print statements. + gocritic: + enable-all: true + disabled-checks: + - commentFormatting + - commentedOutCode + - deferInLoop + - filepathJoin + - hugeParam + - importShadow + - paramTypeCombine + - rangeValCopy + - tooManyResultsChecker + - unnamedResult + - whyNoLint godot: exclude: # Ignore "See: URL". @@ -18,17 +34,21 @@ linters: capital: true misspell: locale: US + revive: + rules: + - name: var-naming + # TODO(SuperQ): See: https://github.com/prometheus/prometheus/issues/17766 + arguments: + - [] + - [] + - - skip-package-name-checks: true exclusions: - generated: lax presets: - comments - common-false-positives - legacy - std-error-handling - paths: - - third_party$ - - builtin$ - - examples$ + warn-unused: true formatters: enable: - gofmt @@ -37,9 +57,3 @@ formatters: goimports: local-prefixes: - github.com/prometheus/procfs - exclusions: - generated: lax - paths: - - third_party$ - - builtin$ - - examples$ diff --git a/vendor/github.com/prometheus/procfs/Makefile b/vendor/github.com/prometheus/procfs/Makefile index 7edfe4d093..bce50a19c5 100644 --- a/vendor/github.com/prometheus/procfs/Makefile +++ b/vendor/github.com/prometheus/procfs/Makefile @@ -1,4 +1,4 @@ -# Copyright 2018 The Prometheus Authors +# Copyright The Prometheus Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/Makefile.common b/vendor/github.com/prometheus/procfs/Makefile.common index 0ed55c2ba2..a7c5f553e1 100644 --- a/vendor/github.com/prometheus/procfs/Makefile.common +++ b/vendor/github.com/prometheus/procfs/Makefile.common @@ -1,4 +1,4 @@ -# Copyright 2018 The Prometheus Authors +# Copyright The Prometheus Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -33,7 +33,7 @@ GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) GO_VERSION ?= $(shell $(GO) version) -GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION))Error Parsing File +GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.') PROMU := $(FIRST_GOPATH)/bin/promu @@ -55,13 +55,14 @@ ifneq ($(shell command -v gotestsum 2> /dev/null),) endif endif -PROMU_VERSION ?= 0.17.0 +PROMU_VERSION ?= 0.20.0 PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= -GOLANGCI_LINT_VERSION ?= v2.0.2 +GOLANGCI_LINT_VERSION ?= v2.11.4 +GOLANGCI_FMT_OPTS ?= # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) @@ -81,11 +82,32 @@ endif PREFIX ?= $(shell pwd) BIN_DIR ?= $(shell pwd) DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) -DOCKERFILE_PATH ?= ./Dockerfile DOCKERBUILD_CONTEXT ?= ./ DOCKER_REPO ?= prom -DOCKER_ARCHS ?= amd64 +# Check if deprecated DOCKERFILE_PATH is set +ifdef DOCKERFILE_PATH +$(error DOCKERFILE_PATH is deprecated. Use DOCKERFILE_VARIANTS ?= $(DOCKERFILE_PATH) in the Makefile) +endif + +DOCKER_ARCHS ?= amd64 arm64 armv7 ppc64le riscv64 s390x +DOCKERFILE_VARIANTS ?= $(wildcard Dockerfile Dockerfile.*) + +# Function to extract variant from Dockerfile label. +# Returns the variant name from io.prometheus.image.variant label, or "default" if not found. +define dockerfile_variant +$(strip $(or $(shell sed -n 's/.*io\.prometheus\.image\.variant="\([^"]*\)".*/\1/p' $(1)),default)) +endef + +# Check for duplicate variant names (including default for Dockerfiles without labels). +DOCKERFILE_VARIANT_NAMES := $(foreach df,$(DOCKERFILE_VARIANTS),$(call dockerfile_variant,$(df))) +DOCKERFILE_VARIANT_NAMES_SORTED := $(sort $(DOCKERFILE_VARIANT_NAMES)) +ifneq ($(words $(DOCKERFILE_VARIANT_NAMES)),$(words $(DOCKERFILE_VARIANT_NAMES_SORTED))) +$(error Duplicate variant names found. Each Dockerfile must have a unique io.prometheus.image.variant label, and only one can be without a label (default)) +endif + +# Build variant:dockerfile pairs for shell iteration. +DOCKERFILE_VARIANTS_WITH_NAMES := $(foreach df,$(DOCKERFILE_VARIANTS),$(call dockerfile_variant,$(df)):$(df)) BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) @@ -111,7 +133,7 @@ common-all: precheck style check_license lint yamllint unused build test .PHONY: common-style common-style: @echo ">> checking code style" - @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \ + @fmtRes=$$($(GOFMT) -d $$(git ls-files '*.go' ':!:vendor/*' || find . -path ./vendor -prune -o -name '*.go' -print)); \ if [ -n "$${fmtRes}" ]; then \ echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ echo "Please ensure you are using $$($(GO) version) for formatting code."; \ @@ -121,13 +143,19 @@ common-style: .PHONY: common-check_license common-check_license: @echo ">> checking license header" - @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \ + @licRes=$$(for file in $$(git ls-files '*.go' ':!:vendor/*' || find . -path ./vendor -prune -o -type f -iname '*.go' -print) ; do \ awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \ done); \ if [ -n "$${licRes}" ]; then \ echo "license header checking failed:"; echo "$${licRes}"; \ exit 1; \ fi + @echo ">> checking for copyright years 2026 or later" + @futureYearRes=$$(git grep -E 'Copyright (202[6-9]|20[3-9][0-9])' -- '*.go' ':!:vendor/*' || true); \ + if [ -n "$${futureYearRes}" ]; then \ + echo "Files with copyright year 2026 or later found (should use 'Copyright The Prometheus Authors'):"; echo "$${futureYearRes}"; \ + exit 1; \ + fi .PHONY: common-deps common-deps: @@ -138,7 +166,7 @@ common-deps: update-go-deps: @echo ">> updating Go dependencies" @for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \ - $(GO) get -d $$m; \ + $(GO) get $$m; \ done $(GO) mod tidy @@ -156,9 +184,13 @@ $(GOTEST_DIR): @mkdir -p $@ .PHONY: common-format -common-format: +common-format: $(GOLANGCI_LINT) @echo ">> formatting code" $(GO) fmt $(pkgs) +ifdef GOLANGCI_LINT + @echo ">> formatting code with golangci-lint" + $(GOLANGCI_LINT) fmt $(GOLANGCI_FMT_OPTS) +endif .PHONY: common-vet common-vet: @@ -215,28 +247,142 @@ common-docker-repo-name: .PHONY: common-docker $(BUILD_DOCKER_ARCHS) common-docker: $(BUILD_DOCKER_ARCHS) $(BUILD_DOCKER_ARCHS): common-docker-%: - docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \ - -f $(DOCKERFILE_PATH) \ - --build-arg ARCH="$*" \ - --build-arg OS="linux" \ - $(DOCKERBUILD_CONTEXT) + @for variant in $(DOCKERFILE_VARIANTS_WITH_NAMES); do \ + dockerfile=$${variant#*:}; \ + variant_name=$${variant%%:*}; \ + distroless_arch="$*"; \ + if [ "$*" = "armv7" ]; then \ + distroless_arch="arm"; \ + fi; \ + if [ "$$dockerfile" = "Dockerfile" ]; then \ + echo "Building default variant ($$variant_name) for linux-$* using $$dockerfile"; \ + docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \ + -f $$dockerfile \ + --build-arg ARCH="$*" \ + --build-arg OS="linux" \ + --build-arg DISTROLESS_ARCH="$$distroless_arch" \ + $(DOCKERBUILD_CONTEXT); \ + if [ "$$variant_name" != "default" ]; then \ + echo "Tagging default variant with $$variant_name suffix"; \ + docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \ + "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name"; \ + fi; \ + else \ + echo "Building $$variant_name variant for linux-$* using $$dockerfile"; \ + docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name" \ + -f $$dockerfile \ + --build-arg ARCH="$*" \ + --build-arg OS="linux" \ + --build-arg DISTROLESS_ARCH="$$distroless_arch" \ + $(DOCKERBUILD_CONTEXT); \ + fi; \ + done .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) common-docker-publish: $(PUBLISH_DOCKER_ARCHS) $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: - docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" + @for variant in $(DOCKERFILE_VARIANTS_WITH_NAMES); do \ + dockerfile=$${variant#*:}; \ + variant_name=$${variant%%:*}; \ + if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \ + echo "Pushing $$variant_name variant for linux-$*"; \ + docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name"; \ + fi; \ + if [ "$$dockerfile" = "Dockerfile" ]; then \ + echo "Pushing default variant ($$variant_name) for linux-$*"; \ + docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)"; \ + fi; \ + if [ "$(DOCKER_IMAGE_TAG)" = "latest" ]; then \ + if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \ + echo "Pushing $$variant_name variant version tags for linux-$*"; \ + docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name"; \ + fi; \ + if [ "$$dockerfile" = "Dockerfile" ]; then \ + echo "Pushing default variant version tag for linux-$*"; \ + docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"; \ + fi; \ + fi; \ + done DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION))) .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) common-docker-tag-latest: $(TAG_DOCKER_ARCHS) $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: - docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" - docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)" + @for variant in $(DOCKERFILE_VARIANTS_WITH_NAMES); do \ + dockerfile=$${variant#*:}; \ + variant_name=$${variant%%:*}; \ + if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \ + echo "Tagging $$variant_name variant for linux-$* as latest"; \ + docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest-$$variant_name"; \ + docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name"; \ + fi; \ + if [ "$$dockerfile" = "Dockerfile" ]; then \ + echo "Tagging default variant ($$variant_name) for linux-$* as latest"; \ + docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"; \ + docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"; \ + fi; \ + done .PHONY: common-docker-manifest common-docker-manifest: - DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG)) - DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" + @for variant in $(DOCKERFILE_VARIANTS_WITH_NAMES); do \ + dockerfile=$${variant#*:}; \ + variant_name=$${variant%%:*}; \ + if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \ + echo "Creating manifest for $$variant_name variant"; \ + refs=""; \ + for arch in $(DOCKER_ARCHS); do \ + refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name"; \ + done; \ + if [ -z "$$refs" ]; then \ + echo "Skipping manifest for $$variant_name variant (no supported architectures)"; \ + continue; \ + fi; \ + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name" $$refs; \ + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name"; \ + fi; \ + if [ "$$dockerfile" = "Dockerfile" ]; then \ + echo "Creating default variant ($$variant_name) manifest"; \ + refs=""; \ + for arch in $(DOCKER_ARCHS); do \ + refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:$(SANITIZED_DOCKER_IMAGE_TAG)"; \ + done; \ + if [ -z "$$refs" ]; then \ + echo "Skipping default variant manifest (no supported architectures)"; \ + continue; \ + fi; \ + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $$refs; \ + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)"; \ + fi; \ + if [ "$(DOCKER_IMAGE_TAG)" = "latest" ]; then \ + if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \ + echo "Creating manifest for $$variant_name variant version tag"; \ + refs=""; \ + for arch in $(DOCKER_ARCHS); do \ + refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name"; \ + done; \ + if [ -z "$$refs" ]; then \ + echo "Skipping version-tag manifest for $$variant_name variant (no supported architectures)"; \ + continue; \ + fi; \ + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name" $$refs; \ + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name"; \ + fi; \ + if [ "$$dockerfile" = "Dockerfile" ]; then \ + echo "Creating default variant version tag manifest"; \ + refs=""; \ + for arch in $(DOCKER_ARCHS); do \ + refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:v$(DOCKER_MAJOR_VERSION_TAG)"; \ + done; \ + if [ -z "$$refs" ]; then \ + echo "Skipping default variant version-tag manifest (no supported architectures)"; \ + continue; \ + fi; \ + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)" $$refs; \ + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)"; \ + fi; \ + fi; \ + done .PHONY: promu promu: $(PROMU) @@ -248,8 +394,8 @@ $(PROMU): cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu rm -r $(PROMU_TMP) -.PHONY: proto -proto: +.PHONY: common-proto +common-proto: @echo ">> generating code from proto files" @./scripts/genproto.sh @@ -261,6 +407,10 @@ $(GOLANGCI_LINT): | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) endif +.PHONY: common-print-golangci-lint-version +common-print-golangci-lint-version: + @echo $(GOLANGCI_LINT_VERSION) + .PHONY: precheck precheck:: @@ -275,9 +425,3 @@ $(1)_precheck: exit 1; \ fi endef - -govulncheck: install-govulncheck - govulncheck ./... - -install-govulncheck: - command -v govulncheck > /dev/null || go install golang.org/x/vuln/cmd/govulncheck@latest diff --git a/vendor/github.com/prometheus/procfs/README.md b/vendor/github.com/prometheus/procfs/README.md index 0718239cf1..363524094b 100644 --- a/vendor/github.com/prometheus/procfs/README.md +++ b/vendor/github.com/prometheus/procfs/README.md @@ -7,7 +7,7 @@ metrics from the pseudo-filesystems /proc and /sys. backwards-incompatible ways without warnings. Use it at your own risk. [![Go Reference](https://pkg.go.dev/badge/github.com/prometheus/procfs.svg)](https://pkg.go.dev/github.com/prometheus/procfs) -[![CircleCI](https://circleci.com/gh/prometheus/procfs/tree/master.svg?style=svg)](https://circleci.com/gh/prometheus/procfs/tree/master) +[![Build Status](https://github.com/prometheus/procfs/actions/workflows/ci.yml/badge.svg)](https://github.com/prometheus/procfs/actions/workflows/ci.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/procfs)](https://goreportcard.com/report/github.com/prometheus/procfs) ## Usage diff --git a/vendor/github.com/prometheus/procfs/SECURITY.md b/vendor/github.com/prometheus/procfs/SECURITY.md index fed02d85c7..5e6f976dbf 100644 --- a/vendor/github.com/prometheus/procfs/SECURITY.md +++ b/vendor/github.com/prometheus/procfs/SECURITY.md @@ -3,4 +3,4 @@ The Prometheus security policy, including how to report vulnerabilities, can be found here: - +[https://prometheus.io/docs/operating/security/](https://prometheus.io/docs/operating/security/) diff --git a/vendor/github.com/prometheus/procfs/arp.go b/vendor/github.com/prometheus/procfs/arp.go index 2e53344151..716bdef109 100644 --- a/vendor/github.com/prometheus/procfs/arp.go +++ b/vendor/github.com/prometheus/procfs/arp.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -73,15 +73,16 @@ func parseARPEntries(data []byte) ([]ARPEntry, error) { columns := strings.Fields(line) width := len(columns) - if width == expectedHeaderWidth || width == 0 { + switch width { + case expectedHeaderWidth, 0: continue - } else if width == expectedDataWidth { + case expectedDataWidth: entry, err := parseARPEntry(columns) if err != nil { return []ARPEntry{}, fmt.Errorf("%w: Failed to parse ARP entry: %v: %w", ErrFileParse, entry, err) } entries = append(entries, entry) - } else { + default: return []ARPEntry{}, fmt.Errorf("%w: %d columns found, but expected %d: %w", ErrFileParse, width, expectedDataWidth, err) } diff --git a/vendor/github.com/prometheus/procfs/buddyinfo.go b/vendor/github.com/prometheus/procfs/buddyinfo.go index 8380750090..53243e6875 100644 --- a/vendor/github.com/prometheus/procfs/buddyinfo.go +++ b/vendor/github.com/prometheus/procfs/buddyinfo.go @@ -1,4 +1,4 @@ -// Copyright 2017 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -64,14 +64,12 @@ func parseBuddyInfo(r io.Reader) ([]BuddyInfo, error) { if bucketCount == -1 { bucketCount = arraySize - } else { - if bucketCount != arraySize { - return nil, fmt.Errorf("%w: mismatch in number of buddyinfo buckets, previous count %d, new count %d", ErrFileParse, bucketCount, arraySize) - } + } else if bucketCount != arraySize { + return nil, fmt.Errorf("%w: mismatch in number of buddyinfo buckets, previous count %d, new count %d", ErrFileParse, bucketCount, arraySize) } sizes := make([]float64, arraySize) - for i := 0; i < arraySize; i++ { + for i := range arraySize { sizes[i], err = strconv.ParseFloat(parts[i+4], 64) if err != nil { return nil, fmt.Errorf("%w: Invalid valid in buddyinfo: %f: %w", ErrFileParse, sizes[i], err) diff --git a/vendor/github.com/prometheus/procfs/cmdline.go b/vendor/github.com/prometheus/procfs/cmdline.go index bf4f3b48c0..4f1cac1f0a 100644 --- a/vendor/github.com/prometheus/procfs/cmdline.go +++ b/vendor/github.com/prometheus/procfs/cmdline.go @@ -1,4 +1,4 @@ -// Copyright 2021 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/cpuinfo.go b/vendor/github.com/prometheus/procfs/cpuinfo.go index f0950bb495..4b23d8d6b5 100644 --- a/vendor/github.com/prometheus/procfs/cpuinfo.go +++ b/vendor/github.com/prometheus/procfs/cpuinfo.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,7 +12,6 @@ // limitations under the License. //go:build linux -// +build linux package procfs @@ -502,7 +501,7 @@ func parseCPUInfoRISCV(info []byte) ([]CPUInfo, error) { return cpuinfo, nil } -func parseCPUInfoDummy(_ []byte) ([]CPUInfo, error) { // nolint:unused,deadcode +func parseCPUInfoDummy(_ []byte) ([]CPUInfo, error) { //nolint:unused return nil, errors.New("not implemented") } diff --git a/vendor/github.com/prometheus/procfs/cpuinfo_armx.go b/vendor/github.com/prometheus/procfs/cpuinfo_armx.go index 64cfd534c1..b09035ff38 100644 --- a/vendor/github.com/prometheus/procfs/cpuinfo_armx.go +++ b/vendor/github.com/prometheus/procfs/cpuinfo_armx.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,8 +12,6 @@ // limitations under the License. //go:build linux && (arm || arm64) -// +build linux -// +build arm arm64 package procfs diff --git a/vendor/github.com/prometheus/procfs/cpuinfo_loong64.go b/vendor/github.com/prometheus/procfs/cpuinfo_loong64.go index d88442f0ed..7bb20211f9 100644 --- a/vendor/github.com/prometheus/procfs/cpuinfo_loong64.go +++ b/vendor/github.com/prometheus/procfs/cpuinfo_loong64.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,7 +12,6 @@ // limitations under the License. //go:build linux -// +build linux package procfs diff --git a/vendor/github.com/prometheus/procfs/cpuinfo_mipsx.go b/vendor/github.com/prometheus/procfs/cpuinfo_mipsx.go index c11207f3ab..fd75d0f79d 100644 --- a/vendor/github.com/prometheus/procfs/cpuinfo_mipsx.go +++ b/vendor/github.com/prometheus/procfs/cpuinfo_mipsx.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,8 +12,6 @@ // limitations under the License. //go:build linux && (mips || mipsle || mips64 || mips64le) -// +build linux -// +build mips mipsle mips64 mips64le package procfs diff --git a/vendor/github.com/prometheus/procfs/cpuinfo_others.go b/vendor/github.com/prometheus/procfs/cpuinfo_others.go index a6b2b3127c..3d36ba0e6b 100644 --- a/vendor/github.com/prometheus/procfs/cpuinfo_others.go +++ b/vendor/github.com/prometheus/procfs/cpuinfo_others.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,7 +12,6 @@ // limitations under the License. //go:build linux && !386 && !amd64 && !arm && !arm64 && !loong64 && !mips && !mips64 && !mips64le && !mipsle && !ppc64 && !ppc64le && !riscv64 && !s390x -// +build linux,!386,!amd64,!arm,!arm64,!loong64,!mips,!mips64,!mips64le,!mipsle,!ppc64,!ppc64le,!riscv64,!s390x package procfs diff --git a/vendor/github.com/prometheus/procfs/cpuinfo_ppcx.go b/vendor/github.com/prometheus/procfs/cpuinfo_ppcx.go index 003bc2ad4a..b3425051ef 100644 --- a/vendor/github.com/prometheus/procfs/cpuinfo_ppcx.go +++ b/vendor/github.com/prometheus/procfs/cpuinfo_ppcx.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,8 +12,6 @@ // limitations under the License. //go:build linux && (ppc64 || ppc64le) -// +build linux -// +build ppc64 ppc64le package procfs diff --git a/vendor/github.com/prometheus/procfs/cpuinfo_riscvx.go b/vendor/github.com/prometheus/procfs/cpuinfo_riscvx.go index 1c9b7313b6..72598230c3 100644 --- a/vendor/github.com/prometheus/procfs/cpuinfo_riscvx.go +++ b/vendor/github.com/prometheus/procfs/cpuinfo_riscvx.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,8 +12,6 @@ // limitations under the License. //go:build linux && (riscv || riscv64) -// +build linux -// +build riscv riscv64 package procfs diff --git a/vendor/github.com/prometheus/procfs/cpuinfo_s390x.go b/vendor/github.com/prometheus/procfs/cpuinfo_s390x.go index fa3686bc00..50a8239cbc 100644 --- a/vendor/github.com/prometheus/procfs/cpuinfo_s390x.go +++ b/vendor/github.com/prometheus/procfs/cpuinfo_s390x.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,7 +12,6 @@ // limitations under the License. //go:build linux -// +build linux package procfs diff --git a/vendor/github.com/prometheus/procfs/cpuinfo_x86.go b/vendor/github.com/prometheus/procfs/cpuinfo_x86.go index a0ef55562e..00edb30a5c 100644 --- a/vendor/github.com/prometheus/procfs/cpuinfo_x86.go +++ b/vendor/github.com/prometheus/procfs/cpuinfo_x86.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,8 +12,6 @@ // limitations under the License. //go:build linux && (386 || amd64) -// +build linux -// +build 386 amd64 package procfs diff --git a/vendor/github.com/prometheus/procfs/crypto.go b/vendor/github.com/prometheus/procfs/crypto.go index 5f2a37a78b..d93b712e05 100644 --- a/vendor/github.com/prometheus/procfs/crypto.go +++ b/vendor/github.com/prometheus/procfs/crypto.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -48,11 +48,13 @@ type Crypto struct { Walksize *uint64 } +var cryptoFile = "crypto" + // Crypto parses an crypto-file (/proc/crypto) and returns a slice of // structs containing the relevant info. More information available here: // https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html func (fs FS) Crypto() ([]Crypto, error) { - path := fs.proc.Path("crypto") + path := fs.proc.Path(cryptoFile) b, err := util.ReadFileNoStat(path) if err != nil { return nil, fmt.Errorf("%w: Cannot read file %v: %w", ErrFileRead, b, err) @@ -82,6 +84,10 @@ func parseCrypto(r io.Reader) ([]Crypto, error) { continue } + if len(out) == 0 { + return nil, fmt.Errorf("%w: parsed invalid line before name parsed: %q", ErrFileParse, text) + } + kv := strings.Split(text, ":") if len(kv) != 2 { return nil, fmt.Errorf("%w: Cannot parse line: %q", ErrFileParse, text) diff --git a/vendor/github.com/prometheus/procfs/doc.go b/vendor/github.com/prometheus/procfs/doc.go index f9d961e441..26bfea071b 100644 --- a/vendor/github.com/prometheus/procfs/doc.go +++ b/vendor/github.com/prometheus/procfs/doc.go @@ -1,4 +1,4 @@ -// Copyright 2014 Prometheus Team +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/fs.go b/vendor/github.com/prometheus/procfs/fs.go index 9bdaccc7c8..8f27912a13 100644 --- a/vendor/github.com/prometheus/procfs/fs.go +++ b/vendor/github.com/prometheus/procfs/fs.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/fs_statfs_notype.go b/vendor/github.com/prometheus/procfs/fs_statfs_notype.go index 1b5bdbdf84..0bef25bdd9 100644 --- a/vendor/github.com/prometheus/procfs/fs_statfs_notype.go +++ b/vendor/github.com/prometheus/procfs/fs_statfs_notype.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,7 +12,6 @@ // limitations under the License. //go:build !freebsd && !linux -// +build !freebsd,!linux package procfs diff --git a/vendor/github.com/prometheus/procfs/fs_statfs_type.go b/vendor/github.com/prometheus/procfs/fs_statfs_type.go index 80df79c319..d183330390 100644 --- a/vendor/github.com/prometheus/procfs/fs_statfs_type.go +++ b/vendor/github.com/prometheus/procfs/fs_statfs_type.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,7 +12,6 @@ // limitations under the License. //go:build freebsd || linux -// +build freebsd linux package procfs diff --git a/vendor/github.com/prometheus/procfs/fscache.go b/vendor/github.com/prometheus/procfs/fscache.go index 7db8633077..9dde857073 100644 --- a/vendor/github.com/prometheus/procfs/fscache.go +++ b/vendor/github.com/prometheus/procfs/fscache.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -388,20 +388,21 @@ func parseFscacheinfo(r io.Reader) (*Fscacheinfo, error) { } } case "CacheOp:": - if strings.Split(fields[1], "=")[0] == "alo" { + switch strings.Split(fields[1], "=")[0] { + case "alo": err := setFSCacheFields(fields[1:], &m.CacheopAllocationsinProgress, &m.CacheopLookupObjectInProgress, &m.CacheopLookupCompleteInPorgress, &m.CacheopGrabObjectInProgress) if err != nil { return &m, err } - } else if strings.Split(fields[1], "=")[0] == "inv" { + case "inv": err := setFSCacheFields(fields[1:], &m.CacheopInvalidations, &m.CacheopUpdateObjectInProgress, &m.CacheopDropObjectInProgress, &m.CacheopPutObjectInProgress, &m.CacheopAttributeChangeInProgress, &m.CacheopSyncCacheInProgress) if err != nil { return &m, err } - } else { + default: err := setFSCacheFields(fields[1:], &m.CacheopReadOrAllocPageInProgress, &m.CacheopReadOrAllocPagesInProgress, &m.CacheopAllocatePageInProgress, &m.CacheopAllocatePagesInProgress, &m.CacheopWritePagesInProgress, &m.CacheopUncachePagesInProgress, &m.CacheopDissociatePagesInProgress) diff --git a/vendor/github.com/prometheus/procfs/internal/fs/fs.go b/vendor/github.com/prometheus/procfs/internal/fs/fs.go index 3a43e83915..e7ccad66b2 100644 --- a/vendor/github.com/prometheus/procfs/internal/fs/fs.go +++ b/vendor/github.com/prometheus/procfs/internal/fs/fs.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/internal/util/parse.go b/vendor/github.com/prometheus/procfs/internal/util/parse.go index 5a7d2df06a..30c5872019 100644 --- a/vendor/github.com/prometheus/procfs/internal/util/parse.go +++ b/vendor/github.com/prometheus/procfs/internal/util/parse.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/internal/util/readfile.go b/vendor/github.com/prometheus/procfs/internal/util/readfile.go index 71b7a70ebd..0e41f71af1 100644 --- a/vendor/github.com/prometheus/procfs/internal/util/readfile.go +++ b/vendor/github.com/prometheus/procfs/internal/util/readfile.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/internal/util/sysreadfile.go b/vendor/github.com/prometheus/procfs/internal/util/sysreadfile.go index d5404a6d72..f6a4a4de62 100644 --- a/vendor/github.com/prometheus/procfs/internal/util/sysreadfile.go +++ b/vendor/github.com/prometheus/procfs/internal/util/sysreadfile.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,8 +12,6 @@ // limitations under the License. //go:build (linux || darwin) && !appengine -// +build linux darwin -// +build !appengine package util diff --git a/vendor/github.com/prometheus/procfs/internal/util/sysreadfile_compat.go b/vendor/github.com/prometheus/procfs/internal/util/sysreadfile_compat.go index 1d86f5e63f..c80e082cb9 100644 --- a/vendor/github.com/prometheus/procfs/internal/util/sysreadfile_compat.go +++ b/vendor/github.com/prometheus/procfs/internal/util/sysreadfile_compat.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,7 +12,6 @@ // limitations under the License. //go:build (linux && appengine) || (!linux && !darwin) -// +build linux,appengine !linux,!darwin package util diff --git a/vendor/github.com/prometheus/procfs/internal/util/valueparser.go b/vendor/github.com/prometheus/procfs/internal/util/valueparser.go index fe2355d3c6..e0ed671ea0 100644 --- a/vendor/github.com/prometheus/procfs/internal/util/valueparser.go +++ b/vendor/github.com/prometheus/procfs/internal/util/valueparser.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/ipvs.go b/vendor/github.com/prometheus/procfs/ipvs.go index bc3a20c932..5374da9fa8 100644 --- a/vendor/github.com/prometheus/procfs/ipvs.go +++ b/vendor/github.com/prometheus/procfs/ipvs.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/kernel_hung.go b/vendor/github.com/prometheus/procfs/kernel_hung.go new file mode 100644 index 0000000000..0c7a69f99f --- /dev/null +++ b/vendor/github.com/prometheus/procfs/kernel_hung.go @@ -0,0 +1,44 @@ +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !windows + +package procfs + +import ( + "os" + "strconv" + "strings" +) + +// KernelHung contains information about to the kernel's hung_task_detect_count number. +type KernelHung struct { + // Indicates the total number of tasks that have been detected as hung since the system boot. + // This file shows up if `CONFIG_DETECT_HUNG_TASK` is enabled. + HungTaskDetectCount *uint64 +} + +// KernelHung returns values from /proc/sys/kernel/hung_task_detect_count. +func (fs FS) KernelHung() (KernelHung, error) { + data, err := os.ReadFile(fs.proc.Path("sys", "kernel", "hung_task_detect_count")) + if err != nil { + return KernelHung{}, err + } + val, err := strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64) + if err != nil { + return KernelHung{}, err + } + return KernelHung{ + HungTaskDetectCount: &val, + }, nil +} diff --git a/vendor/github.com/prometheus/procfs/kernel_random.go b/vendor/github.com/prometheus/procfs/kernel_random.go index db88566bdf..e7c5b8cf2b 100644 --- a/vendor/github.com/prometheus/procfs/kernel_random.go +++ b/vendor/github.com/prometheus/procfs/kernel_random.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,7 +12,6 @@ // limitations under the License. //go:build !windows -// +build !windows package procfs diff --git a/vendor/github.com/prometheus/procfs/loadavg.go b/vendor/github.com/prometheus/procfs/loadavg.go index 332e76c17f..c8c78a65ed 100644 --- a/vendor/github.com/prometheus/procfs/loadavg.go +++ b/vendor/github.com/prometheus/procfs/loadavg.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/mdstat.go b/vendor/github.com/prometheus/procfs/mdstat.go index 67a9d2b448..d66eeda82a 100644 --- a/vendor/github.com/prometheus/procfs/mdstat.go +++ b/vendor/github.com/prometheus/procfs/mdstat.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -27,13 +27,34 @@ var ( recoveryLinePctRE = regexp.MustCompile(`= (.+)%`) recoveryLineFinishRE = regexp.MustCompile(`finish=(.+)min`) recoveryLineSpeedRE = regexp.MustCompile(`speed=(.+)[A-Z]`) - componentDeviceRE = regexp.MustCompile(`(.*)\[\d+\]`) + componentDeviceRE = regexp.MustCompile(`(.*)\[(\d+)\](\([SF]+\))?`) + personalitiesPrefix = "Personalities : " ) +type MDStatComponent struct { + // Name of the component device. + Name string + // DescriptorIndex number of component device, e.g. the order in the superblock. + DescriptorIndex int32 + // Flags per Linux drivers/md/md.[ch] as of v6.12-rc1 + // Subset that are exposed in mdstat + WriteMostly bool + Journal bool + Faulty bool // "Faulty" is what kernel source uses for "(F)" + Spare bool + Replacement bool + // Some additional flags that are NOT exposed in procfs today; they may + // be available via sysfs. + // In_sync, Bitmap_sync, Blocked, WriteErrorSeen, FaultRecorded, + // BlockedBadBlocks, WantReplacement, Candidate, ... +} + // MDStat holds info parsed from /proc/mdstat. type MDStat struct { // Name of the device. Name string + // raid type of the device. + Type string // activity-state of the device. ActivityState string // Number of active disks. @@ -58,8 +79,8 @@ type MDStat struct { BlocksSyncedFinishTime float64 // current sync speed (in Kilobytes/sec) BlocksSyncedSpeed float64 - // Name of md component devices - Devices []string + // component devices + Devices []MDStatComponent } // MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of @@ -80,28 +101,52 @@ func (fs FS) MDStat() ([]MDStat, error) { // parseMDStat parses data from mdstat file (/proc/mdstat) and returns a slice of // structs containing the relevant info. func parseMDStat(mdStatData []byte) ([]MDStat, error) { + // TODO: + // - parse global hotspares from the "unused devices" line. mdStats := []MDStat{} lines := strings.Split(string(mdStatData), "\n") + knownRaidTypes := make(map[string]bool) for i, line := range lines { if strings.TrimSpace(line) == "" || line[0] == ' ' || - strings.HasPrefix(line, "Personalities") || strings.HasPrefix(line, "unused") { continue } + // Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10] + if len(knownRaidTypes) == 0 && strings.HasPrefix(line, personalitiesPrefix) { + personalities := strings.Fields(line[len(personalitiesPrefix):]) + for _, word := range personalities { + word := word[1 : len(word)-1] + knownRaidTypes[word] = true + } + continue + } deviceFields := strings.Fields(line) if len(deviceFields) < 3 { return nil, fmt.Errorf("%w: Expected 3+ lines, got %q", ErrFileParse, line) } mdName := deviceFields[0] // mdx - state := deviceFields[2] // active or inactive + state := deviceFields[2] // active, inactive, broken + + mdType := "unknown" // raid1, raid5, etc. + var deviceStartIndex int + if len(deviceFields) > 3 { // mdType may be in the 3rd or 4th field + if isRaidType(deviceFields[3], knownRaidTypes) { + mdType = deviceFields[3] + deviceStartIndex = 4 + } else if len(deviceFields) > 4 && isRaidType(deviceFields[4], knownRaidTypes) { + // if the 3rd field is (...), the 4th field is the mdType + mdType = deviceFields[4] + deviceStartIndex = 5 + } + } if len(lines) <= i+3 { return nil, fmt.Errorf("%w: Too few lines for md device: %q", ErrFileParse, mdName) } - // Failed disks have the suffix (F) & Spare disks have the suffix (S). + // Failed (Faulty) disks have the suffix (F) & Spare disks have the suffix (S). fail := int64(strings.Count(line, "(F)")) spare := int64(strings.Count(line, "(S)")) active, total, down, size, err := evalStatusLine(lines[i], lines[i+1]) @@ -123,16 +168,20 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { finish := float64(0) pct := float64(0) recovering := strings.Contains(lines[syncLineIdx], "recovery") + reshaping := strings.Contains(lines[syncLineIdx], "reshape") resyncing := strings.Contains(lines[syncLineIdx], "resync") checking := strings.Contains(lines[syncLineIdx], "check") // Append recovery and resyncing state info. - if recovering || resyncing || checking { - if recovering { + if recovering || resyncing || checking || reshaping { + switch { + case recovering: state = "recovering" - } else if checking { + case reshaping: + state = "reshaping" + case checking: state = "checking" - } else { + default: state = "resyncing" } @@ -148,8 +197,14 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { } } + devices, err := evalComponentDevices(deviceFields[deviceStartIndex:]) + if err != nil { + return nil, fmt.Errorf("error parsing components in md device %q: %w", mdName, err) + } + mdStats = append(mdStats, MDStat{ Name: mdName, + Type: mdType, ActivityState: state, DisksActive: active, DisksFailed: fail, @@ -162,14 +217,24 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { BlocksSyncedPct: pct, BlocksSyncedFinishTime: finish, BlocksSyncedSpeed: speed, - Devices: evalComponentDevices(deviceFields), + Devices: devices, }) } return mdStats, nil } +// check if a string's format is like the mdType +// Rule 1: mdType should not be like (...) +// Rule 2: mdType should not be like sda[0] +// . +func isRaidType(mdType string, knownRaidTypes map[string]bool) bool { + _, ok := knownRaidTypes[mdType] + return !strings.ContainsAny(mdType, "([") && ok +} + func evalStatusLine(deviceLine, statusLine string) (active, total, down, size int64, err error) { + // e.g. 523968 blocks super 1.2 [4/4] [UUUU] statusFields := strings.Fields(statusLine) if len(statusFields) < 1 { return 0, 0, 0, 0, fmt.Errorf("%w: Unexpected statusline %q: %w", ErrFileParse, statusLine, err) @@ -260,17 +325,29 @@ func evalRecoveryLine(recoveryLine string) (blocksSynced int64, blocksToBeSynced return blocksSynced, blocksToBeSynced, pct, finish, speed, nil } -func evalComponentDevices(deviceFields []string) []string { - mdComponentDevices := make([]string, 0) - if len(deviceFields) > 3 { - for _, field := range deviceFields[4:] { - match := componentDeviceRE.FindStringSubmatch(field) - if match == nil { - continue - } - mdComponentDevices = append(mdComponentDevices, match[1]) +func evalComponentDevices(deviceFields []string) ([]MDStatComponent, error) { + mdComponentDevices := make([]MDStatComponent, 0) + for _, field := range deviceFields { + match := componentDeviceRE.FindStringSubmatch(field) + if match == nil { + continue + } + descriptorIndex, err := strconv.ParseInt(match[2], 10, 32) + if err != nil { + return mdComponentDevices, fmt.Errorf("error parsing int from device %q: %w", match[2], err) } + mdComponentDevices = append(mdComponentDevices, MDStatComponent{ + Name: match[1], + DescriptorIndex: int32(descriptorIndex), + // match may contain one or more of these + // https://github.com/torvalds/linux/blob/7ec462100ef9142344ddbf86f2c3008b97acddbe/drivers/md/md.c#L8376-L8392 + Faulty: strings.Contains(match[3], "(F)"), + Spare: strings.Contains(match[3], "(S)"), + Journal: strings.Contains(match[3], "(J)"), + Replacement: strings.Contains(match[3], "(R)"), + WriteMostly: strings.Contains(match[3], "(W)"), + }) } - return mdComponentDevices + return mdComponentDevices, nil } diff --git a/vendor/github.com/prometheus/procfs/meminfo.go b/vendor/github.com/prometheus/procfs/meminfo.go index 4b2c4050a3..3420383187 100644 --- a/vendor/github.com/prometheus/procfs/meminfo.go +++ b/vendor/github.com/prometheus/procfs/meminfo.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -66,6 +66,10 @@ type Meminfo struct { // Memory which has been evicted from RAM, and is temporarily // on the disk SwapFree *uint64 + // Memory consumed by the zswap backend (compressed size) + Zswap *uint64 + // Amount of anonymous memory stored in zswap (original size) + Zswapped *uint64 // Memory which is waiting to get written back to the disk Dirty *uint64 // Memory which is actively being written back to the disk @@ -85,6 +89,8 @@ type Meminfo struct { // amount of memory dedicated to the lowest level of page // tables. PageTables *uint64 + // secondary page tables. + SecPageTables *uint64 // NFS pages sent to the server, but not yet committed to // stable storage NFSUnstable *uint64 @@ -129,15 +135,18 @@ type Meminfo struct { Percpu *uint64 HardwareCorrupted *uint64 AnonHugePages *uint64 + FileHugePages *uint64 ShmemHugePages *uint64 ShmemPmdMapped *uint64 CmaTotal *uint64 CmaFree *uint64 + Unaccepted *uint64 HugePagesTotal *uint64 HugePagesFree *uint64 HugePagesRsvd *uint64 HugePagesSurp *uint64 Hugepagesize *uint64 + Hugetlb *uint64 DirectMap4k *uint64 DirectMap2M *uint64 DirectMap1G *uint64 @@ -161,6 +170,8 @@ type Meminfo struct { MlockedBytes *uint64 SwapTotalBytes *uint64 SwapFreeBytes *uint64 + ZswapBytes *uint64 + ZswappedBytes *uint64 DirtyBytes *uint64 WritebackBytes *uint64 AnonPagesBytes *uint64 @@ -171,6 +182,7 @@ type Meminfo struct { SUnreclaimBytes *uint64 KernelStackBytes *uint64 PageTablesBytes *uint64 + SecPageTablesBytes *uint64 NFSUnstableBytes *uint64 BounceBytes *uint64 WritebackTmpBytes *uint64 @@ -182,11 +194,14 @@ type Meminfo struct { PercpuBytes *uint64 HardwareCorruptedBytes *uint64 AnonHugePagesBytes *uint64 + FileHugePagesBytes *uint64 ShmemHugePagesBytes *uint64 ShmemPmdMappedBytes *uint64 CmaTotalBytes *uint64 CmaFreeBytes *uint64 + UnacceptedBytes *uint64 HugepagesizeBytes *uint64 + HugetlbBytes *uint64 DirectMap4kBytes *uint64 DirectMap2MBytes *uint64 DirectMap1GBytes *uint64 @@ -287,6 +302,12 @@ func parseMemInfo(r io.Reader) (*Meminfo, error) { case "SwapFree:": m.SwapFree = &val m.SwapFreeBytes = &valBytes + case "Zswap:": + m.Zswap = &val + m.ZswapBytes = &valBytes + case "Zswapped:": + m.Zswapped = &val + m.ZswappedBytes = &valBytes case "Dirty:": m.Dirty = &val m.DirtyBytes = &valBytes @@ -317,6 +338,9 @@ func parseMemInfo(r io.Reader) (*Meminfo, error) { case "PageTables:": m.PageTables = &val m.PageTablesBytes = &valBytes + case "SecPageTables:": + m.SecPageTables = &val + m.SecPageTablesBytes = &valBytes case "NFS_Unstable:": m.NFSUnstable = &val m.NFSUnstableBytes = &valBytes @@ -350,6 +374,9 @@ func parseMemInfo(r io.Reader) (*Meminfo, error) { case "AnonHugePages:": m.AnonHugePages = &val m.AnonHugePagesBytes = &valBytes + case "FileHugePages:": + m.FileHugePages = &val + m.FileHugePagesBytes = &valBytes case "ShmemHugePages:": m.ShmemHugePages = &val m.ShmemHugePagesBytes = &valBytes @@ -362,6 +389,9 @@ func parseMemInfo(r io.Reader) (*Meminfo, error) { case "CmaFree:": m.CmaFree = &val m.CmaFreeBytes = &valBytes + case "Unaccepted:": + m.Unaccepted = &val + m.UnacceptedBytes = &valBytes case "HugePages_Total:": m.HugePagesTotal = &val case "HugePages_Free:": @@ -373,6 +403,9 @@ func parseMemInfo(r io.Reader) (*Meminfo, error) { case "Hugepagesize:": m.Hugepagesize = &val m.HugepagesizeBytes = &valBytes + case "Hugetlb:": + m.Hugetlb = &val + m.HugetlbBytes = &valBytes case "DirectMap4k:": m.DirectMap4k = &val m.DirectMap4kBytes = &valBytes diff --git a/vendor/github.com/prometheus/procfs/mountinfo.go b/vendor/github.com/prometheus/procfs/mountinfo.go index a704c5e735..8594ae7f1e 100644 --- a/vendor/github.com/prometheus/procfs/mountinfo.go +++ b/vendor/github.com/prometheus/procfs/mountinfo.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -17,10 +17,10 @@ import ( "bufio" "bytes" "fmt" + "io" + "os" "strconv" "strings" - - "github.com/prometheus/procfs/internal/util" ) // A MountInfo is a type that describes the details, options @@ -147,8 +147,7 @@ func mountOptionsParseOptionalFields(o []string) (map[string]string, error) { // mountOptionsParser parses the mount options, superblock options. func mountOptionsParser(mountOptions string) map[string]string { opts := make(map[string]string) - options := strings.Split(mountOptions, ",") - for _, opt := range options { + for opt := range strings.SplitSeq(mountOptions, ",") { splitOption := strings.Split(opt, "=") if len(splitOption) < 2 { key := splitOption[0] @@ -161,9 +160,19 @@ func mountOptionsParser(mountOptions string) map[string]string { return opts } +// readMountInfo reads a full mountinfo file (no 1 MiB cap, unlike util.ReadFileNoStat). +func readMountInfo(path string) ([]byte, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + return io.ReadAll(f) +} + // GetMounts retrieves mountinfo information from `/proc/self/mountinfo`. func GetMounts() ([]*MountInfo, error) { - data, err := util.ReadFileNoStat("/proc/self/mountinfo") + data, err := readMountInfo("/proc/self/mountinfo") if err != nil { return nil, err } @@ -172,7 +181,25 @@ func GetMounts() ([]*MountInfo, error) { // GetProcMounts retrieves mountinfo information from a processes' `/proc//mountinfo`. func GetProcMounts(pid int) ([]*MountInfo, error) { - data, err := util.ReadFileNoStat(fmt.Sprintf("/proc/%d/mountinfo", pid)) + data, err := readMountInfo(fmt.Sprintf("/proc/%d/mountinfo", pid)) + if err != nil { + return nil, err + } + return parseMountInfo(data) +} + +// GetMounts retrieves mountinfo information from `/proc/self/mountinfo`. +func (fs FS) GetMounts() ([]*MountInfo, error) { + data, err := readMountInfo(fs.proc.Path("self/mountinfo")) + if err != nil { + return nil, err + } + return parseMountInfo(data) +} + +// GetProcMounts retrieves mountinfo information from a processes' `/proc//mountinfo`. +func (fs FS) GetProcMounts(pid int) ([]*MountInfo, error) { + data, err := readMountInfo(fs.proc.Path(fmt.Sprintf("%d/mountinfo", pid))) if err != nil { return nil, err } diff --git a/vendor/github.com/prometheus/procfs/mountstats.go b/vendor/github.com/prometheus/procfs/mountstats.go index 50caa73274..e503cb3a6c 100644 --- a/vendor/github.com/prometheus/procfs/mountstats.go +++ b/vendor/github.com/prometheus/procfs/mountstats.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -383,7 +383,7 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e if stats.Opts == nil { stats.Opts = map[string]string{} } - for _, opt := range strings.Split(ss[1], ",") { + for opt := range strings.SplitSeq(ss[1], ",") { split := strings.Split(opt, "=") if len(split) == 2 { stats.Opts[split[0]] = split[1] diff --git a/vendor/github.com/prometheus/procfs/net_conntrackstat.go b/vendor/github.com/prometheus/procfs/net_conntrackstat.go index 316df5fbb7..e9ca357079 100644 --- a/vendor/github.com/prometheus/procfs/net_conntrackstat.go +++ b/vendor/github.com/prometheus/procfs/net_conntrackstat.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/net_dev.go b/vendor/github.com/prometheus/procfs/net_dev.go index e66208aa05..7b3e1d61c9 100644 --- a/vendor/github.com/prometheus/procfs/net_dev.go +++ b/vendor/github.com/prometheus/procfs/net_dev.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/net_dev_snmp6.go b/vendor/github.com/prometheus/procfs/net_dev_snmp6.go index f50b38e352..2a0f60f29f 100644 --- a/vendor/github.com/prometheus/procfs/net_dev_snmp6.go +++ b/vendor/github.com/prometheus/procfs/net_dev_snmp6.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -18,6 +18,7 @@ import ( "errors" "io" "os" + "path/filepath" "strconv" "strings" ) @@ -56,7 +57,9 @@ func newNetDevSNMP6(dir string) (NetDevSNMP6, error) { } for _, iFaceFile := range ifaceFiles { - f, err := os.Open(dir + "/" + iFaceFile.Name()) + filePath := filepath.Join(dir, iFaceFile.Name()) + + f, err := os.Open(filePath) if err != nil { return netDevSNMP6, err } diff --git a/vendor/github.com/prometheus/procfs/net_ip_socket.go b/vendor/github.com/prometheus/procfs/net_ip_socket.go index 19e3378f72..9291f8cd4c 100644 --- a/vendor/github.com/prometheus/procfs/net_ip_socket.go +++ b/vendor/github.com/prometheus/procfs/net_ip_socket.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/net_protocols.go b/vendor/github.com/prometheus/procfs/net_protocols.go index 8d4b1ac05b..eaa996cbcf 100644 --- a/vendor/github.com/prometheus/procfs/net_protocols.go +++ b/vendor/github.com/prometheus/procfs/net_protocols.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -169,7 +169,7 @@ func (pc *NetProtocolCapabilities) parseCapabilities(capabilities []string) erro &pc.EnterMemoryPressure, } - for i := 0; i < len(capabilities); i++ { + for i := range capabilities { switch capabilities[i] { case "y": *capabilityFields[i] = true diff --git a/vendor/github.com/prometheus/procfs/net_route.go b/vendor/github.com/prometheus/procfs/net_route.go index deb7029fe1..fa3812d9d0 100644 --- a/vendor/github.com/prometheus/procfs/net_route.go +++ b/vendor/github.com/prometheus/procfs/net_route.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/net_sockstat.go b/vendor/github.com/prometheus/procfs/net_sockstat.go index fae62b13d9..8b221ebfff 100644 --- a/vendor/github.com/prometheus/procfs/net_sockstat.go +++ b/vendor/github.com/prometheus/procfs/net_sockstat.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -139,9 +139,6 @@ func parseSockstatKVs(kvs []string) (map[string]int, error) { func parseSockstatProtocol(kvs map[string]int) NetSockstatProtocol { var nsp NetSockstatProtocol for k, v := range kvs { - // Capture the range variable to ensure we get unique pointers for - // each of the optional fields. - v := v switch k { case "inuse": nsp.InUse = v diff --git a/vendor/github.com/prometheus/procfs/net_softnet.go b/vendor/github.com/prometheus/procfs/net_softnet.go index 71c8059f4d..4a2dfa18fd 100644 --- a/vendor/github.com/prometheus/procfs/net_softnet.go +++ b/vendor/github.com/prometheus/procfs/net_softnet.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/net_tcp.go b/vendor/github.com/prometheus/procfs/net_tcp.go index 0396d72015..2c7f9bc7c3 100644 --- a/vendor/github.com/prometheus/procfs/net_tcp.go +++ b/vendor/github.com/prometheus/procfs/net_tcp.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -25,6 +25,7 @@ type ( // NetTCP returns the IPv4 kernel/networking statistics for TCP datagrams // read from /proc/net/tcp. +// // Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET) instead. func (fs FS) NetTCP() (NetTCP, error) { return newNetTCP(fs.proc.Path("net/tcp")) @@ -32,6 +33,7 @@ func (fs FS) NetTCP() (NetTCP, error) { // NetTCP6 returns the IPv6 kernel/networking statistics for TCP datagrams // read from /proc/net/tcp6. +// // Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET6) instead. func (fs FS) NetTCP6() (NetTCP, error) { return newNetTCP(fs.proc.Path("net/tcp6")) @@ -39,6 +41,7 @@ func (fs FS) NetTCP6() (NetTCP, error) { // NetTCPSummary returns already computed statistics like the total queue lengths // for TCP datagrams read from /proc/net/tcp. +// // Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET) instead. func (fs FS) NetTCPSummary() (*NetTCPSummary, error) { return newNetTCPSummary(fs.proc.Path("net/tcp")) @@ -46,6 +49,7 @@ func (fs FS) NetTCPSummary() (*NetTCPSummary, error) { // NetTCP6Summary returns already computed statistics like the total queue lengths // for TCP datagrams read from /proc/net/tcp6. +// // Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET6) instead. func (fs FS) NetTCP6Summary() (*NetTCPSummary, error) { return newNetTCPSummary(fs.proc.Path("net/tcp6")) diff --git a/vendor/github.com/prometheus/procfs/net_tls_stat.go b/vendor/github.com/prometheus/procfs/net_tls_stat.go index 13994c1782..b1b3f6a6a2 100644 --- a/vendor/github.com/prometheus/procfs/net_tls_stat.go +++ b/vendor/github.com/prometheus/procfs/net_tls_stat.go @@ -1,4 +1,4 @@ -// Copyright 2023 Prometheus Team +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/net_udp.go b/vendor/github.com/prometheus/procfs/net_udp.go index 9ac3daf2d4..8a32779102 100644 --- a/vendor/github.com/prometheus/procfs/net_udp.go +++ b/vendor/github.com/prometheus/procfs/net_udp.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/net_unix.go b/vendor/github.com/prometheus/procfs/net_unix.go index d7e0cacb4c..e4d6359236 100644 --- a/vendor/github.com/prometheus/procfs/net_unix.go +++ b/vendor/github.com/prometheus/procfs/net_unix.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/net_wireless.go b/vendor/github.com/prometheus/procfs/net_wireless.go index 7c597bc870..f74dd3bed0 100644 --- a/vendor/github.com/prometheus/procfs/net_wireless.go +++ b/vendor/github.com/prometheus/procfs/net_wireless.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -114,47 +114,47 @@ func parseWireless(r io.Reader) ([]*Wireless, error) { qlink, err := strconv.Atoi(strings.TrimSuffix(stats[1], ".")) if err != nil { - return nil, fmt.Errorf("%w: parse Quality:link as integer %q: %w", ErrFileParse, qlink, err) + return nil, fmt.Errorf("%w: parse Quality:link as integer %q: %w", ErrFileParse, stats[1], err) } qlevel, err := strconv.Atoi(strings.TrimSuffix(stats[2], ".")) if err != nil { - return nil, fmt.Errorf("%w: Quality:level as integer %q: %w", ErrFileParse, qlevel, err) + return nil, fmt.Errorf("%w: Quality:level as integer %q: %w", ErrFileParse, stats[2], err) } qnoise, err := strconv.Atoi(strings.TrimSuffix(stats[3], ".")) if err != nil { - return nil, fmt.Errorf("%w: Quality:noise as integer %q: %w", ErrFileParse, qnoise, err) + return nil, fmt.Errorf("%w: Quality:noise as integer %q: %w", ErrFileParse, stats[3], err) } dnwid, err := strconv.Atoi(stats[4]) if err != nil { - return nil, fmt.Errorf("%w: Discarded:nwid as integer %q: %w", ErrFileParse, dnwid, err) + return nil, fmt.Errorf("%w: Discarded:nwid as integer %q: %w", ErrFileParse, stats[4], err) } dcrypt, err := strconv.Atoi(stats[5]) if err != nil { - return nil, fmt.Errorf("%w: Discarded:crypt as integer %q: %w", ErrFileParse, dcrypt, err) + return nil, fmt.Errorf("%w: Discarded:crypt as integer %q: %w", ErrFileParse, stats[5], err) } dfrag, err := strconv.Atoi(stats[6]) if err != nil { - return nil, fmt.Errorf("%w: Discarded:frag as integer %q: %w", ErrFileParse, dfrag, err) + return nil, fmt.Errorf("%w: Discarded:frag as integer %q: %w", ErrFileParse, stats[6], err) } dretry, err := strconv.Atoi(stats[7]) if err != nil { - return nil, fmt.Errorf("%w: Discarded:retry as integer %q: %w", ErrFileParse, dretry, err) + return nil, fmt.Errorf("%w: Discarded:retry as integer %q: %w", ErrFileParse, stats[7], err) } dmisc, err := strconv.Atoi(stats[8]) if err != nil { - return nil, fmt.Errorf("%w: Discarded:misc as integer %q: %w", ErrFileParse, dmisc, err) + return nil, fmt.Errorf("%w: Discarded:misc as integer %q: %w", ErrFileParse, stats[8], err) } mbeacon, err := strconv.Atoi(stats[9]) if err != nil { - return nil, fmt.Errorf("%w: Missed:beacon as integer %q: %w", ErrFileParse, mbeacon, err) + return nil, fmt.Errorf("%w: Missed:beacon as integer %q: %w", ErrFileParse, stats[9], err) } w := &Wireless{ diff --git a/vendor/github.com/prometheus/procfs/net_xfrm.go b/vendor/github.com/prometheus/procfs/net_xfrm.go index 932ef20468..5a9f497d19 100644 --- a/vendor/github.com/prometheus/procfs/net_xfrm.go +++ b/vendor/github.com/prometheus/procfs/net_xfrm.go @@ -1,4 +1,4 @@ -// Copyright 2017 Prometheus Team +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/netstat.go b/vendor/github.com/prometheus/procfs/netstat.go index 742dff453b..dbdae47392 100644 --- a/vendor/github.com/prometheus/procfs/netstat.go +++ b/vendor/github.com/prometheus/procfs/netstat.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/nfnetlink_queue.go b/vendor/github.com/prometheus/procfs/nfnetlink_queue.go new file mode 100644 index 0000000000..b0a73b11e9 --- /dev/null +++ b/vendor/github.com/prometheus/procfs/nfnetlink_queue.go @@ -0,0 +1,85 @@ +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package procfs + +import ( + "bufio" + "bytes" + "fmt" + + "github.com/prometheus/procfs/internal/util" +) + +const nfNetLinkQueueFormat = "%d %d %d %d %d %d %d %d %d" + +// NFNetLinkQueue contains general information about netfilter queues found in /proc/net/netfilter/nfnetlink_queue. +type NFNetLinkQueue struct { + // id of the queue + QueueID uint + // pid of process handling the queue + PeerPID uint + // number of packets waiting for a decision + QueueTotal uint + // indicate how userspace receive packets + CopyMode uint + // size of copy + CopyRange uint + // number of items dropped by the kernel because too many packets were waiting a decision. + // It queue_total is superior to queue_max_len (1024 per default) the packets are dropped. + QueueDropped uint + // number of packets dropped by userspace (due to kernel send failure on the netlink socket) + QueueUserDropped uint + // sequence number of packets queued. It gives a correct approximation of the number of queued packets. + SequenceID uint + // internal value (number of entity using the queue) + Use uint +} + +// NFNetLinkQueue returns information about current state of netfilter queues. +func (fs FS) NFNetLinkQueue() ([]NFNetLinkQueue, error) { + data, err := util.ReadFileNoStat(fs.proc.Path("net/netfilter/nfnetlink_queue")) + if err != nil { + return nil, err + } + + queue := []NFNetLinkQueue{} + if len(data) == 0 { + return queue, nil + } + + scanner := bufio.NewScanner(bytes.NewReader(data)) + for scanner.Scan() { + line := scanner.Text() + nFNetLinkQueue, err := parseNFNetLinkQueueLine(line) + if err != nil { + return nil, err + } + queue = append(queue, *nFNetLinkQueue) + } + return queue, nil +} + +// parseNFNetLinkQueueLine parses each line of the /proc/net/netfilter/nfnetlink_queue file. +func parseNFNetLinkQueueLine(line string) (*NFNetLinkQueue, error) { + nFNetLinkQueue := NFNetLinkQueue{} + _, err := fmt.Sscanf( + line, nfNetLinkQueueFormat, + &nFNetLinkQueue.QueueID, &nFNetLinkQueue.PeerPID, &nFNetLinkQueue.QueueTotal, &nFNetLinkQueue.CopyMode, + &nFNetLinkQueue.CopyRange, &nFNetLinkQueue.QueueDropped, &nFNetLinkQueue.QueueUserDropped, &nFNetLinkQueue.SequenceID, &nFNetLinkQueue.Use, + ) + if err != nil { + return nil, err + } + return &nFNetLinkQueue, nil +} diff --git a/vendor/github.com/prometheus/procfs/proc.go b/vendor/github.com/prometheus/procfs/proc.go index 368187fa88..39c14aa55e 100644 --- a/vendor/github.com/prometheus/procfs/proc.go +++ b/vendor/github.com/prometheus/procfs/proc.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -49,7 +49,7 @@ func (p Procs) Less(i, j int) bool { return p[i].PID < p[j].PID } // Self returns a process for the current process read via /proc/self. func Self() (Proc, error) { fs, err := NewFS(DefaultMountPoint) - if err != nil || errors.Unwrap(err) == ErrMountPoint { + if err != nil || errors.Is(err, ErrMountPoint) { return Proc{}, err } return fs.Self() diff --git a/vendor/github.com/prometheus/procfs/proc_cgroup.go b/vendor/github.com/prometheus/procfs/proc_cgroup.go index 4a64347c03..7e8a122978 100644 --- a/vendor/github.com/prometheus/procfs/proc_cgroup.go +++ b/vendor/github.com/prometheus/procfs/proc_cgroup.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -60,7 +60,7 @@ func parseCgroupString(cgroupStr string) (*Cgroup, error) { } cgroup.HierarchyID, err = strconv.Atoi(fields[0]) if err != nil { - return nil, fmt.Errorf("%w: hierarchy ID: %q", ErrFileParse, cgroup.HierarchyID) + return nil, fmt.Errorf("%w: hierarchy ID: %q", ErrFileParse, fields[0]) } if fields[1] != "" { ssNames := strings.Split(fields[1], ",") diff --git a/vendor/github.com/prometheus/procfs/proc_cgroups.go b/vendor/github.com/prometheus/procfs/proc_cgroups.go index 5dd4938999..0b275c3b1f 100644 --- a/vendor/github.com/prometheus/procfs/proc_cgroups.go +++ b/vendor/github.com/prometheus/procfs/proc_cgroups.go @@ -1,4 +1,4 @@ -// Copyright 2021 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -40,13 +40,13 @@ type CgroupSummary struct { // parseCgroupSummary parses each line of the /proc/cgroup file // Line format is `subsys_name hierarchy num_cgroups enabled`. -func parseCgroupSummaryString(CgroupSummaryStr string) (*CgroupSummary, error) { +func parseCgroupSummaryString(cgroupSummaryStr string) (*CgroupSummary, error) { var err error - fields := strings.Fields(CgroupSummaryStr) + fields := strings.Fields(cgroupSummaryStr) // require at least 4 fields if len(fields) < 4 { - return nil, fmt.Errorf("%w: 4+ fields required, found %d fields in cgroup info string: %s", ErrFileParse, len(fields), CgroupSummaryStr) + return nil, fmt.Errorf("%w: 4+ fields required, found %d fields in cgroup info string: %s", ErrFileParse, len(fields), cgroupSummaryStr) } CgroupSummary := &CgroupSummary{ diff --git a/vendor/github.com/prometheus/procfs/proc_environ.go b/vendor/github.com/prometheus/procfs/proc_environ.go index 57a89895d6..5b941de047 100644 --- a/vendor/github.com/prometheus/procfs/proc_environ.go +++ b/vendor/github.com/prometheus/procfs/proc_environ.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/proc_fdinfo.go b/vendor/github.com/prometheus/procfs/proc_fdinfo.go index fa761b3529..fa57761dbe 100644 --- a/vendor/github.com/prometheus/procfs/proc_fdinfo.go +++ b/vendor/github.com/prometheus/procfs/proc_fdinfo.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -60,15 +60,16 @@ func (p Proc) FDInfo(fd string) (*ProcFDInfo, error) { scanner := bufio.NewScanner(bytes.NewReader(data)) for scanner.Scan() { text = scanner.Text() - if rPos.MatchString(text) { + switch { + case rPos.MatchString(text): pos = rPos.FindStringSubmatch(text)[1] - } else if rFlags.MatchString(text) { + case rFlags.MatchString(text): flags = rFlags.FindStringSubmatch(text)[1] - } else if rMntID.MatchString(text) { + case rMntID.MatchString(text): mntid = rMntID.FindStringSubmatch(text)[1] - } else if rIno.MatchString(text) { + case rIno.MatchString(text): ino = rIno.FindStringSubmatch(text)[1] - } else if rInotify.MatchString(text) { + case rInotify.MatchString(text): newInotify, err := parseInotifyInfo(text) if err != nil { return nil, err diff --git a/vendor/github.com/prometheus/procfs/proc_interrupts.go b/vendor/github.com/prometheus/procfs/proc_interrupts.go index 86b4b45246..643b500d5d 100644 --- a/vendor/github.com/prometheus/procfs/proc_interrupts.go +++ b/vendor/github.com/prometheus/procfs/proc_interrupts.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -42,7 +42,7 @@ type Interrupts map[string]Interrupt // Interrupts creates a new instance from a given Proc instance. func (p Proc) Interrupts() (Interrupts, error) { - data, err := util.ReadFileNoStat(p.path("interrupts")) + data, err := util.ReadFileNoStat(p.fs.proc.Path("interrupts")) if err != nil { return nil, err } diff --git a/vendor/github.com/prometheus/procfs/proc_io.go b/vendor/github.com/prometheus/procfs/proc_io.go index d15b66ddb6..dd8086ba2e 100644 --- a/vendor/github.com/prometheus/procfs/proc_io.go +++ b/vendor/github.com/prometheus/procfs/proc_io.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/proc_limits.go b/vendor/github.com/prometheus/procfs/proc_limits.go index 9530b14bc6..4b7d337847 100644 --- a/vendor/github.com/prometheus/procfs/proc_limits.go +++ b/vendor/github.com/prometheus/procfs/proc_limits.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -19,6 +19,7 @@ import ( "os" "regexp" "strconv" + "strings" ) // ProcLimits represents the soft limits for each of the process's resource @@ -74,7 +75,7 @@ const ( ) var ( - limitsMatch = regexp.MustCompile(`(Max \w+\s{0,1}?\w*\s{0,1}\w*)\s{2,}(\w+)\s+(\w+)`) + limitsMatch = regexp.MustCompile(`(Max \w+\s??\w*\s?\w*)\s{2,}(\w+)\s+(\w+)`) ) // NewLimits returns the current soft limits of the process. @@ -106,7 +107,7 @@ func (p Proc) Limits() (ProcLimits, error) { return ProcLimits{}, fmt.Errorf("%w: couldn't parse %q line %q", ErrFileParse, f.Name(), s.Text()) } - switch fields[1] { + switch strings.TrimSpace(fields[1]) { case "Max cpu time": l.CPUTime, err = parseUint(fields[2]) case "Max file size": diff --git a/vendor/github.com/prometheus/procfs/proc_maps.go b/vendor/github.com/prometheus/procfs/proc_maps.go index 7e75c286b5..08b89a6eb9 100644 --- a/vendor/github.com/prometheus/procfs/proc_maps.go +++ b/vendor/github.com/prometheus/procfs/proc_maps.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,8 +12,6 @@ // limitations under the License. //go:build (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris) && !js -// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris -// +build !js package procfs diff --git a/vendor/github.com/prometheus/procfs/proc_netstat.go b/vendor/github.com/prometheus/procfs/proc_netstat.go index 4248c1716e..7f94cc8914 100644 --- a/vendor/github.com/prometheus/procfs/proc_netstat.go +++ b/vendor/github.com/prometheus/procfs/proc_netstat.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/proc_ns.go b/vendor/github.com/prometheus/procfs/proc_ns.go index 0f8f847f95..5fc0eb9e2f 100644 --- a/vendor/github.com/prometheus/procfs/proc_ns.go +++ b/vendor/github.com/prometheus/procfs/proc_ns.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/proc_psi.go b/vendor/github.com/prometheus/procfs/proc_psi.go index ccd35f153a..cc2c5de873 100644 --- a/vendor/github.com/prometheus/procfs/proc_psi.go +++ b/vendor/github.com/prometheus/procfs/proc_psi.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/proc_smaps.go b/vendor/github.com/prometheus/procfs/proc_smaps.go index 9a297afcf8..f637309b3d 100644 --- a/vendor/github.com/prometheus/procfs/proc_smaps.go +++ b/vendor/github.com/prometheus/procfs/proc_smaps.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,7 +12,6 @@ // limitations under the License. //go:build !windows -// +build !windows package procfs diff --git a/vendor/github.com/prometheus/procfs/proc_snmp.go b/vendor/github.com/prometheus/procfs/proc_snmp.go index 4bdc90b07e..8d9a9bcd67 100644 --- a/vendor/github.com/prometheus/procfs/proc_snmp.go +++ b/vendor/github.com/prometheus/procfs/proc_snmp.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/proc_snmp6.go b/vendor/github.com/prometheus/procfs/proc_snmp6.go index fb7fd3995b..841fef4649 100644 --- a/vendor/github.com/prometheus/procfs/proc_snmp6.go +++ b/vendor/github.com/prometheus/procfs/proc_snmp6.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/proc_stat.go b/vendor/github.com/prometheus/procfs/proc_stat.go index 06a8d931c9..02e3f9e316 100644 --- a/vendor/github.com/prometheus/procfs/proc_stat.go +++ b/vendor/github.com/prometheus/procfs/proc_stat.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -101,6 +101,12 @@ type ProcStat struct { RSS int // Soft limit in bytes on the rss of the process. RSSLimit uint64 + // The address above which program text can run. + StartCode uint64 + // The address below which program text can run. + EndCode uint64 + // The address of the start (i.e., bottom) of the stack. + StartStack uint64 // CPU number last executed on. Processor uint // Real-time scheduling priority, a number in the range 1 to 99 for processes @@ -177,9 +183,9 @@ func (p Proc) Stat() (ProcStat, error) { &s.VSize, &s.RSS, &s.RSSLimit, - &ignoreUint64, - &ignoreUint64, - &ignoreUint64, + &s.StartCode, + &s.EndCode, + &s.StartStack, &ignoreUint64, &ignoreUint64, &ignoreUint64, diff --git a/vendor/github.com/prometheus/procfs/proc_statm.go b/vendor/github.com/prometheus/procfs/proc_statm.go new file mode 100644 index 0000000000..6bcc97ec9c --- /dev/null +++ b/vendor/github.com/prometheus/procfs/proc_statm.go @@ -0,0 +1,117 @@ +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package procfs + +import ( + "os" + "strconv" + "strings" + + "github.com/prometheus/procfs/internal/util" +) + +// - https://man7.org/linux/man-pages/man5/proc_pid_statm.5.html + +// ProcStatm Provides memory usage information for a process, measured in memory pages. +// Read from /proc/[pid]/statm. +type ProcStatm struct { + // The process ID. + PID int + // total program size (same as VmSize in status) + Size uint64 + // resident set size (same as VmRSS in status) + Resident uint64 + // number of resident shared pages (i.e., backed by a file) + Shared uint64 + // text (code) + Text uint64 + // library (unused since Linux 2.6; always 0) + Lib uint64 + // data + stack + Data uint64 + // dirty pages (unused since Linux 2.6; always 0) + Dt uint64 +} + +// NewStatm returns the current status information of the process. +// +// Deprecated: Use p.Statm() instead. +func (p Proc) NewStatm() (ProcStatm, error) { + return p.Statm() +} + +// Statm returns the current memory usage information of the process. +func (p Proc) Statm() (ProcStatm, error) { + data, err := util.ReadFileNoStat(p.path("statm")) + if err != nil { + return ProcStatm{}, err + } + + statmSlice, err := parseStatm(data) + if err != nil { + return ProcStatm{}, err + } + + procStatm := ProcStatm{ + PID: p.PID, + Size: statmSlice[0], + Resident: statmSlice[1], + Shared: statmSlice[2], + Text: statmSlice[3], + Lib: statmSlice[4], + Data: statmSlice[5], + Dt: statmSlice[6], + } + + return procStatm, nil +} + +// parseStatm return /proc/[pid]/statm data to uint64 slice. +func parseStatm(data []byte) ([]uint64, error) { + var statmSlice []uint64 + statmItems := strings.Fields(string(data)) + for i := range statmItems { + statmItem, err := strconv.ParseUint(statmItems[i], 10, 64) + if err != nil { + return nil, err + } + statmSlice = append(statmSlice, statmItem) + } + return statmSlice, nil +} + +// SizeBytes returns the process of total program size in bytes. +func (s ProcStatm) SizeBytes() uint64 { + return s.Size * uint64(os.Getpagesize()) +} + +// ResidentBytes returns the process of resident set size in bytes. +func (s ProcStatm) ResidentBytes() uint64 { + return s.Resident * uint64(os.Getpagesize()) +} + +// SHRBytes returns the process of share memory size in bytes. +func (s ProcStatm) SHRBytes() uint64 { + return s.Shared * uint64(os.Getpagesize()) +} + +// TextBytes returns the process of text (code) size in bytes. +func (s ProcStatm) TextBytes() uint64 { + return s.Text * uint64(os.Getpagesize()) +} + +// DataBytes returns the process of data + stack size in bytes. +func (s ProcStatm) DataBytes() uint64 { + return s.Data * uint64(os.Getpagesize()) +} diff --git a/vendor/github.com/prometheus/procfs/proc_status.go b/vendor/github.com/prometheus/procfs/proc_status.go index dd8aa56885..12d65581c8 100644 --- a/vendor/github.com/prometheus/procfs/proc_status.go +++ b/vendor/github.com/prometheus/procfs/proc_status.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -16,7 +16,7 @@ package procfs import ( "bytes" "math/bits" - "sort" + "slices" "strconv" "strings" @@ -83,6 +83,19 @@ type ProcStatus struct { // CpusAllowedList: List of cpu cores processes are allowed to run on. CpusAllowedList []uint64 + + // CapInh is the bitmap of inheritable capabilities + // + // See: https://www.kernel.org/doc/man-pages/online/pages/man7/capabilities.7.html + CapInh uint64 + // CapPrm is the bitmap of permitted capabilities + CapPrm uint64 + // CapEff is the bitmap of effective capabilities + CapEff uint64 + // CapBnd is the bitmap of bounding capabilities + CapBnd uint64 + // CapAmb is the bitmap of ambient capabilities + CapAmb uint64 } // NewStatus returns the current status information of the process. @@ -94,8 +107,7 @@ func (p Proc) NewStatus() (ProcStatus, error) { s := ProcStatus{PID: p.PID} - lines := strings.Split(string(data), "\n") - for _, line := range lines { + for line := range strings.SplitSeq(string(data), "\n") { if !bytes.Contains([]byte(line), []byte(":")) { continue } @@ -191,6 +203,36 @@ func (s *ProcStatus) fillStatus(k string, vString string, vUint uint64, vUintByt s.NonVoluntaryCtxtSwitches = vUint case "Cpus_allowed_list": s.CpusAllowedList = calcCpusAllowedList(vString) + case "CapInh": + var err error + s.CapInh, err = strconv.ParseUint(vString, 16, 64) + if err != nil { + return err + } + case "CapPrm": + var err error + s.CapPrm, err = strconv.ParseUint(vString, 16, 64) + if err != nil { + return err + } + case "CapEff": + var err error + s.CapEff, err = strconv.ParseUint(vString, 16, 64) + if err != nil { + return err + } + case "CapBnd": + var err error + s.CapBnd, err = strconv.ParseUint(vString, 16, 64) + if err != nil { + return err + } + case "CapAmb": + var err error + s.CapAmb, err = strconv.ParseUint(vString, 16, 64) + if err != nil { + return err + } } return nil @@ -222,7 +264,7 @@ func calcCpusAllowedList(cpuString string) []uint64 { } - sort.Slice(g, func(i, j int) bool { return g[i] < g[j] }) + slices.Sort(g) return g } diff --git a/vendor/github.com/prometheus/procfs/proc_sys.go b/vendor/github.com/prometheus/procfs/proc_sys.go index 3810d1ac99..52658a4d52 100644 --- a/vendor/github.com/prometheus/procfs/proc_sys.go +++ b/vendor/github.com/prometheus/procfs/proc_sys.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/schedstat.go b/vendor/github.com/prometheus/procfs/schedstat.go index 5f7f32dc83..fafd8dff74 100644 --- a/vendor/github.com/prometheus/procfs/schedstat.go +++ b/vendor/github.com/prometheus/procfs/schedstat.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/slab.go b/vendor/github.com/prometheus/procfs/slab.go index 8611c90177..32a04678ad 100644 --- a/vendor/github.com/prometheus/procfs/slab.go +++ b/vendor/github.com/prometheus/procfs/slab.go @@ -1,4 +1,4 @@ -// Copyright 2020 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/softirqs.go b/vendor/github.com/prometheus/procfs/softirqs.go index 403e6ae708..47b73a7297 100644 --- a/vendor/github.com/prometheus/procfs/softirqs.go +++ b/vendor/github.com/prometheus/procfs/softirqs.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/stat.go b/vendor/github.com/prometheus/procfs/stat.go index e36b41c18a..593ad0f62f 100644 --- a/vendor/github.com/prometheus/procfs/stat.go +++ b/vendor/github.com/prometheus/procfs/stat.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -16,6 +16,7 @@ package procfs import ( "bufio" "bytes" + "errors" "fmt" "io" "strconv" @@ -92,7 +93,7 @@ func parseCPUStat(line string) (CPUStat, int64, error) { &cpuStat.Iowait, &cpuStat.IRQ, &cpuStat.SoftIRQ, &cpuStat.Steal, &cpuStat.Guest, &cpuStat.GuestNice) - if err != nil && err != io.EOF { + if err != nil && !errors.Is(err, io.EOF) { return CPUStat{}, -1, fmt.Errorf("%w: couldn't parse %q (cpu): %w", ErrFileParse, line, err) } if count == 0 { diff --git a/vendor/github.com/prometheus/procfs/swaps.go b/vendor/github.com/prometheus/procfs/swaps.go index 65fec834bf..ee17bf4888 100644 --- a/vendor/github.com/prometheus/procfs/swaps.go +++ b/vendor/github.com/prometheus/procfs/swaps.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/thread.go b/vendor/github.com/prometheus/procfs/thread.go index 80e0e947be..0cfbb54184 100644 --- a/vendor/github.com/prometheus/procfs/thread.go +++ b/vendor/github.com/prometheus/procfs/thread.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/vendor/github.com/prometheus/procfs/vm.go b/vendor/github.com/prometheus/procfs/vm.go index 51c49d89e8..52180c03e2 100644 --- a/vendor/github.com/prometheus/procfs/vm.go +++ b/vendor/github.com/prometheus/procfs/vm.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,7 +12,6 @@ // limitations under the License. //go:build !windows -// +build !windows package procfs diff --git a/vendor/github.com/prometheus/procfs/zoneinfo.go b/vendor/github.com/prometheus/procfs/zoneinfo.go index e54d94b090..63d1898bc8 100644 --- a/vendor/github.com/prometheus/procfs/zoneinfo.go +++ b/vendor/github.com/prometheus/procfs/zoneinfo.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,7 +12,6 @@ // limitations under the License. //go:build !windows -// +build !windows package procfs @@ -88,11 +87,9 @@ func parseZoneinfo(zoneinfoData []byte) ([]Zoneinfo, error) { zoneinfo := []Zoneinfo{} - zoneinfoBlocks := bytes.Split(zoneinfoData, []byte("\nNode")) - for _, block := range zoneinfoBlocks { + for block := range bytes.SplitSeq(zoneinfoData, []byte("\nNode")) { var zoneinfoElement Zoneinfo - lines := strings.Split(string(block), "\n") - for _, line := range lines { + for line := range strings.SplitSeq(string(block), "\n") { if nodeZone := nodeZoneRE.FindStringSubmatch(line); nodeZone != nil { zoneinfoElement.Node = nodeZone[1] diff --git a/vendor/github.com/sirupsen/logrus/.golangci.yml b/vendor/github.com/sirupsen/logrus/.golangci.yml index 65dc285037..792db36181 100644 --- a/vendor/github.com/sirupsen/logrus/.golangci.yml +++ b/vendor/github.com/sirupsen/logrus/.golangci.yml @@ -1,40 +1,67 @@ +version: "2" run: - # do not run on test files yet tests: false - -# all available settings of specific linters -linters-settings: - errcheck: - # report about not checking of errors in type assetions: `a := b.(MyStruct)`; - # default is false: such cases aren't reported by default. - check-type-assertions: false - - # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; - # default is false: such cases aren't reported by default. - check-blank: false - - lll: - line-length: 100 - tab-width: 4 - - prealloc: - simple: false - range-loops: false - for-loops: false - - whitespace: - multi-if: false # Enforces newlines (or comments) after every multi-line if statement - multi-func: false # Enforces newlines (or comments) after every multi-line function signature - linters: enable: - - megacheck - - govet + - asasalint + - asciicheck + - bidichk + - bodyclose + - contextcheck + - durationcheck + - errchkjson + - errorlint + - exhaustive + - gocheckcompilerdirectives + - gochecksumtype + - gosec + - gosmopolitan + - loggercheck + - makezero + - musttag + - nilerr + - nilnesserr + - noctx + - protogetter + - reassign + - recvcheck + - rowserrcheck + - spancheck + - sqlclosecheck + - testifylint + - unparam + - zerologlint disable: - - maligned - prealloc - disable-all: false - presets: - - bugs - - unused - fast: false + settings: + errcheck: + check-type-assertions: false + check-blank: false + lll: + line-length: 100 + tab-width: 4 + prealloc: + simple: false + range-loops: false + for-loops: false + whitespace: + multi-if: false + multi-func: false + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/vendor/github.com/sirupsen/logrus/CHANGELOG.md b/vendor/github.com/sirupsen/logrus/CHANGELOG.md index 7567f61289..098608ff4b 100644 --- a/vendor/github.com/sirupsen/logrus/CHANGELOG.md +++ b/vendor/github.com/sirupsen/logrus/CHANGELOG.md @@ -37,7 +37,7 @@ Features: # 1.6.0 Fixes: * end of line cleanup - * revert the entry concurrency bug fix whic leads to deadlock under some circumstances + * revert the entry concurrency bug fix which leads to deadlock under some circumstances * update dependency on go-windows-terminal-sequences to fix a crash with go 1.14 Features: @@ -129,7 +129,7 @@ This new release introduces: which is mostly useful for logger wrapper * a fix reverting the immutability of the entry given as parameter to the hooks a new configuration field of the json formatter in order to put all the fields - in a nested dictionnary + in a nested dictionary * a new SetOutput method in the Logger * a new configuration of the textformatter to configure the name of the default keys * a new configuration of the text formatter to disable the level truncation diff --git a/vendor/github.com/sirupsen/logrus/README.md b/vendor/github.com/sirupsen/logrus/README.md index d1d4a85fd7..cc5dab7eb7 100644 --- a/vendor/github.com/sirupsen/logrus/README.md +++ b/vendor/github.com/sirupsen/logrus/README.md @@ -1,4 +1,4 @@ -# Logrus :walrus: [![Build Status](https://github.com/sirupsen/logrus/workflows/CI/badge.svg)](https://github.com/sirupsen/logrus/actions?query=workflow%3ACI) [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![Go Reference](https://pkg.go.dev/badge/github.com/sirupsen/logrus.svg)](https://pkg.go.dev/github.com/sirupsen/logrus) +# Logrus :walrus: [![Build Status](https://github.com/sirupsen/logrus/workflows/CI/badge.svg)](https://github.com/sirupsen/logrus/actions?query=workflow%3ACI) [![Go Reference](https://pkg.go.dev/badge/github.com/sirupsen/logrus.svg)](https://pkg.go.dev/github.com/sirupsen/logrus) Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger. @@ -40,7 +40,7 @@ plain text): ![Colored](http://i.imgur.com/PY7qMwd.png) -With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash +With `logrus.SetFormatter(&logrus.JSONFormatter{})`, for easy parsing by logstash or Splunk: ```text @@ -60,9 +60,9 @@ ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"} "time":"2014-03-10 19:57:38.562543128 -0400 EDT"} ``` -With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not +With the default `logrus.SetFormatter(&logrus.TextFormatter{})` when a TTY is not attached, the output is compatible with the -[logfmt](http://godoc.org/github.com/kr/logfmt) format: +[logfmt](https://pkg.go.dev/github.com/kr/logfmt) format: ```text time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8 @@ -75,17 +75,18 @@ time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x20822 To ensure this behaviour even if a TTY is attached, set your formatter as follows: ```go - log.SetFormatter(&log.TextFormatter{ - DisableColors: true, - FullTimestamp: true, - }) +logrus.SetFormatter(&logrus.TextFormatter{ + DisableColors: true, + FullTimestamp: true, +}) ``` #### Logging Method Name If you wish to add the calling method as a field, instruct the logger via: + ```go -log.SetReportCaller(true) +logrus.SetReportCaller(true) ``` This adds the caller as 'method' like so: @@ -100,11 +101,11 @@ time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcr Note that this does add measurable overhead - the cost will depend on the version of Go, but is between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your environment via benchmarks: -``` + +```bash go test -bench=.*CallerTracing ``` - #### Case-sensitivity The organization's name was changed to lower-case--and this will not be changed @@ -118,12 +119,10 @@ The simplest way to use Logrus is simply the package-level exported logger: ```go package main -import ( - log "github.com/sirupsen/logrus" -) +import "github.com/sirupsen/logrus" func main() { - log.WithFields(log.Fields{ + logrus.WithFields(logrus.Fields{ "animal": "walrus", }).Info("A walrus appears") } @@ -139,6 +138,7 @@ package main import ( "os" + log "github.com/sirupsen/logrus" ) @@ -190,26 +190,27 @@ package main import ( "os" + "github.com/sirupsen/logrus" ) // Create a new instance of the logger. You can have any number of instances. -var log = logrus.New() +var logger = logrus.New() func main() { // The API for setting attributes is a little different than the package level - // exported logger. See Godoc. - log.Out = os.Stdout + // exported logger. See Godoc. + logger.Out = os.Stdout // You could set this to any `io.Writer` such as a file // file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) // if err == nil { - // log.Out = file + // logger.Out = file // } else { - // log.Info("Failed to log to file, using default stderr") + // logger.Info("Failed to log to file, using default stderr") // } - log.WithFields(logrus.Fields{ + logger.WithFields(logrus.Fields{ "animal": "walrus", "size": 10, }).Info("A group of walrus emerges from the ocean") @@ -219,12 +220,12 @@ func main() { #### Fields Logrus encourages careful, structured logging through logging fields instead of -long, unparseable error messages. For example, instead of: `log.Fatalf("Failed +long, unparseable error messages. For example, instead of: `logrus.Fatalf("Failed to send event %s to topic %s with key %d")`, you should log the much more discoverable: ```go -log.WithFields(log.Fields{ +logrus.WithFields(logrus.Fields{ "event": event, "topic": topic, "key": key, @@ -245,12 +246,12 @@ seen as a hint you should add a field, however, you can still use the Often it's helpful to have fields _always_ attached to log statements in an application or parts of one. For example, you may want to always log the `request_id` and `user_ip` in the context of a request. Instead of writing -`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on +`logger.WithFields(logrus.Fields{"request_id": request_id, "user_ip": user_ip})` on every line, you can create a `logrus.Entry` to pass around instead: ```go -requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip}) -requestLogger.Info("something happened on that request") # will log request_id and user_ip +requestLogger := logger.WithFields(logrus.Fields{"request_id": request_id, "user_ip": user_ip}) +requestLogger.Info("something happened on that request") // will log request_id and user_ip requestLogger.Warn("something not great happened") ``` @@ -264,28 +265,31 @@ Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in `init`: ```go +package main + import ( - log "github.com/sirupsen/logrus" - "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake" - logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" "log/syslog" + + "github.com/sirupsen/logrus" + airbrake "gopkg.in/gemnasium/logrus-airbrake-hook.v2" + logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" ) func init() { // Use the Airbrake hook to report errors that have Error severity or above to // an exception tracker. You can create custom hooks, see the Hooks section. - log.AddHook(airbrake.NewHook(123, "xyz", "production")) + logrus.AddHook(airbrake.NewHook(123, "xyz", "production")) hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") if err != nil { - log.Error("Unable to connect to local syslog daemon") + logrus.Error("Unable to connect to local syslog daemon") } else { - log.AddHook(hook) + logrus.AddHook(hook) } } ``` -Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md). +Note: Syslog hooks also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md). A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks) @@ -295,15 +299,15 @@ A list of currently known service hooks can be found in this wiki [page](https:/ Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic. ```go -log.Trace("Something very low level.") -log.Debug("Useful debugging information.") -log.Info("Something noteworthy happened!") -log.Warn("You should probably take a look at this.") -log.Error("Something failed but I'm not quitting.") +logrus.Trace("Something very low level.") +logrus.Debug("Useful debugging information.") +logrus.Info("Something noteworthy happened!") +logrus.Warn("You should probably take a look at this.") +logrus.Error("Something failed but I'm not quitting.") // Calls os.Exit(1) after logging -log.Fatal("Bye.") +logrus.Fatal("Bye.") // Calls panic() after logging -log.Panic("I'm bailing.") +logrus.Panic("I'm bailing.") ``` You can set the logging level on a `Logger`, then it will only log entries with @@ -311,13 +315,13 @@ that severity or anything above it: ```go // Will log anything that is info or above (warn, error, fatal, panic). Default. -log.SetLevel(log.InfoLevel) +logrus.SetLevel(logrus.InfoLevel) ``` -It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose +It may be useful to set `logrus.Level = logrus.DebugLevel` in a debug or verbose environment if your application has that. -Note: If you want different log levels for global (`log.SetLevel(...)`) and syslog logging, please check the [syslog hook README](hooks/syslog/README.md#different-log-levels-for-local-and-remote-logging). +Note: If you want different log levels for global (`logrus.SetLevel(...)`) and syslog logging, please check the [syslog hook README](hooks/syslog/README.md#different-log-levels-for-local-and-remote-logging). #### Entries @@ -340,17 +344,17 @@ could do: ```go import ( - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) func init() { // do something here to set environment depending on an environment variable // or command-line flag if Environment == "production" { - log.SetFormatter(&log.JSONFormatter{}) + logrus.SetFormatter(&logrus.JSONFormatter{}) } else { // The TextFormatter is default, you don't actually have to do this. - log.SetFormatter(&log.TextFormatter{}) + logrus.SetFormatter(&logrus.TextFormatter{}) } } ``` @@ -372,11 +376,11 @@ The built-in logging formatters are: * When colors are enabled, levels are truncated to 4 characters by default. To disable truncation set the `DisableLevelTruncation` field to `true`. * When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text. - * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter). + * All options are listed in the [generated docs](https://pkg.go.dev/github.com/sirupsen/logrus#TextFormatter). * `logrus.JSONFormatter`. Logs fields as JSON. - * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter). + * All options are listed in the [generated docs](https://pkg.go.dev/github.com/sirupsen/logrus#JSONFormatter). -Third party logging formatters: +Third-party logging formatters: * [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine. * [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html). @@ -384,7 +388,7 @@ Third party logging formatters: * [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout. * [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo. * [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure. -* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files. +* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Save log to files. * [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added. You can define your formatter by implementing the `Formatter` interface, @@ -393,10 +397,9 @@ requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a default ones (see Entries section above): ```go -type MyJSONFormatter struct { -} +type MyJSONFormatter struct{} -log.SetFormatter(new(MyJSONFormatter)) +logrus.SetFormatter(new(MyJSONFormatter)) func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) { // Note this doesn't include Time, Level and Message which are available on @@ -455,17 +458,18 @@ entries. It should not be a feature of the application-level logger. #### Testing -Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides: +Logrus has a built-in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides: * decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook * a test logger (`test.NewNullLogger`) that just records log messages (and does not output any): ```go import( + "testing" + "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" - "testing" ) func TestSomething(t*testing.T){ @@ -486,15 +490,15 @@ func TestSomething(t*testing.T){ Logrus can register one or more functions that will be called when any `fatal` level message is logged. The registered handlers will be executed before logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need -to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted. +to gracefully shut down. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted. -``` -... +```go +// ... handler := func() { - // gracefully shutdown something... + // gracefully shut down something... } logrus.RegisterExitHandler(handler) -... +// ... ``` #### Thread safety @@ -502,7 +506,7 @@ logrus.RegisterExitHandler(handler) By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs. If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking. -Situation when locking is not needed includes: +Situations when locking is not needed include: * You have no hooks registered, or hooks calling is already thread-safe. diff --git a/vendor/github.com/sirupsen/logrus/appveyor.yml b/vendor/github.com/sirupsen/logrus/appveyor.yml index df9d65c3a5..e90f09ea68 100644 --- a/vendor/github.com/sirupsen/logrus/appveyor.yml +++ b/vendor/github.com/sirupsen/logrus/appveyor.yml @@ -1,14 +1,12 @@ -version: "{build}" +# Minimal stub to satisfy AppVeyor CI +version: 1.0.{build} platform: x64 -clone_folder: c:\gopath\src\github.com\sirupsen\logrus -environment: - GOPATH: c:\gopath +shallow_clone: true + branches: only: - master -install: - - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - - go version + - main + build_script: - - go get -t - - go test + - echo "No-op build to satisfy AppVeyor CI" diff --git a/vendor/github.com/sirupsen/logrus/entry.go b/vendor/github.com/sirupsen/logrus/entry.go index 71cdbbc35d..71d796d0b1 100644 --- a/vendor/github.com/sirupsen/logrus/entry.go +++ b/vendor/github.com/sirupsen/logrus/entry.go @@ -34,13 +34,15 @@ func init() { minimumCallerDepth = 1 } -// Defines the key when adding errors using WithError. +// ErrorKey defines the key when adding errors using [WithError], [Logger.WithError]. var ErrorKey = "error" -// An entry is the final or intermediate Logrus logging entry. It contains all +// Entry is the final or intermediate Logrus logging entry. It contains all // the fields passed with WithField{,s}. It's finally logged when Trace, Debug, // Info, Warn, Error, Fatal or Panic is called on it. These objects can be // reused and passed around as much as you wish to avoid field duplication. +// +//nolint:recvcheck // the methods of "Entry" use pointer receiver and non-pointer receiver. type Entry struct { Logger *Logger @@ -86,12 +88,12 @@ func (entry *Entry) Dup() *Entry { return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err} } -// Returns the bytes representation of this entry from the formatter. +// Bytes returns the bytes representation of this entry from the formatter. func (entry *Entry) Bytes() ([]byte, error) { return entry.Logger.Formatter.Format(entry) } -// Returns the string representation from the reader and ultimately the +// String returns the string representation from the reader and ultimately the // formatter. func (entry *Entry) String() (string, error) { serialized, err := entry.Bytes() @@ -102,12 +104,13 @@ func (entry *Entry) String() (string, error) { return str, nil } -// Add an error as single field (using the key defined in ErrorKey) to the Entry. +// WithError adds an error as single field (using the key defined in [ErrorKey]) +// to the Entry. func (entry *Entry) WithError(err error) *Entry { return entry.WithField(ErrorKey, err) } -// Add a context to the Entry. +// WithContext adds a context to the Entry. func (entry *Entry) WithContext(ctx context.Context) *Entry { dataCopy := make(Fields, len(entry.Data)) for k, v := range entry.Data { @@ -116,12 +119,12 @@ func (entry *Entry) WithContext(ctx context.Context) *Entry { return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx} } -// Add a single field to the Entry. +// WithField adds a single field to the Entry. func (entry *Entry) WithField(key string, value interface{}) *Entry { return entry.WithFields(Fields{key: value}) } -// Add a map of fields to the Entry. +// WithFields adds a map of fields to the Entry. func (entry *Entry) WithFields(fields Fields) *Entry { data := make(Fields, len(entry.Data)+len(fields)) for k, v := range entry.Data { @@ -150,7 +153,7 @@ func (entry *Entry) WithFields(fields Fields) *Entry { return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context} } -// Overrides the time of the Entry. +// WithTime overrides the time of the Entry. func (entry *Entry) WithTime(t time.Time) *Entry { dataCopy := make(Fields, len(entry.Data)) for k, v := range entry.Data { @@ -204,7 +207,7 @@ func getCaller() *runtime.Frame { // If the caller isn't part of this package, we're done if pkg != logrusPackage { - return &f //nolint:scopelint + return &f } } @@ -432,7 +435,7 @@ func (entry *Entry) Panicln(args ...interface{}) { entry.Logln(PanicLevel, args...) } -// Sprintlnn => Sprint no newline. This is to get the behavior of how +// sprintlnn => Sprint no newline. This is to get the behavior of how // fmt.Sprintln where spaces are always added between operands, regardless of // their type. Instead of vendoring the Sprintln implementation to spare a // string allocation, we do the simplest thing. diff --git a/vendor/github.com/sirupsen/logrus/hooks.go b/vendor/github.com/sirupsen/logrus/hooks.go index 3f151cdc39..9ab978a457 100644 --- a/vendor/github.com/sirupsen/logrus/hooks.go +++ b/vendor/github.com/sirupsen/logrus/hooks.go @@ -1,16 +1,16 @@ package logrus -// A hook to be fired when logging on the logging levels returned from -// `Levels()` on your implementation of the interface. Note that this is not +// Hook describes hooks to be fired when logging on the logging levels returned from +// [Hook.Levels] on your implementation of the interface. Note that this is not // fired in a goroutine or a channel with workers, you should handle such -// functionality yourself if your call is non-blocking and you don't wish for +// functionality yourself if your call is non-blocking, and you don't wish for // the logging calls for levels returned from `Levels()` to block. type Hook interface { Levels() []Level Fire(*Entry) error } -// Internal type for storing the hooks on a logger instance. +// LevelHooks is an internal type for storing the hooks on a logger instance. type LevelHooks map[Level][]Hook // Add a hook to an instance of logger. This is called with diff --git a/vendor/github.com/sirupsen/logrus/logger.go b/vendor/github.com/sirupsen/logrus/logger.go index 5ff0aef6d3..f5b8c439ee 100644 --- a/vendor/github.com/sirupsen/logrus/logger.go +++ b/vendor/github.com/sirupsen/logrus/logger.go @@ -72,16 +72,16 @@ func (mw *MutexWrap) Disable() { mw.disabled = true } -// Creates a new logger. Configuration should be set by changing `Formatter`, -// `Out` and `Hooks` directly on the default logger instance. You can also just +// New Creates a new logger. Configuration should be set by changing [Formatter], +// Out and Hooks directly on the default Logger instance. You can also just // instantiate your own: // -// var log = &logrus.Logger{ -// Out: os.Stderr, -// Formatter: new(logrus.TextFormatter), -// Hooks: make(logrus.LevelHooks), -// Level: logrus.DebugLevel, -// } +// var log = &logrus.Logger{ +// Out: os.Stderr, +// Formatter: new(logrus.TextFormatter), +// Hooks: make(logrus.LevelHooks), +// Level: logrus.DebugLevel, +// } // // It's recommended to make this a global instance called `log`. func New() *Logger { @@ -118,30 +118,30 @@ func (logger *Logger) WithField(key string, value interface{}) *Entry { return entry.WithField(key, value) } -// Adds a struct of fields to the log entry. All it does is call `WithField` for -// each `Field`. +// WithFields adds a struct of fields to the log entry. It calls [Entry.WithField] +// for each Field. func (logger *Logger) WithFields(fields Fields) *Entry { entry := logger.newEntry() defer logger.releaseEntry(entry) return entry.WithFields(fields) } -// Add an error as single field to the log entry. All it does is call -// `WithError` for the given `error`. +// WithError adds an error as single field to the log entry. It calls +// [Entry.WithError] for the given error. func (logger *Logger) WithError(err error) *Entry { entry := logger.newEntry() defer logger.releaseEntry(entry) return entry.WithError(err) } -// Add a context to the log entry. +// WithContext add a context to the log entry. func (logger *Logger) WithContext(ctx context.Context) *Entry { entry := logger.newEntry() defer logger.releaseEntry(entry) return entry.WithContext(ctx) } -// Overrides the time of the log entry. +// WithTime overrides the time of the log entry. func (logger *Logger) WithTime(t time.Time) *Entry { entry := logger.newEntry() defer logger.releaseEntry(entry) @@ -347,9 +347,9 @@ func (logger *Logger) Exit(code int) { logger.ExitFunc(code) } -//When file is opened with appending mode, it's safe to -//write concurrently to a file (within 4k message on Linux). -//In these cases user can choose to disable the lock. +// SetNoLock disables the lock for situations where a file is opened with +// appending mode, and safe for concurrent writes to the file (within 4k +// message on Linux). In these cases user can choose to disable the lock. func (logger *Logger) SetNoLock() { logger.mu.Disable() } diff --git a/vendor/github.com/sirupsen/logrus/logrus.go b/vendor/github.com/sirupsen/logrus/logrus.go index 2f16224cb9..37fc4fef85 100644 --- a/vendor/github.com/sirupsen/logrus/logrus.go +++ b/vendor/github.com/sirupsen/logrus/logrus.go @@ -6,13 +6,15 @@ import ( "strings" ) -// Fields type, used to pass to `WithFields`. +// Fields type, used to pass to [WithFields]. type Fields map[string]interface{} // Level type +// +//nolint:recvcheck // the methods of "Entry" use pointer receiver and non-pointer receiver. type Level uint32 -// Convert the Level to a string. E.g. PanicLevel becomes "panic". +// Convert the Level to a string. E.g. [PanicLevel] becomes "panic". func (level Level) String() string { if b, err := level.MarshalText(); err == nil { return string(b) @@ -77,7 +79,7 @@ func (level Level) MarshalText() ([]byte, error) { return nil, fmt.Errorf("not a valid logrus level %d", level) } -// A constant exposing all logging levels +// AllLevels exposing all logging levels. var AllLevels = []Level{ PanicLevel, FatalLevel, @@ -119,8 +121,8 @@ var ( ) // StdLogger is what your logrus-enabled library should take, that way -// it'll accept a stdlib logger and a logrus logger. There's no standard -// interface, this is the closest we get, unfortunately. +// it'll accept a stdlib logger ([log.Logger]) and a logrus logger. +// There's no standard interface, so this is the closest we get, unfortunately. type StdLogger interface { Print(...interface{}) Printf(string, ...interface{}) @@ -135,7 +137,8 @@ type StdLogger interface { Panicln(...interface{}) } -// The FieldLogger interface generalizes the Entry and Logger types +// FieldLogger extends the [StdLogger] interface, generalizing +// the [Entry] and [Logger] types. type FieldLogger interface { WithField(key string, value interface{}) *Entry WithFields(fields Fields) *Entry @@ -176,8 +179,9 @@ type FieldLogger interface { // IsPanicEnabled() bool } -// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is -// here for consistancy. Do not use. Use Logger or Entry instead. +// Ext1FieldLogger (the first extension to [FieldLogger]) is superfluous, it is +// here for consistency. Do not use. Use [FieldLogger], [Logger] or [Entry] +// instead. type Ext1FieldLogger interface { FieldLogger Tracef(format string, args ...interface{}) diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go b/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go index 499789984d..69956b425a 100644 --- a/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go +++ b/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go @@ -1,4 +1,4 @@ -// +build darwin dragonfly freebsd netbsd openbsd +// +build darwin dragonfly freebsd netbsd openbsd hurd // +build !js package logrus diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_unix.go b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go index 04748b8515..c9aed267a4 100644 --- a/vendor/github.com/sirupsen/logrus/terminal_check_unix.go +++ b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go @@ -1,5 +1,7 @@ +//go:build (linux || aix || zos) && !js && !wasi // +build linux aix zos // +build !js +// +build !wasi package logrus diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_wasi.go b/vendor/github.com/sirupsen/logrus/terminal_check_wasi.go new file mode 100644 index 0000000000..2822b212fb --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_wasi.go @@ -0,0 +1,8 @@ +//go:build wasi +// +build wasi + +package logrus + +func isTerminal(fd int) bool { + return false +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_wasip1.go b/vendor/github.com/sirupsen/logrus/terminal_check_wasip1.go new file mode 100644 index 0000000000..108a6be12b --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_wasip1.go @@ -0,0 +1,8 @@ +//go:build wasip1 +// +build wasip1 + +package logrus + +func isTerminal(fd int) bool { + return false +} diff --git a/vendor/github.com/sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go index be2c6efe5e..6dfeb18b10 100644 --- a/vendor/github.com/sirupsen/logrus/text_formatter.go +++ b/vendor/github.com/sirupsen/logrus/text_formatter.go @@ -306,6 +306,7 @@ func (f *TextFormatter) needsQuoting(text string) bool { return false } for _, ch := range text { + //nolint:staticcheck // QF1001: could apply De Morgan's law if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || @@ -334,6 +335,6 @@ func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { if !f.needsQuoting(stringVal) { b.WriteString(stringVal) } else { - b.WriteString(fmt.Sprintf("%q", stringVal)) + fmt.Fprintf(b, "%q", stringVal) } } diff --git a/vendor/go.yaml.in/yaml/v2/.travis.yml b/vendor/go.yaml.in/yaml/v2/.travis.yml deleted file mode 100644 index 7348c50c0c..0000000000 --- a/vendor/go.yaml.in/yaml/v2/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: go - -go: - - "1.4.x" - - "1.5.x" - - "1.6.x" - - "1.7.x" - - "1.8.x" - - "1.9.x" - - "1.10.x" - - "1.11.x" - - "1.12.x" - - "1.13.x" - - "1.14.x" - - "tip" - -go_import_path: gopkg.in/yaml.v2 diff --git a/vendor/go.yaml.in/yaml/v2/LICENSE b/vendor/go.yaml.in/yaml/v2/LICENSE deleted file mode 100644 index 8dada3edaf..0000000000 --- a/vendor/go.yaml.in/yaml/v2/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/go.yaml.in/yaml/v2/LICENSE.libyaml b/vendor/go.yaml.in/yaml/v2/LICENSE.libyaml deleted file mode 100644 index 8da58fbf6f..0000000000 --- a/vendor/go.yaml.in/yaml/v2/LICENSE.libyaml +++ /dev/null @@ -1,31 +0,0 @@ -The following files were ported to Go from C files of libyaml, and thus -are still covered by their original copyright and license: - - apic.go - emitterc.go - parserc.go - readerc.go - scannerc.go - writerc.go - yamlh.go - yamlprivateh.go - -Copyright (c) 2006 Kirill Simonov - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/go.yaml.in/yaml/v2/NOTICE b/vendor/go.yaml.in/yaml/v2/NOTICE deleted file mode 100644 index 866d74a7ad..0000000000 --- a/vendor/go.yaml.in/yaml/v2/NOTICE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2011-2016 Canonical Ltd. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/vendor/go.yaml.in/yaml/v2/README.md b/vendor/go.yaml.in/yaml/v2/README.md deleted file mode 100644 index c9388da425..0000000000 --- a/vendor/go.yaml.in/yaml/v2/README.md +++ /dev/null @@ -1,131 +0,0 @@ -# YAML support for the Go language - -Introduction ------------- - -The yaml package enables Go programs to comfortably encode and decode YAML -values. It was developed within [Canonical](https://www.canonical.com) as -part of the [juju](https://juju.ubuntu.com) project, and is based on a -pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML) -C library to parse and generate YAML data quickly and reliably. - -Compatibility -------------- - -The yaml package supports most of YAML 1.1 and 1.2, including support for -anchors, tags, map merging, etc. Multi-document unmarshalling is not yet -implemented, and base-60 floats from YAML 1.1 are purposefully not -supported since they're a poor design and are gone in YAML 1.2. - -Installation and usage ----------------------- - -The import path for the package is *go.yaml.in/yaml/v2*. - -To install it, run: - - go get go.yaml.in/yaml/v2 - -API documentation ------------------ - -See: - -API stability -------------- - -The package API for yaml v2 will remain stable as described in [gopkg.in](https://gopkg.in). - - -License -------- - -The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details. - - -Example -------- - -```Go -package main - -import ( - "fmt" - "log" - - "go.yaml.in/yaml/v2" -) - -var data = ` -a: Easy! -b: - c: 2 - d: [3, 4] -` - -// Note: struct fields must be public in order for unmarshal to -// correctly populate the data. -type T struct { - A string - B struct { - RenamedC int `yaml:"c"` - D []int `yaml:",flow"` - } -} - -func main() { - t := T{} - - err := yaml.Unmarshal([]byte(data), &t) - if err != nil { - log.Fatalf("error: %v", err) - } - fmt.Printf("--- t:\n%v\n\n", t) - - d, err := yaml.Marshal(&t) - if err != nil { - log.Fatalf("error: %v", err) - } - fmt.Printf("--- t dump:\n%s\n\n", string(d)) - - m := make(map[interface{}]interface{}) - - err = yaml.Unmarshal([]byte(data), &m) - if err != nil { - log.Fatalf("error: %v", err) - } - fmt.Printf("--- m:\n%v\n\n", m) - - d, err = yaml.Marshal(&m) - if err != nil { - log.Fatalf("error: %v", err) - } - fmt.Printf("--- m dump:\n%s\n\n", string(d)) -} -``` - -This example will generate the following output: - -``` ---- t: -{Easy! {2 [3 4]}} - ---- t dump: -a: Easy! -b: - c: 2 - d: [3, 4] - - ---- m: -map[a:Easy! b:map[c:2 d:[3 4]]] - ---- m dump: -a: Easy! -b: - c: 2 - d: - - 3 - - 4 -``` - diff --git a/vendor/go.yaml.in/yaml/v2/apic.go b/vendor/go.yaml.in/yaml/v2/apic.go deleted file mode 100644 index acf71402cf..0000000000 --- a/vendor/go.yaml.in/yaml/v2/apic.go +++ /dev/null @@ -1,744 +0,0 @@ -package yaml - -import ( - "io" -) - -func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) { - //fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens)) - - // Check if we can move the queue at the beginning of the buffer. - if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) { - if parser.tokens_head != len(parser.tokens) { - copy(parser.tokens, parser.tokens[parser.tokens_head:]) - } - parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head] - parser.tokens_head = 0 - } - parser.tokens = append(parser.tokens, *token) - if pos < 0 { - return - } - copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:]) - parser.tokens[parser.tokens_head+pos] = *token -} - -// Create a new parser object. -func yaml_parser_initialize(parser *yaml_parser_t) bool { - *parser = yaml_parser_t{ - raw_buffer: make([]byte, 0, input_raw_buffer_size), - buffer: make([]byte, 0, input_buffer_size), - } - return true -} - -// Destroy a parser object. -func yaml_parser_delete(parser *yaml_parser_t) { - *parser = yaml_parser_t{} -} - -// String read handler. -func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { - if parser.input_pos == len(parser.input) { - return 0, io.EOF - } - n = copy(buffer, parser.input[parser.input_pos:]) - parser.input_pos += n - return n, nil -} - -// Reader read handler. -func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { - return parser.input_reader.Read(buffer) -} - -// Set a string input. -func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) { - if parser.read_handler != nil { - panic("must set the input source only once") - } - parser.read_handler = yaml_string_read_handler - parser.input = input - parser.input_pos = 0 -} - -// Set a file input. -func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) { - if parser.read_handler != nil { - panic("must set the input source only once") - } - parser.read_handler = yaml_reader_read_handler - parser.input_reader = r -} - -// Set the source encoding. -func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) { - if parser.encoding != yaml_ANY_ENCODING { - panic("must set the encoding only once") - } - parser.encoding = encoding -} - -var disableLineWrapping = false - -// Create a new emitter object. -func yaml_emitter_initialize(emitter *yaml_emitter_t) { - *emitter = yaml_emitter_t{ - buffer: make([]byte, output_buffer_size), - raw_buffer: make([]byte, 0, output_raw_buffer_size), - states: make([]yaml_emitter_state_t, 0, initial_stack_size), - events: make([]yaml_event_t, 0, initial_queue_size), - } - if disableLineWrapping { - emitter.best_width = -1 - } -} - -// Destroy an emitter object. -func yaml_emitter_delete(emitter *yaml_emitter_t) { - *emitter = yaml_emitter_t{} -} - -// String write handler. -func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error { - *emitter.output_buffer = append(*emitter.output_buffer, buffer...) - return nil -} - -// yaml_writer_write_handler uses emitter.output_writer to write the -// emitted text. -func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error { - _, err := emitter.output_writer.Write(buffer) - return err -} - -// Set a string output. -func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) { - if emitter.write_handler != nil { - panic("must set the output target only once") - } - emitter.write_handler = yaml_string_write_handler - emitter.output_buffer = output_buffer -} - -// Set a file output. -func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) { - if emitter.write_handler != nil { - panic("must set the output target only once") - } - emitter.write_handler = yaml_writer_write_handler - emitter.output_writer = w -} - -// Set the output encoding. -func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) { - if emitter.encoding != yaml_ANY_ENCODING { - panic("must set the output encoding only once") - } - emitter.encoding = encoding -} - -// Set the canonical output style. -func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) { - emitter.canonical = canonical -} - -//// Set the indentation increment. -func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) { - if indent < 2 || indent > 9 { - indent = 2 - } - emitter.best_indent = indent -} - -// Set the preferred line width. -func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) { - if width < 0 { - width = -1 - } - emitter.best_width = width -} - -// Set if unescaped non-ASCII characters are allowed. -func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) { - emitter.unicode = unicode -} - -// Set the preferred line break character. -func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) { - emitter.line_break = line_break -} - -///* -// * Destroy a token object. -// */ -// -//YAML_DECLARE(void) -//yaml_token_delete(yaml_token_t *token) -//{ -// assert(token); // Non-NULL token object expected. -// -// switch (token.type) -// { -// case YAML_TAG_DIRECTIVE_TOKEN: -// yaml_free(token.data.tag_directive.handle); -// yaml_free(token.data.tag_directive.prefix); -// break; -// -// case YAML_ALIAS_TOKEN: -// yaml_free(token.data.alias.value); -// break; -// -// case YAML_ANCHOR_TOKEN: -// yaml_free(token.data.anchor.value); -// break; -// -// case YAML_TAG_TOKEN: -// yaml_free(token.data.tag.handle); -// yaml_free(token.data.tag.suffix); -// break; -// -// case YAML_SCALAR_TOKEN: -// yaml_free(token.data.scalar.value); -// break; -// -// default: -// break; -// } -// -// memset(token, 0, sizeof(yaml_token_t)); -//} -// -///* -// * Check if a string is a valid UTF-8 sequence. -// * -// * Check 'reader.c' for more details on UTF-8 encoding. -// */ -// -//static int -//yaml_check_utf8(yaml_char_t *start, size_t length) -//{ -// yaml_char_t *end = start+length; -// yaml_char_t *pointer = start; -// -// while (pointer < end) { -// unsigned char octet; -// unsigned int width; -// unsigned int value; -// size_t k; -// -// octet = pointer[0]; -// width = (octet & 0x80) == 0x00 ? 1 : -// (octet & 0xE0) == 0xC0 ? 2 : -// (octet & 0xF0) == 0xE0 ? 3 : -// (octet & 0xF8) == 0xF0 ? 4 : 0; -// value = (octet & 0x80) == 0x00 ? octet & 0x7F : -// (octet & 0xE0) == 0xC0 ? octet & 0x1F : -// (octet & 0xF0) == 0xE0 ? octet & 0x0F : -// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; -// if (!width) return 0; -// if (pointer+width > end) return 0; -// for (k = 1; k < width; k ++) { -// octet = pointer[k]; -// if ((octet & 0xC0) != 0x80) return 0; -// value = (value << 6) + (octet & 0x3F); -// } -// if (!((width == 1) || -// (width == 2 && value >= 0x80) || -// (width == 3 && value >= 0x800) || -// (width == 4 && value >= 0x10000))) return 0; -// -// pointer += width; -// } -// -// return 1; -//} -// - -// Create STREAM-START. -func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) { - *event = yaml_event_t{ - typ: yaml_STREAM_START_EVENT, - encoding: encoding, - } -} - -// Create STREAM-END. -func yaml_stream_end_event_initialize(event *yaml_event_t) { - *event = yaml_event_t{ - typ: yaml_STREAM_END_EVENT, - } -} - -// Create DOCUMENT-START. -func yaml_document_start_event_initialize( - event *yaml_event_t, - version_directive *yaml_version_directive_t, - tag_directives []yaml_tag_directive_t, - implicit bool, -) { - *event = yaml_event_t{ - typ: yaml_DOCUMENT_START_EVENT, - version_directive: version_directive, - tag_directives: tag_directives, - implicit: implicit, - } -} - -// Create DOCUMENT-END. -func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) { - *event = yaml_event_t{ - typ: yaml_DOCUMENT_END_EVENT, - implicit: implicit, - } -} - -///* -// * Create ALIAS. -// */ -// -//YAML_DECLARE(int) -//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t) -//{ -// mark yaml_mark_t = { 0, 0, 0 } -// anchor_copy *yaml_char_t = NULL -// -// assert(event) // Non-NULL event object is expected. -// assert(anchor) // Non-NULL anchor is expected. -// -// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0 -// -// anchor_copy = yaml_strdup(anchor) -// if (!anchor_copy) -// return 0 -// -// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark) -// -// return 1 -//} - -// Create SCALAR. -func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool { - *event = yaml_event_t{ - typ: yaml_SCALAR_EVENT, - anchor: anchor, - tag: tag, - value: value, - implicit: plain_implicit, - quoted_implicit: quoted_implicit, - style: yaml_style_t(style), - } - return true -} - -// Create SEQUENCE-START. -func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool { - *event = yaml_event_t{ - typ: yaml_SEQUENCE_START_EVENT, - anchor: anchor, - tag: tag, - implicit: implicit, - style: yaml_style_t(style), - } - return true -} - -// Create SEQUENCE-END. -func yaml_sequence_end_event_initialize(event *yaml_event_t) bool { - *event = yaml_event_t{ - typ: yaml_SEQUENCE_END_EVENT, - } - return true -} - -// Create MAPPING-START. -func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) { - *event = yaml_event_t{ - typ: yaml_MAPPING_START_EVENT, - anchor: anchor, - tag: tag, - implicit: implicit, - style: yaml_style_t(style), - } -} - -// Create MAPPING-END. -func yaml_mapping_end_event_initialize(event *yaml_event_t) { - *event = yaml_event_t{ - typ: yaml_MAPPING_END_EVENT, - } -} - -// Destroy an event object. -func yaml_event_delete(event *yaml_event_t) { - *event = yaml_event_t{} -} - -///* -// * Create a document object. -// */ -// -//YAML_DECLARE(int) -//yaml_document_initialize(document *yaml_document_t, -// version_directive *yaml_version_directive_t, -// tag_directives_start *yaml_tag_directive_t, -// tag_directives_end *yaml_tag_directive_t, -// start_implicit int, end_implicit int) -//{ -// struct { -// error yaml_error_type_t -// } context -// struct { -// start *yaml_node_t -// end *yaml_node_t -// top *yaml_node_t -// } nodes = { NULL, NULL, NULL } -// version_directive_copy *yaml_version_directive_t = NULL -// struct { -// start *yaml_tag_directive_t -// end *yaml_tag_directive_t -// top *yaml_tag_directive_t -// } tag_directives_copy = { NULL, NULL, NULL } -// value yaml_tag_directive_t = { NULL, NULL } -// mark yaml_mark_t = { 0, 0, 0 } -// -// assert(document) // Non-NULL document object is expected. -// assert((tag_directives_start && tag_directives_end) || -// (tag_directives_start == tag_directives_end)) -// // Valid tag directives are expected. -// -// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error -// -// if (version_directive) { -// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t)) -// if (!version_directive_copy) goto error -// version_directive_copy.major = version_directive.major -// version_directive_copy.minor = version_directive.minor -// } -// -// if (tag_directives_start != tag_directives_end) { -// tag_directive *yaml_tag_directive_t -// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE)) -// goto error -// for (tag_directive = tag_directives_start -// tag_directive != tag_directives_end; tag_directive ++) { -// assert(tag_directive.handle) -// assert(tag_directive.prefix) -// if (!yaml_check_utf8(tag_directive.handle, -// strlen((char *)tag_directive.handle))) -// goto error -// if (!yaml_check_utf8(tag_directive.prefix, -// strlen((char *)tag_directive.prefix))) -// goto error -// value.handle = yaml_strdup(tag_directive.handle) -// value.prefix = yaml_strdup(tag_directive.prefix) -// if (!value.handle || !value.prefix) goto error -// if (!PUSH(&context, tag_directives_copy, value)) -// goto error -// value.handle = NULL -// value.prefix = NULL -// } -// } -// -// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy, -// tag_directives_copy.start, tag_directives_copy.top, -// start_implicit, end_implicit, mark, mark) -// -// return 1 -// -//error: -// STACK_DEL(&context, nodes) -// yaml_free(version_directive_copy) -// while (!STACK_EMPTY(&context, tag_directives_copy)) { -// value yaml_tag_directive_t = POP(&context, tag_directives_copy) -// yaml_free(value.handle) -// yaml_free(value.prefix) -// } -// STACK_DEL(&context, tag_directives_copy) -// yaml_free(value.handle) -// yaml_free(value.prefix) -// -// return 0 -//} -// -///* -// * Destroy a document object. -// */ -// -//YAML_DECLARE(void) -//yaml_document_delete(document *yaml_document_t) -//{ -// struct { -// error yaml_error_type_t -// } context -// tag_directive *yaml_tag_directive_t -// -// context.error = YAML_NO_ERROR // Eliminate a compiler warning. -// -// assert(document) // Non-NULL document object is expected. -// -// while (!STACK_EMPTY(&context, document.nodes)) { -// node yaml_node_t = POP(&context, document.nodes) -// yaml_free(node.tag) -// switch (node.type) { -// case YAML_SCALAR_NODE: -// yaml_free(node.data.scalar.value) -// break -// case YAML_SEQUENCE_NODE: -// STACK_DEL(&context, node.data.sequence.items) -// break -// case YAML_MAPPING_NODE: -// STACK_DEL(&context, node.data.mapping.pairs) -// break -// default: -// assert(0) // Should not happen. -// } -// } -// STACK_DEL(&context, document.nodes) -// -// yaml_free(document.version_directive) -// for (tag_directive = document.tag_directives.start -// tag_directive != document.tag_directives.end -// tag_directive++) { -// yaml_free(tag_directive.handle) -// yaml_free(tag_directive.prefix) -// } -// yaml_free(document.tag_directives.start) -// -// memset(document, 0, sizeof(yaml_document_t)) -//} -// -///** -// * Get a document node. -// */ -// -//YAML_DECLARE(yaml_node_t *) -//yaml_document_get_node(document *yaml_document_t, index int) -//{ -// assert(document) // Non-NULL document object is expected. -// -// if (index > 0 && document.nodes.start + index <= document.nodes.top) { -// return document.nodes.start + index - 1 -// } -// return NULL -//} -// -///** -// * Get the root object. -// */ -// -//YAML_DECLARE(yaml_node_t *) -//yaml_document_get_root_node(document *yaml_document_t) -//{ -// assert(document) // Non-NULL document object is expected. -// -// if (document.nodes.top != document.nodes.start) { -// return document.nodes.start -// } -// return NULL -//} -// -///* -// * Add a scalar node to a document. -// */ -// -//YAML_DECLARE(int) -//yaml_document_add_scalar(document *yaml_document_t, -// tag *yaml_char_t, value *yaml_char_t, length int, -// style yaml_scalar_style_t) -//{ -// struct { -// error yaml_error_type_t -// } context -// mark yaml_mark_t = { 0, 0, 0 } -// tag_copy *yaml_char_t = NULL -// value_copy *yaml_char_t = NULL -// node yaml_node_t -// -// assert(document) // Non-NULL document object is expected. -// assert(value) // Non-NULL value is expected. -// -// if (!tag) { -// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG -// } -// -// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error -// tag_copy = yaml_strdup(tag) -// if (!tag_copy) goto error -// -// if (length < 0) { -// length = strlen((char *)value) -// } -// -// if (!yaml_check_utf8(value, length)) goto error -// value_copy = yaml_malloc(length+1) -// if (!value_copy) goto error -// memcpy(value_copy, value, length) -// value_copy[length] = '\0' -// -// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark) -// if (!PUSH(&context, document.nodes, node)) goto error -// -// return document.nodes.top - document.nodes.start -// -//error: -// yaml_free(tag_copy) -// yaml_free(value_copy) -// -// return 0 -//} -// -///* -// * Add a sequence node to a document. -// */ -// -//YAML_DECLARE(int) -//yaml_document_add_sequence(document *yaml_document_t, -// tag *yaml_char_t, style yaml_sequence_style_t) -//{ -// struct { -// error yaml_error_type_t -// } context -// mark yaml_mark_t = { 0, 0, 0 } -// tag_copy *yaml_char_t = NULL -// struct { -// start *yaml_node_item_t -// end *yaml_node_item_t -// top *yaml_node_item_t -// } items = { NULL, NULL, NULL } -// node yaml_node_t -// -// assert(document) // Non-NULL document object is expected. -// -// if (!tag) { -// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG -// } -// -// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error -// tag_copy = yaml_strdup(tag) -// if (!tag_copy) goto error -// -// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error -// -// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end, -// style, mark, mark) -// if (!PUSH(&context, document.nodes, node)) goto error -// -// return document.nodes.top - document.nodes.start -// -//error: -// STACK_DEL(&context, items) -// yaml_free(tag_copy) -// -// return 0 -//} -// -///* -// * Add a mapping node to a document. -// */ -// -//YAML_DECLARE(int) -//yaml_document_add_mapping(document *yaml_document_t, -// tag *yaml_char_t, style yaml_mapping_style_t) -//{ -// struct { -// error yaml_error_type_t -// } context -// mark yaml_mark_t = { 0, 0, 0 } -// tag_copy *yaml_char_t = NULL -// struct { -// start *yaml_node_pair_t -// end *yaml_node_pair_t -// top *yaml_node_pair_t -// } pairs = { NULL, NULL, NULL } -// node yaml_node_t -// -// assert(document) // Non-NULL document object is expected. -// -// if (!tag) { -// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG -// } -// -// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error -// tag_copy = yaml_strdup(tag) -// if (!tag_copy) goto error -// -// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error -// -// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end, -// style, mark, mark) -// if (!PUSH(&context, document.nodes, node)) goto error -// -// return document.nodes.top - document.nodes.start -// -//error: -// STACK_DEL(&context, pairs) -// yaml_free(tag_copy) -// -// return 0 -//} -// -///* -// * Append an item to a sequence node. -// */ -// -//YAML_DECLARE(int) -//yaml_document_append_sequence_item(document *yaml_document_t, -// sequence int, item int) -//{ -// struct { -// error yaml_error_type_t -// } context -// -// assert(document) // Non-NULL document is required. -// assert(sequence > 0 -// && document.nodes.start + sequence <= document.nodes.top) -// // Valid sequence id is required. -// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE) -// // A sequence node is required. -// assert(item > 0 && document.nodes.start + item <= document.nodes.top) -// // Valid item id is required. -// -// if (!PUSH(&context, -// document.nodes.start[sequence-1].data.sequence.items, item)) -// return 0 -// -// return 1 -//} -// -///* -// * Append a pair of a key and a value to a mapping node. -// */ -// -//YAML_DECLARE(int) -//yaml_document_append_mapping_pair(document *yaml_document_t, -// mapping int, key int, value int) -//{ -// struct { -// error yaml_error_type_t -// } context -// -// pair yaml_node_pair_t -// -// assert(document) // Non-NULL document is required. -// assert(mapping > 0 -// && document.nodes.start + mapping <= document.nodes.top) -// // Valid mapping id is required. -// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE) -// // A mapping node is required. -// assert(key > 0 && document.nodes.start + key <= document.nodes.top) -// // Valid key id is required. -// assert(value > 0 && document.nodes.start + value <= document.nodes.top) -// // Valid value id is required. -// -// pair.key = key -// pair.value = value -// -// if (!PUSH(&context, -// document.nodes.start[mapping-1].data.mapping.pairs, pair)) -// return 0 -// -// return 1 -//} -// -// diff --git a/vendor/go.yaml.in/yaml/v2/decode.go b/vendor/go.yaml.in/yaml/v2/decode.go deleted file mode 100644 index 129bc2a97d..0000000000 --- a/vendor/go.yaml.in/yaml/v2/decode.go +++ /dev/null @@ -1,815 +0,0 @@ -package yaml - -import ( - "encoding" - "encoding/base64" - "fmt" - "io" - "math" - "reflect" - "strconv" - "time" -) - -const ( - documentNode = 1 << iota - mappingNode - sequenceNode - scalarNode - aliasNode -) - -type node struct { - kind int - line, column int - tag string - // For an alias node, alias holds the resolved alias. - alias *node - value string - implicit bool - children []*node - anchors map[string]*node -} - -// ---------------------------------------------------------------------------- -// Parser, produces a node tree out of a libyaml event stream. - -type parser struct { - parser yaml_parser_t - event yaml_event_t - doc *node - doneInit bool -} - -func newParser(b []byte) *parser { - p := parser{} - if !yaml_parser_initialize(&p.parser) { - panic("failed to initialize YAML emitter") - } - if len(b) == 0 { - b = []byte{'\n'} - } - yaml_parser_set_input_string(&p.parser, b) - return &p -} - -func newParserFromReader(r io.Reader) *parser { - p := parser{} - if !yaml_parser_initialize(&p.parser) { - panic("failed to initialize YAML emitter") - } - yaml_parser_set_input_reader(&p.parser, r) - return &p -} - -func (p *parser) init() { - if p.doneInit { - return - } - p.expect(yaml_STREAM_START_EVENT) - p.doneInit = true -} - -func (p *parser) destroy() { - if p.event.typ != yaml_NO_EVENT { - yaml_event_delete(&p.event) - } - yaml_parser_delete(&p.parser) -} - -// expect consumes an event from the event stream and -// checks that it's of the expected type. -func (p *parser) expect(e yaml_event_type_t) { - if p.event.typ == yaml_NO_EVENT { - if !yaml_parser_parse(&p.parser, &p.event) { - p.fail() - } - } - if p.event.typ == yaml_STREAM_END_EVENT { - failf("attempted to go past the end of stream; corrupted value?") - } - if p.event.typ != e { - p.parser.problem = fmt.Sprintf("expected %s event but got %s", e, p.event.typ) - p.fail() - } - yaml_event_delete(&p.event) - p.event.typ = yaml_NO_EVENT -} - -// peek peeks at the next event in the event stream, -// puts the results into p.event and returns the event type. -func (p *parser) peek() yaml_event_type_t { - if p.event.typ != yaml_NO_EVENT { - return p.event.typ - } - if !yaml_parser_parse(&p.parser, &p.event) { - p.fail() - } - return p.event.typ -} - -func (p *parser) fail() { - var where string - var line int - if p.parser.problem_mark.line != 0 { - line = p.parser.problem_mark.line - // Scanner errors don't iterate line before returning error - if p.parser.error == yaml_SCANNER_ERROR { - line++ - } - } else if p.parser.context_mark.line != 0 { - line = p.parser.context_mark.line - } - if line != 0 { - where = "line " + strconv.Itoa(line) + ": " - } - var msg string - if len(p.parser.problem) > 0 { - msg = p.parser.problem - } else { - msg = "unknown problem parsing YAML content" - } - failf("%s%s", where, msg) -} - -func (p *parser) anchor(n *node, anchor []byte) { - if anchor != nil { - p.doc.anchors[string(anchor)] = n - } -} - -func (p *parser) parse() *node { - p.init() - switch p.peek() { - case yaml_SCALAR_EVENT: - return p.scalar() - case yaml_ALIAS_EVENT: - return p.alias() - case yaml_MAPPING_START_EVENT: - return p.mapping() - case yaml_SEQUENCE_START_EVENT: - return p.sequence() - case yaml_DOCUMENT_START_EVENT: - return p.document() - case yaml_STREAM_END_EVENT: - // Happens when attempting to decode an empty buffer. - return nil - default: - panic("attempted to parse unknown event: " + p.event.typ.String()) - } -} - -func (p *parser) node(kind int) *node { - return &node{ - kind: kind, - line: p.event.start_mark.line, - column: p.event.start_mark.column, - } -} - -func (p *parser) document() *node { - n := p.node(documentNode) - n.anchors = make(map[string]*node) - p.doc = n - p.expect(yaml_DOCUMENT_START_EVENT) - n.children = append(n.children, p.parse()) - p.expect(yaml_DOCUMENT_END_EVENT) - return n -} - -func (p *parser) alias() *node { - n := p.node(aliasNode) - n.value = string(p.event.anchor) - n.alias = p.doc.anchors[n.value] - if n.alias == nil { - failf("unknown anchor '%s' referenced", n.value) - } - p.expect(yaml_ALIAS_EVENT) - return n -} - -func (p *parser) scalar() *node { - n := p.node(scalarNode) - n.value = string(p.event.value) - n.tag = string(p.event.tag) - n.implicit = p.event.implicit - p.anchor(n, p.event.anchor) - p.expect(yaml_SCALAR_EVENT) - return n -} - -func (p *parser) sequence() *node { - n := p.node(sequenceNode) - p.anchor(n, p.event.anchor) - p.expect(yaml_SEQUENCE_START_EVENT) - for p.peek() != yaml_SEQUENCE_END_EVENT { - n.children = append(n.children, p.parse()) - } - p.expect(yaml_SEQUENCE_END_EVENT) - return n -} - -func (p *parser) mapping() *node { - n := p.node(mappingNode) - p.anchor(n, p.event.anchor) - p.expect(yaml_MAPPING_START_EVENT) - for p.peek() != yaml_MAPPING_END_EVENT { - n.children = append(n.children, p.parse(), p.parse()) - } - p.expect(yaml_MAPPING_END_EVENT) - return n -} - -// ---------------------------------------------------------------------------- -// Decoder, unmarshals a node into a provided value. - -type decoder struct { - doc *node - aliases map[*node]bool - mapType reflect.Type - terrors []string - strict bool - - decodeCount int - aliasCount int - aliasDepth int -} - -var ( - mapItemType = reflect.TypeOf(MapItem{}) - durationType = reflect.TypeOf(time.Duration(0)) - defaultMapType = reflect.TypeOf(map[interface{}]interface{}{}) - ifaceType = defaultMapType.Elem() - timeType = reflect.TypeOf(time.Time{}) - ptrTimeType = reflect.TypeOf(&time.Time{}) -) - -func newDecoder(strict bool) *decoder { - d := &decoder{mapType: defaultMapType, strict: strict} - d.aliases = make(map[*node]bool) - return d -} - -func (d *decoder) terror(n *node, tag string, out reflect.Value) { - if n.tag != "" { - tag = n.tag - } - value := n.value - if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG { - if len(value) > 10 { - value = " `" + value[:7] + "...`" - } else { - value = " `" + value + "`" - } - } - d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.line+1, shortTag(tag), value, out.Type())) -} - -func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) { - terrlen := len(d.terrors) - err := u.UnmarshalYAML(func(v interface{}) (err error) { - defer handleErr(&err) - d.unmarshal(n, reflect.ValueOf(v)) - if len(d.terrors) > terrlen { - issues := d.terrors[terrlen:] - d.terrors = d.terrors[:terrlen] - return &TypeError{issues} - } - return nil - }) - if e, ok := err.(*TypeError); ok { - d.terrors = append(d.terrors, e.Errors...) - return false - } - if err != nil { - fail(err) - } - return true -} - -// d.prepare initializes and dereferences pointers and calls UnmarshalYAML -// if a value is found to implement it. -// It returns the initialized and dereferenced out value, whether -// unmarshalling was already done by UnmarshalYAML, and if so whether -// its types unmarshalled appropriately. -// -// If n holds a null value, prepare returns before doing anything. -func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) { - if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) { - return out, false, false - } - again := true - for again { - again = false - if out.Kind() == reflect.Ptr { - if out.IsNil() { - out.Set(reflect.New(out.Type().Elem())) - } - out = out.Elem() - again = true - } - if out.CanAddr() { - if u, ok := out.Addr().Interface().(Unmarshaler); ok { - good = d.callUnmarshaler(n, u) - return out, true, good - } - } - } - return out, false, false -} - -const ( - // 400,000 decode operations is ~500kb of dense object declarations, or - // ~5kb of dense object declarations with 10000% alias expansion - alias_ratio_range_low = 400000 - - // 4,000,000 decode operations is ~5MB of dense object declarations, or - // ~4.5MB of dense object declarations with 10% alias expansion - alias_ratio_range_high = 4000000 - - // alias_ratio_range is the range over which we scale allowed alias ratios - alias_ratio_range = float64(alias_ratio_range_high - alias_ratio_range_low) -) - -func allowedAliasRatio(decodeCount int) float64 { - switch { - case decodeCount <= alias_ratio_range_low: - // allow 99% to come from alias expansion for small-to-medium documents - return 0.99 - case decodeCount >= alias_ratio_range_high: - // allow 10% to come from alias expansion for very large documents - return 0.10 - default: - // scale smoothly from 99% down to 10% over the range. - // this maps to 396,000 - 400,000 allowed alias-driven decodes over the range. - // 400,000 decode operations is ~100MB of allocations in worst-case scenarios (single-item maps). - return 0.99 - 0.89*(float64(decodeCount-alias_ratio_range_low)/alias_ratio_range) - } -} - -func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) { - d.decodeCount++ - if d.aliasDepth > 0 { - d.aliasCount++ - } - if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > allowedAliasRatio(d.decodeCount) { - failf("document contains excessive aliasing") - } - switch n.kind { - case documentNode: - return d.document(n, out) - case aliasNode: - return d.alias(n, out) - } - out, unmarshaled, good := d.prepare(n, out) - if unmarshaled { - return good - } - switch n.kind { - case scalarNode: - good = d.scalar(n, out) - case mappingNode: - good = d.mapping(n, out) - case sequenceNode: - good = d.sequence(n, out) - default: - panic("internal error: unknown node kind: " + strconv.Itoa(n.kind)) - } - return good -} - -func (d *decoder) document(n *node, out reflect.Value) (good bool) { - if len(n.children) == 1 { - d.doc = n - d.unmarshal(n.children[0], out) - return true - } - return false -} - -func (d *decoder) alias(n *node, out reflect.Value) (good bool) { - if d.aliases[n] { - // TODO this could actually be allowed in some circumstances. - failf("anchor '%s' value contains itself", n.value) - } - d.aliases[n] = true - d.aliasDepth++ - good = d.unmarshal(n.alias, out) - d.aliasDepth-- - delete(d.aliases, n) - return good -} - -var zeroValue reflect.Value - -func resetMap(out reflect.Value) { - for _, k := range out.MapKeys() { - out.SetMapIndex(k, zeroValue) - } -} - -func (d *decoder) scalar(n *node, out reflect.Value) bool { - var tag string - var resolved interface{} - if n.tag == "" && !n.implicit { - tag = yaml_STR_TAG - resolved = n.value - } else { - tag, resolved = resolve(n.tag, n.value) - if tag == yaml_BINARY_TAG { - data, err := base64.StdEncoding.DecodeString(resolved.(string)) - if err != nil { - failf("!!binary value contains invalid base64 data") - } - resolved = string(data) - } - } - if resolved == nil { - if out.Kind() == reflect.Map && !out.CanAddr() { - resetMap(out) - } else { - out.Set(reflect.Zero(out.Type())) - } - return true - } - if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { - // We've resolved to exactly the type we want, so use that. - out.Set(resolvedv) - return true - } - // Perhaps we can use the value as a TextUnmarshaler to - // set its value. - if out.CanAddr() { - u, ok := out.Addr().Interface().(encoding.TextUnmarshaler) - if ok { - var text []byte - if tag == yaml_BINARY_TAG { - text = []byte(resolved.(string)) - } else { - // We let any value be unmarshaled into TextUnmarshaler. - // That might be more lax than we'd like, but the - // TextUnmarshaler itself should bowl out any dubious values. - text = []byte(n.value) - } - err := u.UnmarshalText(text) - if err != nil { - fail(err) - } - return true - } - } - switch out.Kind() { - case reflect.String: - if tag == yaml_BINARY_TAG { - out.SetString(resolved.(string)) - return true - } - if resolved != nil { - out.SetString(n.value) - return true - } - case reflect.Interface: - if resolved == nil { - out.Set(reflect.Zero(out.Type())) - } else if tag == yaml_TIMESTAMP_TAG { - // It looks like a timestamp but for backward compatibility - // reasons we set it as a string, so that code that unmarshals - // timestamp-like values into interface{} will continue to - // see a string and not a time.Time. - // TODO(v3) Drop this. - out.Set(reflect.ValueOf(n.value)) - } else { - out.Set(reflect.ValueOf(resolved)) - } - return true - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - switch resolved := resolved.(type) { - case int: - if !out.OverflowInt(int64(resolved)) { - out.SetInt(int64(resolved)) - return true - } - case int64: - if !out.OverflowInt(resolved) { - out.SetInt(resolved) - return true - } - case uint64: - if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { - out.SetInt(int64(resolved)) - return true - } - case float64: - if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { - out.SetInt(int64(resolved)) - return true - } - case string: - if out.Type() == durationType { - d, err := time.ParseDuration(resolved) - if err == nil { - out.SetInt(int64(d)) - return true - } - } - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - switch resolved := resolved.(type) { - case int: - if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { - out.SetUint(uint64(resolved)) - return true - } - case int64: - if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { - out.SetUint(uint64(resolved)) - return true - } - case uint64: - if !out.OverflowUint(uint64(resolved)) { - out.SetUint(uint64(resolved)) - return true - } - case float64: - if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) { - out.SetUint(uint64(resolved)) - return true - } - } - case reflect.Bool: - switch resolved := resolved.(type) { - case bool: - out.SetBool(resolved) - return true - } - case reflect.Float32, reflect.Float64: - switch resolved := resolved.(type) { - case int: - out.SetFloat(float64(resolved)) - return true - case int64: - out.SetFloat(float64(resolved)) - return true - case uint64: - out.SetFloat(float64(resolved)) - return true - case float64: - out.SetFloat(resolved) - return true - } - case reflect.Struct: - if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { - out.Set(resolvedv) - return true - } - case reflect.Ptr: - if out.Type().Elem() == reflect.TypeOf(resolved) { - // TODO DOes this make sense? When is out a Ptr except when decoding a nil value? - elem := reflect.New(out.Type().Elem()) - elem.Elem().Set(reflect.ValueOf(resolved)) - out.Set(elem) - return true - } - } - d.terror(n, tag, out) - return false -} - -func settableValueOf(i interface{}) reflect.Value { - v := reflect.ValueOf(i) - sv := reflect.New(v.Type()).Elem() - sv.Set(v) - return sv -} - -func (d *decoder) sequence(n *node, out reflect.Value) (good bool) { - l := len(n.children) - - var iface reflect.Value - switch out.Kind() { - case reflect.Slice: - out.Set(reflect.MakeSlice(out.Type(), l, l)) - case reflect.Array: - if l != out.Len() { - failf("invalid array: want %d elements but got %d", out.Len(), l) - } - case reflect.Interface: - // No type hints. Will have to use a generic sequence. - iface = out - out = settableValueOf(make([]interface{}, l)) - default: - d.terror(n, yaml_SEQ_TAG, out) - return false - } - et := out.Type().Elem() - - j := 0 - for i := 0; i < l; i++ { - e := reflect.New(et).Elem() - if ok := d.unmarshal(n.children[i], e); ok { - out.Index(j).Set(e) - j++ - } - } - if out.Kind() != reflect.Array { - out.Set(out.Slice(0, j)) - } - if iface.IsValid() { - iface.Set(out) - } - return true -} - -func (d *decoder) mapping(n *node, out reflect.Value) (good bool) { - switch out.Kind() { - case reflect.Struct: - return d.mappingStruct(n, out) - case reflect.Slice: - return d.mappingSlice(n, out) - case reflect.Map: - // okay - case reflect.Interface: - if d.mapType.Kind() == reflect.Map { - iface := out - out = reflect.MakeMap(d.mapType) - iface.Set(out) - } else { - slicev := reflect.New(d.mapType).Elem() - if !d.mappingSlice(n, slicev) { - return false - } - out.Set(slicev) - return true - } - default: - d.terror(n, yaml_MAP_TAG, out) - return false - } - outt := out.Type() - kt := outt.Key() - et := outt.Elem() - - mapType := d.mapType - if outt.Key() == ifaceType && outt.Elem() == ifaceType { - d.mapType = outt - } - - if out.IsNil() { - out.Set(reflect.MakeMap(outt)) - } - l := len(n.children) - for i := 0; i < l; i += 2 { - if isMerge(n.children[i]) { - d.merge(n.children[i+1], out) - continue - } - k := reflect.New(kt).Elem() - if d.unmarshal(n.children[i], k) { - kkind := k.Kind() - if kkind == reflect.Interface { - kkind = k.Elem().Kind() - } - if kkind == reflect.Map || kkind == reflect.Slice { - failf("invalid map key: %#v", k.Interface()) - } - e := reflect.New(et).Elem() - if d.unmarshal(n.children[i+1], e) { - d.setMapIndex(n.children[i+1], out, k, e) - } - } - } - d.mapType = mapType - return true -} - -func (d *decoder) setMapIndex(n *node, out, k, v reflect.Value) { - if d.strict && out.MapIndex(k) != zeroValue { - d.terrors = append(d.terrors, fmt.Sprintf("line %d: key %#v already set in map", n.line+1, k.Interface())) - return - } - out.SetMapIndex(k, v) -} - -func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) { - outt := out.Type() - if outt.Elem() != mapItemType { - d.terror(n, yaml_MAP_TAG, out) - return false - } - - mapType := d.mapType - d.mapType = outt - - var slice []MapItem - var l = len(n.children) - for i := 0; i < l; i += 2 { - if isMerge(n.children[i]) { - d.merge(n.children[i+1], out) - continue - } - item := MapItem{} - k := reflect.ValueOf(&item.Key).Elem() - if d.unmarshal(n.children[i], k) { - v := reflect.ValueOf(&item.Value).Elem() - if d.unmarshal(n.children[i+1], v) { - slice = append(slice, item) - } - } - } - out.Set(reflect.ValueOf(slice)) - d.mapType = mapType - return true -} - -func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) { - sinfo, err := getStructInfo(out.Type()) - if err != nil { - panic(err) - } - name := settableValueOf("") - l := len(n.children) - - var inlineMap reflect.Value - var elemType reflect.Type - if sinfo.InlineMap != -1 { - inlineMap = out.Field(sinfo.InlineMap) - inlineMap.Set(reflect.New(inlineMap.Type()).Elem()) - elemType = inlineMap.Type().Elem() - } - - var doneFields []bool - if d.strict { - doneFields = make([]bool, len(sinfo.FieldsList)) - } - for i := 0; i < l; i += 2 { - ni := n.children[i] - if isMerge(ni) { - d.merge(n.children[i+1], out) - continue - } - if !d.unmarshal(ni, name) { - continue - } - if info, ok := sinfo.FieldsMap[name.String()]; ok { - if d.strict { - if doneFields[info.Id] { - d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.line+1, name.String(), out.Type())) - continue - } - doneFields[info.Id] = true - } - var field reflect.Value - if info.Inline == nil { - field = out.Field(info.Num) - } else { - field = out.FieldByIndex(info.Inline) - } - d.unmarshal(n.children[i+1], field) - } else if sinfo.InlineMap != -1 { - if inlineMap.IsNil() { - inlineMap.Set(reflect.MakeMap(inlineMap.Type())) - } - value := reflect.New(elemType).Elem() - d.unmarshal(n.children[i+1], value) - d.setMapIndex(n.children[i+1], inlineMap, name, value) - } else if d.strict { - d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.line+1, name.String(), out.Type())) - } - } - return true -} - -func failWantMap() { - failf("map merge requires map or sequence of maps as the value") -} - -func (d *decoder) merge(n *node, out reflect.Value) { - switch n.kind { - case mappingNode: - d.unmarshal(n, out) - case aliasNode: - if n.alias != nil && n.alias.kind != mappingNode { - failWantMap() - } - d.unmarshal(n, out) - case sequenceNode: - // Step backwards as earlier nodes take precedence. - for i := len(n.children) - 1; i >= 0; i-- { - ni := n.children[i] - if ni.kind == aliasNode { - if ni.alias != nil && ni.alias.kind != mappingNode { - failWantMap() - } - } else if ni.kind != mappingNode { - failWantMap() - } - d.unmarshal(ni, out) - } - default: - failWantMap() - } -} - -func isMerge(n *node) bool { - return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG) -} diff --git a/vendor/go.yaml.in/yaml/v2/emitterc.go b/vendor/go.yaml.in/yaml/v2/emitterc.go deleted file mode 100644 index a1c2cc5262..0000000000 --- a/vendor/go.yaml.in/yaml/v2/emitterc.go +++ /dev/null @@ -1,1685 +0,0 @@ -package yaml - -import ( - "bytes" - "fmt" -) - -// Flush the buffer if needed. -func flush(emitter *yaml_emitter_t) bool { - if emitter.buffer_pos+5 >= len(emitter.buffer) { - return yaml_emitter_flush(emitter) - } - return true -} - -// Put a character to the output buffer. -func put(emitter *yaml_emitter_t, value byte) bool { - if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { - return false - } - emitter.buffer[emitter.buffer_pos] = value - emitter.buffer_pos++ - emitter.column++ - return true -} - -// Put a line break to the output buffer. -func put_break(emitter *yaml_emitter_t) bool { - if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { - return false - } - switch emitter.line_break { - case yaml_CR_BREAK: - emitter.buffer[emitter.buffer_pos] = '\r' - emitter.buffer_pos += 1 - case yaml_LN_BREAK: - emitter.buffer[emitter.buffer_pos] = '\n' - emitter.buffer_pos += 1 - case yaml_CRLN_BREAK: - emitter.buffer[emitter.buffer_pos+0] = '\r' - emitter.buffer[emitter.buffer_pos+1] = '\n' - emitter.buffer_pos += 2 - default: - panic("unknown line break setting") - } - emitter.column = 0 - emitter.line++ - return true -} - -// Copy a character from a string into buffer. -func write(emitter *yaml_emitter_t, s []byte, i *int) bool { - if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { - return false - } - p := emitter.buffer_pos - w := width(s[*i]) - switch w { - case 4: - emitter.buffer[p+3] = s[*i+3] - fallthrough - case 3: - emitter.buffer[p+2] = s[*i+2] - fallthrough - case 2: - emitter.buffer[p+1] = s[*i+1] - fallthrough - case 1: - emitter.buffer[p+0] = s[*i+0] - default: - panic("unknown character width") - } - emitter.column++ - emitter.buffer_pos += w - *i += w - return true -} - -// Write a whole string into buffer. -func write_all(emitter *yaml_emitter_t, s []byte) bool { - for i := 0; i < len(s); { - if !write(emitter, s, &i) { - return false - } - } - return true -} - -// Copy a line break character from a string into buffer. -func write_break(emitter *yaml_emitter_t, s []byte, i *int) bool { - if s[*i] == '\n' { - if !put_break(emitter) { - return false - } - *i++ - } else { - if !write(emitter, s, i) { - return false - } - emitter.column = 0 - emitter.line++ - } - return true -} - -// Set an emitter error and return false. -func yaml_emitter_set_emitter_error(emitter *yaml_emitter_t, problem string) bool { - emitter.error = yaml_EMITTER_ERROR - emitter.problem = problem - return false -} - -// Emit an event. -func yaml_emitter_emit(emitter *yaml_emitter_t, event *yaml_event_t) bool { - emitter.events = append(emitter.events, *event) - for !yaml_emitter_need_more_events(emitter) { - event := &emitter.events[emitter.events_head] - if !yaml_emitter_analyze_event(emitter, event) { - return false - } - if !yaml_emitter_state_machine(emitter, event) { - return false - } - yaml_event_delete(event) - emitter.events_head++ - } - return true -} - -// Check if we need to accumulate more events before emitting. -// -// We accumulate extra -// - 1 event for DOCUMENT-START -// - 2 events for SEQUENCE-START -// - 3 events for MAPPING-START -// -func yaml_emitter_need_more_events(emitter *yaml_emitter_t) bool { - if emitter.events_head == len(emitter.events) { - return true - } - var accumulate int - switch emitter.events[emitter.events_head].typ { - case yaml_DOCUMENT_START_EVENT: - accumulate = 1 - break - case yaml_SEQUENCE_START_EVENT: - accumulate = 2 - break - case yaml_MAPPING_START_EVENT: - accumulate = 3 - break - default: - return false - } - if len(emitter.events)-emitter.events_head > accumulate { - return false - } - var level int - for i := emitter.events_head; i < len(emitter.events); i++ { - switch emitter.events[i].typ { - case yaml_STREAM_START_EVENT, yaml_DOCUMENT_START_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT: - level++ - case yaml_STREAM_END_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_END_EVENT, yaml_MAPPING_END_EVENT: - level-- - } - if level == 0 { - return false - } - } - return true -} - -// Append a directive to the directives stack. -func yaml_emitter_append_tag_directive(emitter *yaml_emitter_t, value *yaml_tag_directive_t, allow_duplicates bool) bool { - for i := 0; i < len(emitter.tag_directives); i++ { - if bytes.Equal(value.handle, emitter.tag_directives[i].handle) { - if allow_duplicates { - return true - } - return yaml_emitter_set_emitter_error(emitter, "duplicate %TAG directive") - } - } - - // [Go] Do we actually need to copy this given garbage collection - // and the lack of deallocating destructors? - tag_copy := yaml_tag_directive_t{ - handle: make([]byte, len(value.handle)), - prefix: make([]byte, len(value.prefix)), - } - copy(tag_copy.handle, value.handle) - copy(tag_copy.prefix, value.prefix) - emitter.tag_directives = append(emitter.tag_directives, tag_copy) - return true -} - -// Increase the indentation level. -func yaml_emitter_increase_indent(emitter *yaml_emitter_t, flow, indentless bool) bool { - emitter.indents = append(emitter.indents, emitter.indent) - if emitter.indent < 0 { - if flow { - emitter.indent = emitter.best_indent - } else { - emitter.indent = 0 - } - } else if !indentless { - emitter.indent += emitter.best_indent - } - return true -} - -// State dispatcher. -func yaml_emitter_state_machine(emitter *yaml_emitter_t, event *yaml_event_t) bool { - switch emitter.state { - default: - case yaml_EMIT_STREAM_START_STATE: - return yaml_emitter_emit_stream_start(emitter, event) - - case yaml_EMIT_FIRST_DOCUMENT_START_STATE: - return yaml_emitter_emit_document_start(emitter, event, true) - - case yaml_EMIT_DOCUMENT_START_STATE: - return yaml_emitter_emit_document_start(emitter, event, false) - - case yaml_EMIT_DOCUMENT_CONTENT_STATE: - return yaml_emitter_emit_document_content(emitter, event) - - case yaml_EMIT_DOCUMENT_END_STATE: - return yaml_emitter_emit_document_end(emitter, event) - - case yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE: - return yaml_emitter_emit_flow_sequence_item(emitter, event, true) - - case yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE: - return yaml_emitter_emit_flow_sequence_item(emitter, event, false) - - case yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE: - return yaml_emitter_emit_flow_mapping_key(emitter, event, true) - - case yaml_EMIT_FLOW_MAPPING_KEY_STATE: - return yaml_emitter_emit_flow_mapping_key(emitter, event, false) - - case yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE: - return yaml_emitter_emit_flow_mapping_value(emitter, event, true) - - case yaml_EMIT_FLOW_MAPPING_VALUE_STATE: - return yaml_emitter_emit_flow_mapping_value(emitter, event, false) - - case yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE: - return yaml_emitter_emit_block_sequence_item(emitter, event, true) - - case yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE: - return yaml_emitter_emit_block_sequence_item(emitter, event, false) - - case yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE: - return yaml_emitter_emit_block_mapping_key(emitter, event, true) - - case yaml_EMIT_BLOCK_MAPPING_KEY_STATE: - return yaml_emitter_emit_block_mapping_key(emitter, event, false) - - case yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE: - return yaml_emitter_emit_block_mapping_value(emitter, event, true) - - case yaml_EMIT_BLOCK_MAPPING_VALUE_STATE: - return yaml_emitter_emit_block_mapping_value(emitter, event, false) - - case yaml_EMIT_END_STATE: - return yaml_emitter_set_emitter_error(emitter, "expected nothing after STREAM-END") - } - panic("invalid emitter state") -} - -// Expect STREAM-START. -func yaml_emitter_emit_stream_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { - if event.typ != yaml_STREAM_START_EVENT { - return yaml_emitter_set_emitter_error(emitter, "expected STREAM-START") - } - if emitter.encoding == yaml_ANY_ENCODING { - emitter.encoding = event.encoding - if emitter.encoding == yaml_ANY_ENCODING { - emitter.encoding = yaml_UTF8_ENCODING - } - } - if emitter.best_indent < 2 || emitter.best_indent > 9 { - emitter.best_indent = 2 - } - if emitter.best_width >= 0 && emitter.best_width <= emitter.best_indent*2 { - emitter.best_width = 80 - } - if emitter.best_width < 0 { - emitter.best_width = 1<<31 - 1 - } - if emitter.line_break == yaml_ANY_BREAK { - emitter.line_break = yaml_LN_BREAK - } - - emitter.indent = -1 - emitter.line = 0 - emitter.column = 0 - emitter.whitespace = true - emitter.indention = true - - if emitter.encoding != yaml_UTF8_ENCODING { - if !yaml_emitter_write_bom(emitter) { - return false - } - } - emitter.state = yaml_EMIT_FIRST_DOCUMENT_START_STATE - return true -} - -// Expect DOCUMENT-START or STREAM-END. -func yaml_emitter_emit_document_start(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { - - if event.typ == yaml_DOCUMENT_START_EVENT { - - if event.version_directive != nil { - if !yaml_emitter_analyze_version_directive(emitter, event.version_directive) { - return false - } - } - - for i := 0; i < len(event.tag_directives); i++ { - tag_directive := &event.tag_directives[i] - if !yaml_emitter_analyze_tag_directive(emitter, tag_directive) { - return false - } - if !yaml_emitter_append_tag_directive(emitter, tag_directive, false) { - return false - } - } - - for i := 0; i < len(default_tag_directives); i++ { - tag_directive := &default_tag_directives[i] - if !yaml_emitter_append_tag_directive(emitter, tag_directive, true) { - return false - } - } - - implicit := event.implicit - if !first || emitter.canonical { - implicit = false - } - - if emitter.open_ended && (event.version_directive != nil || len(event.tag_directives) > 0) { - if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { - return false - } - if !yaml_emitter_write_indent(emitter) { - return false - } - } - - if event.version_directive != nil { - implicit = false - if !yaml_emitter_write_indicator(emitter, []byte("%YAML"), true, false, false) { - return false - } - if !yaml_emitter_write_indicator(emitter, []byte("1.1"), true, false, false) { - return false - } - if !yaml_emitter_write_indent(emitter) { - return false - } - } - - if len(event.tag_directives) > 0 { - implicit = false - for i := 0; i < len(event.tag_directives); i++ { - tag_directive := &event.tag_directives[i] - if !yaml_emitter_write_indicator(emitter, []byte("%TAG"), true, false, false) { - return false - } - if !yaml_emitter_write_tag_handle(emitter, tag_directive.handle) { - return false - } - if !yaml_emitter_write_tag_content(emitter, tag_directive.prefix, true) { - return false - } - if !yaml_emitter_write_indent(emitter) { - return false - } - } - } - - if yaml_emitter_check_empty_document(emitter) { - implicit = false - } - if !implicit { - if !yaml_emitter_write_indent(emitter) { - return false - } - if !yaml_emitter_write_indicator(emitter, []byte("---"), true, false, false) { - return false - } - if emitter.canonical { - if !yaml_emitter_write_indent(emitter) { - return false - } - } - } - - emitter.state = yaml_EMIT_DOCUMENT_CONTENT_STATE - return true - } - - if event.typ == yaml_STREAM_END_EVENT { - if emitter.open_ended { - if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { - return false - } - if !yaml_emitter_write_indent(emitter) { - return false - } - } - if !yaml_emitter_flush(emitter) { - return false - } - emitter.state = yaml_EMIT_END_STATE - return true - } - - return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-START or STREAM-END") -} - -// Expect the root node. -func yaml_emitter_emit_document_content(emitter *yaml_emitter_t, event *yaml_event_t) bool { - emitter.states = append(emitter.states, yaml_EMIT_DOCUMENT_END_STATE) - return yaml_emitter_emit_node(emitter, event, true, false, false, false) -} - -// Expect DOCUMENT-END. -func yaml_emitter_emit_document_end(emitter *yaml_emitter_t, event *yaml_event_t) bool { - if event.typ != yaml_DOCUMENT_END_EVENT { - return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-END") - } - if !yaml_emitter_write_indent(emitter) { - return false - } - if !event.implicit { - // [Go] Allocate the slice elsewhere. - if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { - return false - } - if !yaml_emitter_write_indent(emitter) { - return false - } - } - if !yaml_emitter_flush(emitter) { - return false - } - emitter.state = yaml_EMIT_DOCUMENT_START_STATE - emitter.tag_directives = emitter.tag_directives[:0] - return true -} - -// Expect a flow item node. -func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { - if first { - if !yaml_emitter_write_indicator(emitter, []byte{'['}, true, true, false) { - return false - } - if !yaml_emitter_increase_indent(emitter, true, false) { - return false - } - emitter.flow_level++ - } - - if event.typ == yaml_SEQUENCE_END_EVENT { - emitter.flow_level-- - emitter.indent = emitter.indents[len(emitter.indents)-1] - emitter.indents = emitter.indents[:len(emitter.indents)-1] - if emitter.canonical && !first { - if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { - return false - } - if !yaml_emitter_write_indent(emitter) { - return false - } - } - if !yaml_emitter_write_indicator(emitter, []byte{']'}, false, false, false) { - return false - } - emitter.state = emitter.states[len(emitter.states)-1] - emitter.states = emitter.states[:len(emitter.states)-1] - - return true - } - - if !first { - if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { - return false - } - } - - if emitter.canonical || emitter.column > emitter.best_width { - if !yaml_emitter_write_indent(emitter) { - return false - } - } - emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE) - return yaml_emitter_emit_node(emitter, event, false, true, false, false) -} - -// Expect a flow key node. -func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { - if first { - if !yaml_emitter_write_indicator(emitter, []byte{'{'}, true, true, false) { - return false - } - if !yaml_emitter_increase_indent(emitter, true, false) { - return false - } - emitter.flow_level++ - } - - if event.typ == yaml_MAPPING_END_EVENT { - emitter.flow_level-- - emitter.indent = emitter.indents[len(emitter.indents)-1] - emitter.indents = emitter.indents[:len(emitter.indents)-1] - if emitter.canonical && !first { - if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { - return false - } - if !yaml_emitter_write_indent(emitter) { - return false - } - } - if !yaml_emitter_write_indicator(emitter, []byte{'}'}, false, false, false) { - return false - } - emitter.state = emitter.states[len(emitter.states)-1] - emitter.states = emitter.states[:len(emitter.states)-1] - return true - } - - if !first { - if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { - return false - } - } - if emitter.canonical || emitter.column > emitter.best_width { - if !yaml_emitter_write_indent(emitter) { - return false - } - } - - if !emitter.canonical && yaml_emitter_check_simple_key(emitter) { - emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE) - return yaml_emitter_emit_node(emitter, event, false, false, true, true) - } - if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, false) { - return false - } - emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_VALUE_STATE) - return yaml_emitter_emit_node(emitter, event, false, false, true, false) -} - -// Expect a flow value node. -func yaml_emitter_emit_flow_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { - if simple { - if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { - return false - } - } else { - if emitter.canonical || emitter.column > emitter.best_width { - if !yaml_emitter_write_indent(emitter) { - return false - } - } - if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, false) { - return false - } - } - emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_KEY_STATE) - return yaml_emitter_emit_node(emitter, event, false, false, true, false) -} - -// Expect a block item node. -func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { - if first { - if !yaml_emitter_increase_indent(emitter, false, emitter.mapping_context && !emitter.indention) { - return false - } - } - if event.typ == yaml_SEQUENCE_END_EVENT { - emitter.indent = emitter.indents[len(emitter.indents)-1] - emitter.indents = emitter.indents[:len(emitter.indents)-1] - emitter.state = emitter.states[len(emitter.states)-1] - emitter.states = emitter.states[:len(emitter.states)-1] - return true - } - if !yaml_emitter_write_indent(emitter) { - return false - } - if !yaml_emitter_write_indicator(emitter, []byte{'-'}, true, false, true) { - return false - } - emitter.states = append(emitter.states, yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE) - return yaml_emitter_emit_node(emitter, event, false, true, false, false) -} - -// Expect a block key node. -func yaml_emitter_emit_block_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { - if first { - if !yaml_emitter_increase_indent(emitter, false, false) { - return false - } - } - if event.typ == yaml_MAPPING_END_EVENT { - emitter.indent = emitter.indents[len(emitter.indents)-1] - emitter.indents = emitter.indents[:len(emitter.indents)-1] - emitter.state = emitter.states[len(emitter.states)-1] - emitter.states = emitter.states[:len(emitter.states)-1] - return true - } - if !yaml_emitter_write_indent(emitter) { - return false - } - if yaml_emitter_check_simple_key(emitter) { - emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE) - return yaml_emitter_emit_node(emitter, event, false, false, true, true) - } - if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, true) { - return false - } - emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_VALUE_STATE) - return yaml_emitter_emit_node(emitter, event, false, false, true, false) -} - -// Expect a block value node. -func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { - if simple { - if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { - return false - } - } else { - if !yaml_emitter_write_indent(emitter) { - return false - } - if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, true) { - return false - } - } - emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_KEY_STATE) - return yaml_emitter_emit_node(emitter, event, false, false, true, false) -} - -// Expect a node. -func yaml_emitter_emit_node(emitter *yaml_emitter_t, event *yaml_event_t, - root bool, sequence bool, mapping bool, simple_key bool) bool { - - emitter.root_context = root - emitter.sequence_context = sequence - emitter.mapping_context = mapping - emitter.simple_key_context = simple_key - - switch event.typ { - case yaml_ALIAS_EVENT: - return yaml_emitter_emit_alias(emitter, event) - case yaml_SCALAR_EVENT: - return yaml_emitter_emit_scalar(emitter, event) - case yaml_SEQUENCE_START_EVENT: - return yaml_emitter_emit_sequence_start(emitter, event) - case yaml_MAPPING_START_EVENT: - return yaml_emitter_emit_mapping_start(emitter, event) - default: - return yaml_emitter_set_emitter_error(emitter, - fmt.Sprintf("expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS, but got %v", event.typ)) - } -} - -// Expect ALIAS. -func yaml_emitter_emit_alias(emitter *yaml_emitter_t, event *yaml_event_t) bool { - if !yaml_emitter_process_anchor(emitter) { - return false - } - emitter.state = emitter.states[len(emitter.states)-1] - emitter.states = emitter.states[:len(emitter.states)-1] - return true -} - -// Expect SCALAR. -func yaml_emitter_emit_scalar(emitter *yaml_emitter_t, event *yaml_event_t) bool { - if !yaml_emitter_select_scalar_style(emitter, event) { - return false - } - if !yaml_emitter_process_anchor(emitter) { - return false - } - if !yaml_emitter_process_tag(emitter) { - return false - } - if !yaml_emitter_increase_indent(emitter, true, false) { - return false - } - if !yaml_emitter_process_scalar(emitter) { - return false - } - emitter.indent = emitter.indents[len(emitter.indents)-1] - emitter.indents = emitter.indents[:len(emitter.indents)-1] - emitter.state = emitter.states[len(emitter.states)-1] - emitter.states = emitter.states[:len(emitter.states)-1] - return true -} - -// Expect SEQUENCE-START. -func yaml_emitter_emit_sequence_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { - if !yaml_emitter_process_anchor(emitter) { - return false - } - if !yaml_emitter_process_tag(emitter) { - return false - } - if emitter.flow_level > 0 || emitter.canonical || event.sequence_style() == yaml_FLOW_SEQUENCE_STYLE || - yaml_emitter_check_empty_sequence(emitter) { - emitter.state = yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE - } else { - emitter.state = yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE - } - return true -} - -// Expect MAPPING-START. -func yaml_emitter_emit_mapping_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { - if !yaml_emitter_process_anchor(emitter) { - return false - } - if !yaml_emitter_process_tag(emitter) { - return false - } - if emitter.flow_level > 0 || emitter.canonical || event.mapping_style() == yaml_FLOW_MAPPING_STYLE || - yaml_emitter_check_empty_mapping(emitter) { - emitter.state = yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE - } else { - emitter.state = yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE - } - return true -} - -// Check if the document content is an empty scalar. -func yaml_emitter_check_empty_document(emitter *yaml_emitter_t) bool { - return false // [Go] Huh? -} - -// Check if the next events represent an empty sequence. -func yaml_emitter_check_empty_sequence(emitter *yaml_emitter_t) bool { - if len(emitter.events)-emitter.events_head < 2 { - return false - } - return emitter.events[emitter.events_head].typ == yaml_SEQUENCE_START_EVENT && - emitter.events[emitter.events_head+1].typ == yaml_SEQUENCE_END_EVENT -} - -// Check if the next events represent an empty mapping. -func yaml_emitter_check_empty_mapping(emitter *yaml_emitter_t) bool { - if len(emitter.events)-emitter.events_head < 2 { - return false - } - return emitter.events[emitter.events_head].typ == yaml_MAPPING_START_EVENT && - emitter.events[emitter.events_head+1].typ == yaml_MAPPING_END_EVENT -} - -// Check if the next node can be expressed as a simple key. -func yaml_emitter_check_simple_key(emitter *yaml_emitter_t) bool { - length := 0 - switch emitter.events[emitter.events_head].typ { - case yaml_ALIAS_EVENT: - length += len(emitter.anchor_data.anchor) - case yaml_SCALAR_EVENT: - if emitter.scalar_data.multiline { - return false - } - length += len(emitter.anchor_data.anchor) + - len(emitter.tag_data.handle) + - len(emitter.tag_data.suffix) + - len(emitter.scalar_data.value) - case yaml_SEQUENCE_START_EVENT: - if !yaml_emitter_check_empty_sequence(emitter) { - return false - } - length += len(emitter.anchor_data.anchor) + - len(emitter.tag_data.handle) + - len(emitter.tag_data.suffix) - case yaml_MAPPING_START_EVENT: - if !yaml_emitter_check_empty_mapping(emitter) { - return false - } - length += len(emitter.anchor_data.anchor) + - len(emitter.tag_data.handle) + - len(emitter.tag_data.suffix) - default: - return false - } - return length <= 128 -} - -// Determine an acceptable scalar style. -func yaml_emitter_select_scalar_style(emitter *yaml_emitter_t, event *yaml_event_t) bool { - - no_tag := len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 - if no_tag && !event.implicit && !event.quoted_implicit { - return yaml_emitter_set_emitter_error(emitter, "neither tag nor implicit flags are specified") - } - - style := event.scalar_style() - if style == yaml_ANY_SCALAR_STYLE { - style = yaml_PLAIN_SCALAR_STYLE - } - if emitter.canonical { - style = yaml_DOUBLE_QUOTED_SCALAR_STYLE - } - if emitter.simple_key_context && emitter.scalar_data.multiline { - style = yaml_DOUBLE_QUOTED_SCALAR_STYLE - } - - if style == yaml_PLAIN_SCALAR_STYLE { - if emitter.flow_level > 0 && !emitter.scalar_data.flow_plain_allowed || - emitter.flow_level == 0 && !emitter.scalar_data.block_plain_allowed { - style = yaml_SINGLE_QUOTED_SCALAR_STYLE - } - if len(emitter.scalar_data.value) == 0 && (emitter.flow_level > 0 || emitter.simple_key_context) { - style = yaml_SINGLE_QUOTED_SCALAR_STYLE - } - if no_tag && !event.implicit { - style = yaml_SINGLE_QUOTED_SCALAR_STYLE - } - } - if style == yaml_SINGLE_QUOTED_SCALAR_STYLE { - if !emitter.scalar_data.single_quoted_allowed { - style = yaml_DOUBLE_QUOTED_SCALAR_STYLE - } - } - if style == yaml_LITERAL_SCALAR_STYLE || style == yaml_FOLDED_SCALAR_STYLE { - if !emitter.scalar_data.block_allowed || emitter.flow_level > 0 || emitter.simple_key_context { - style = yaml_DOUBLE_QUOTED_SCALAR_STYLE - } - } - - if no_tag && !event.quoted_implicit && style != yaml_PLAIN_SCALAR_STYLE { - emitter.tag_data.handle = []byte{'!'} - } - emitter.scalar_data.style = style - return true -} - -// Write an anchor. -func yaml_emitter_process_anchor(emitter *yaml_emitter_t) bool { - if emitter.anchor_data.anchor == nil { - return true - } - c := []byte{'&'} - if emitter.anchor_data.alias { - c[0] = '*' - } - if !yaml_emitter_write_indicator(emitter, c, true, false, false) { - return false - } - return yaml_emitter_write_anchor(emitter, emitter.anchor_data.anchor) -} - -// Write a tag. -func yaml_emitter_process_tag(emitter *yaml_emitter_t) bool { - if len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 { - return true - } - if len(emitter.tag_data.handle) > 0 { - if !yaml_emitter_write_tag_handle(emitter, emitter.tag_data.handle) { - return false - } - if len(emitter.tag_data.suffix) > 0 { - if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { - return false - } - } - } else { - // [Go] Allocate these slices elsewhere. - if !yaml_emitter_write_indicator(emitter, []byte("!<"), true, false, false) { - return false - } - if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { - return false - } - if !yaml_emitter_write_indicator(emitter, []byte{'>'}, false, false, false) { - return false - } - } - return true -} - -// Write a scalar. -func yaml_emitter_process_scalar(emitter *yaml_emitter_t) bool { - switch emitter.scalar_data.style { - case yaml_PLAIN_SCALAR_STYLE: - return yaml_emitter_write_plain_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) - - case yaml_SINGLE_QUOTED_SCALAR_STYLE: - return yaml_emitter_write_single_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) - - case yaml_DOUBLE_QUOTED_SCALAR_STYLE: - return yaml_emitter_write_double_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) - - case yaml_LITERAL_SCALAR_STYLE: - return yaml_emitter_write_literal_scalar(emitter, emitter.scalar_data.value) - - case yaml_FOLDED_SCALAR_STYLE: - return yaml_emitter_write_folded_scalar(emitter, emitter.scalar_data.value) - } - panic("unknown scalar style") -} - -// Check if a %YAML directive is valid. -func yaml_emitter_analyze_version_directive(emitter *yaml_emitter_t, version_directive *yaml_version_directive_t) bool { - if version_directive.major != 1 || version_directive.minor != 1 { - return yaml_emitter_set_emitter_error(emitter, "incompatible %YAML directive") - } - return true -} - -// Check if a %TAG directive is valid. -func yaml_emitter_analyze_tag_directive(emitter *yaml_emitter_t, tag_directive *yaml_tag_directive_t) bool { - handle := tag_directive.handle - prefix := tag_directive.prefix - if len(handle) == 0 { - return yaml_emitter_set_emitter_error(emitter, "tag handle must not be empty") - } - if handle[0] != '!' { - return yaml_emitter_set_emitter_error(emitter, "tag handle must start with '!'") - } - if handle[len(handle)-1] != '!' { - return yaml_emitter_set_emitter_error(emitter, "tag handle must end with '!'") - } - for i := 1; i < len(handle)-1; i += width(handle[i]) { - if !is_alpha(handle, i) { - return yaml_emitter_set_emitter_error(emitter, "tag handle must contain alphanumerical characters only") - } - } - if len(prefix) == 0 { - return yaml_emitter_set_emitter_error(emitter, "tag prefix must not be empty") - } - return true -} - -// Check if an anchor is valid. -func yaml_emitter_analyze_anchor(emitter *yaml_emitter_t, anchor []byte, alias bool) bool { - if len(anchor) == 0 { - problem := "anchor value must not be empty" - if alias { - problem = "alias value must not be empty" - } - return yaml_emitter_set_emitter_error(emitter, problem) - } - for i := 0; i < len(anchor); i += width(anchor[i]) { - if !is_alpha(anchor, i) { - problem := "anchor value must contain alphanumerical characters only" - if alias { - problem = "alias value must contain alphanumerical characters only" - } - return yaml_emitter_set_emitter_error(emitter, problem) - } - } - emitter.anchor_data.anchor = anchor - emitter.anchor_data.alias = alias - return true -} - -// Check if a tag is valid. -func yaml_emitter_analyze_tag(emitter *yaml_emitter_t, tag []byte) bool { - if len(tag) == 0 { - return yaml_emitter_set_emitter_error(emitter, "tag value must not be empty") - } - for i := 0; i < len(emitter.tag_directives); i++ { - tag_directive := &emitter.tag_directives[i] - if bytes.HasPrefix(tag, tag_directive.prefix) { - emitter.tag_data.handle = tag_directive.handle - emitter.tag_data.suffix = tag[len(tag_directive.prefix):] - return true - } - } - emitter.tag_data.suffix = tag - return true -} - -// Check if a scalar is valid. -func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool { - var ( - block_indicators = false - flow_indicators = false - line_breaks = false - special_characters = false - - leading_space = false - leading_break = false - trailing_space = false - trailing_break = false - break_space = false - space_break = false - - preceded_by_whitespace = false - followed_by_whitespace = false - previous_space = false - previous_break = false - ) - - emitter.scalar_data.value = value - - if len(value) == 0 { - emitter.scalar_data.multiline = false - emitter.scalar_data.flow_plain_allowed = false - emitter.scalar_data.block_plain_allowed = true - emitter.scalar_data.single_quoted_allowed = true - emitter.scalar_data.block_allowed = false - return true - } - - if len(value) >= 3 && ((value[0] == '-' && value[1] == '-' && value[2] == '-') || (value[0] == '.' && value[1] == '.' && value[2] == '.')) { - block_indicators = true - flow_indicators = true - } - - preceded_by_whitespace = true - for i, w := 0, 0; i < len(value); i += w { - w = width(value[i]) - followed_by_whitespace = i+w >= len(value) || is_blank(value, i+w) - - if i == 0 { - switch value[i] { - case '#', ',', '[', ']', '{', '}', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`': - flow_indicators = true - block_indicators = true - case '?', ':': - flow_indicators = true - if followed_by_whitespace { - block_indicators = true - } - case '-': - if followed_by_whitespace { - flow_indicators = true - block_indicators = true - } - } - } else { - switch value[i] { - case ',', '?', '[', ']', '{', '}': - flow_indicators = true - case ':': - flow_indicators = true - if followed_by_whitespace { - block_indicators = true - } - case '#': - if preceded_by_whitespace { - flow_indicators = true - block_indicators = true - } - } - } - - if !is_printable(value, i) || !is_ascii(value, i) && !emitter.unicode { - special_characters = true - } - if is_space(value, i) { - if i == 0 { - leading_space = true - } - if i+width(value[i]) == len(value) { - trailing_space = true - } - if previous_break { - break_space = true - } - previous_space = true - previous_break = false - } else if is_break(value, i) { - line_breaks = true - if i == 0 { - leading_break = true - } - if i+width(value[i]) == len(value) { - trailing_break = true - } - if previous_space { - space_break = true - } - previous_space = false - previous_break = true - } else { - previous_space = false - previous_break = false - } - - // [Go]: Why 'z'? Couldn't be the end of the string as that's the loop condition. - preceded_by_whitespace = is_blankz(value, i) - } - - emitter.scalar_data.multiline = line_breaks - emitter.scalar_data.flow_plain_allowed = true - emitter.scalar_data.block_plain_allowed = true - emitter.scalar_data.single_quoted_allowed = true - emitter.scalar_data.block_allowed = true - - if leading_space || leading_break || trailing_space || trailing_break { - emitter.scalar_data.flow_plain_allowed = false - emitter.scalar_data.block_plain_allowed = false - } - if trailing_space { - emitter.scalar_data.block_allowed = false - } - if break_space { - emitter.scalar_data.flow_plain_allowed = false - emitter.scalar_data.block_plain_allowed = false - emitter.scalar_data.single_quoted_allowed = false - } - if space_break || special_characters { - emitter.scalar_data.flow_plain_allowed = false - emitter.scalar_data.block_plain_allowed = false - emitter.scalar_data.single_quoted_allowed = false - emitter.scalar_data.block_allowed = false - } - if line_breaks { - emitter.scalar_data.flow_plain_allowed = false - emitter.scalar_data.block_plain_allowed = false - } - if flow_indicators { - emitter.scalar_data.flow_plain_allowed = false - } - if block_indicators { - emitter.scalar_data.block_plain_allowed = false - } - return true -} - -// Check if the event data is valid. -func yaml_emitter_analyze_event(emitter *yaml_emitter_t, event *yaml_event_t) bool { - - emitter.anchor_data.anchor = nil - emitter.tag_data.handle = nil - emitter.tag_data.suffix = nil - emitter.scalar_data.value = nil - - switch event.typ { - case yaml_ALIAS_EVENT: - if !yaml_emitter_analyze_anchor(emitter, event.anchor, true) { - return false - } - - case yaml_SCALAR_EVENT: - if len(event.anchor) > 0 { - if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { - return false - } - } - if len(event.tag) > 0 && (emitter.canonical || (!event.implicit && !event.quoted_implicit)) { - if !yaml_emitter_analyze_tag(emitter, event.tag) { - return false - } - } - if !yaml_emitter_analyze_scalar(emitter, event.value) { - return false - } - - case yaml_SEQUENCE_START_EVENT: - if len(event.anchor) > 0 { - if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { - return false - } - } - if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { - if !yaml_emitter_analyze_tag(emitter, event.tag) { - return false - } - } - - case yaml_MAPPING_START_EVENT: - if len(event.anchor) > 0 { - if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { - return false - } - } - if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { - if !yaml_emitter_analyze_tag(emitter, event.tag) { - return false - } - } - } - return true -} - -// Write the BOM character. -func yaml_emitter_write_bom(emitter *yaml_emitter_t) bool { - if !flush(emitter) { - return false - } - pos := emitter.buffer_pos - emitter.buffer[pos+0] = '\xEF' - emitter.buffer[pos+1] = '\xBB' - emitter.buffer[pos+2] = '\xBF' - emitter.buffer_pos += 3 - return true -} - -func yaml_emitter_write_indent(emitter *yaml_emitter_t) bool { - indent := emitter.indent - if indent < 0 { - indent = 0 - } - if !emitter.indention || emitter.column > indent || (emitter.column == indent && !emitter.whitespace) { - if !put_break(emitter) { - return false - } - } - for emitter.column < indent { - if !put(emitter, ' ') { - return false - } - } - emitter.whitespace = true - emitter.indention = true - return true -} - -func yaml_emitter_write_indicator(emitter *yaml_emitter_t, indicator []byte, need_whitespace, is_whitespace, is_indention bool) bool { - if need_whitespace && !emitter.whitespace { - if !put(emitter, ' ') { - return false - } - } - if !write_all(emitter, indicator) { - return false - } - emitter.whitespace = is_whitespace - emitter.indention = (emitter.indention && is_indention) - emitter.open_ended = false - return true -} - -func yaml_emitter_write_anchor(emitter *yaml_emitter_t, value []byte) bool { - if !write_all(emitter, value) { - return false - } - emitter.whitespace = false - emitter.indention = false - return true -} - -func yaml_emitter_write_tag_handle(emitter *yaml_emitter_t, value []byte) bool { - if !emitter.whitespace { - if !put(emitter, ' ') { - return false - } - } - if !write_all(emitter, value) { - return false - } - emitter.whitespace = false - emitter.indention = false - return true -} - -func yaml_emitter_write_tag_content(emitter *yaml_emitter_t, value []byte, need_whitespace bool) bool { - if need_whitespace && !emitter.whitespace { - if !put(emitter, ' ') { - return false - } - } - for i := 0; i < len(value); { - var must_write bool - switch value[i] { - case ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '_', '.', '~', '*', '\'', '(', ')', '[', ']': - must_write = true - default: - must_write = is_alpha(value, i) - } - if must_write { - if !write(emitter, value, &i) { - return false - } - } else { - w := width(value[i]) - for k := 0; k < w; k++ { - octet := value[i] - i++ - if !put(emitter, '%') { - return false - } - - c := octet >> 4 - if c < 10 { - c += '0' - } else { - c += 'A' - 10 - } - if !put(emitter, c) { - return false - } - - c = octet & 0x0f - if c < 10 { - c += '0' - } else { - c += 'A' - 10 - } - if !put(emitter, c) { - return false - } - } - } - } - emitter.whitespace = false - emitter.indention = false - return true -} - -func yaml_emitter_write_plain_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { - if !emitter.whitespace { - if !put(emitter, ' ') { - return false - } - } - - spaces := false - breaks := false - for i := 0; i < len(value); { - if is_space(value, i) { - if allow_breaks && !spaces && emitter.column > emitter.best_width && !is_space(value, i+1) { - if !yaml_emitter_write_indent(emitter) { - return false - } - i += width(value[i]) - } else { - if !write(emitter, value, &i) { - return false - } - } - spaces = true - } else if is_break(value, i) { - if !breaks && value[i] == '\n' { - if !put_break(emitter) { - return false - } - } - if !write_break(emitter, value, &i) { - return false - } - emitter.indention = true - breaks = true - } else { - if breaks { - if !yaml_emitter_write_indent(emitter) { - return false - } - } - if !write(emitter, value, &i) { - return false - } - emitter.indention = false - spaces = false - breaks = false - } - } - - emitter.whitespace = false - emitter.indention = false - if emitter.root_context { - emitter.open_ended = true - } - - return true -} - -func yaml_emitter_write_single_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { - - if !yaml_emitter_write_indicator(emitter, []byte{'\''}, true, false, false) { - return false - } - - spaces := false - breaks := false - for i := 0; i < len(value); { - if is_space(value, i) { - if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 && !is_space(value, i+1) { - if !yaml_emitter_write_indent(emitter) { - return false - } - i += width(value[i]) - } else { - if !write(emitter, value, &i) { - return false - } - } - spaces = true - } else if is_break(value, i) { - if !breaks && value[i] == '\n' { - if !put_break(emitter) { - return false - } - } - if !write_break(emitter, value, &i) { - return false - } - emitter.indention = true - breaks = true - } else { - if breaks { - if !yaml_emitter_write_indent(emitter) { - return false - } - } - if value[i] == '\'' { - if !put(emitter, '\'') { - return false - } - } - if !write(emitter, value, &i) { - return false - } - emitter.indention = false - spaces = false - breaks = false - } - } - if !yaml_emitter_write_indicator(emitter, []byte{'\''}, false, false, false) { - return false - } - emitter.whitespace = false - emitter.indention = false - return true -} - -func yaml_emitter_write_double_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { - spaces := false - if !yaml_emitter_write_indicator(emitter, []byte{'"'}, true, false, false) { - return false - } - - for i := 0; i < len(value); { - if !is_printable(value, i) || (!emitter.unicode && !is_ascii(value, i)) || - is_bom(value, i) || is_break(value, i) || - value[i] == '"' || value[i] == '\\' { - - octet := value[i] - - var w int - var v rune - switch { - case octet&0x80 == 0x00: - w, v = 1, rune(octet&0x7F) - case octet&0xE0 == 0xC0: - w, v = 2, rune(octet&0x1F) - case octet&0xF0 == 0xE0: - w, v = 3, rune(octet&0x0F) - case octet&0xF8 == 0xF0: - w, v = 4, rune(octet&0x07) - } - for k := 1; k < w; k++ { - octet = value[i+k] - v = (v << 6) + (rune(octet) & 0x3F) - } - i += w - - if !put(emitter, '\\') { - return false - } - - var ok bool - switch v { - case 0x00: - ok = put(emitter, '0') - case 0x07: - ok = put(emitter, 'a') - case 0x08: - ok = put(emitter, 'b') - case 0x09: - ok = put(emitter, 't') - case 0x0A: - ok = put(emitter, 'n') - case 0x0b: - ok = put(emitter, 'v') - case 0x0c: - ok = put(emitter, 'f') - case 0x0d: - ok = put(emitter, 'r') - case 0x1b: - ok = put(emitter, 'e') - case 0x22: - ok = put(emitter, '"') - case 0x5c: - ok = put(emitter, '\\') - case 0x85: - ok = put(emitter, 'N') - case 0xA0: - ok = put(emitter, '_') - case 0x2028: - ok = put(emitter, 'L') - case 0x2029: - ok = put(emitter, 'P') - default: - if v <= 0xFF { - ok = put(emitter, 'x') - w = 2 - } else if v <= 0xFFFF { - ok = put(emitter, 'u') - w = 4 - } else { - ok = put(emitter, 'U') - w = 8 - } - for k := (w - 1) * 4; ok && k >= 0; k -= 4 { - digit := byte((v >> uint(k)) & 0x0F) - if digit < 10 { - ok = put(emitter, digit+'0') - } else { - ok = put(emitter, digit+'A'-10) - } - } - } - if !ok { - return false - } - spaces = false - } else if is_space(value, i) { - if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 { - if !yaml_emitter_write_indent(emitter) { - return false - } - if is_space(value, i+1) { - if !put(emitter, '\\') { - return false - } - } - i += width(value[i]) - } else if !write(emitter, value, &i) { - return false - } - spaces = true - } else { - if !write(emitter, value, &i) { - return false - } - spaces = false - } - } - if !yaml_emitter_write_indicator(emitter, []byte{'"'}, false, false, false) { - return false - } - emitter.whitespace = false - emitter.indention = false - return true -} - -func yaml_emitter_write_block_scalar_hints(emitter *yaml_emitter_t, value []byte) bool { - if is_space(value, 0) || is_break(value, 0) { - indent_hint := []byte{'0' + byte(emitter.best_indent)} - if !yaml_emitter_write_indicator(emitter, indent_hint, false, false, false) { - return false - } - } - - emitter.open_ended = false - - var chomp_hint [1]byte - if len(value) == 0 { - chomp_hint[0] = '-' - } else { - i := len(value) - 1 - for value[i]&0xC0 == 0x80 { - i-- - } - if !is_break(value, i) { - chomp_hint[0] = '-' - } else if i == 0 { - chomp_hint[0] = '+' - emitter.open_ended = true - } else { - i-- - for value[i]&0xC0 == 0x80 { - i-- - } - if is_break(value, i) { - chomp_hint[0] = '+' - emitter.open_ended = true - } - } - } - if chomp_hint[0] != 0 { - if !yaml_emitter_write_indicator(emitter, chomp_hint[:], false, false, false) { - return false - } - } - return true -} - -func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bool { - if !yaml_emitter_write_indicator(emitter, []byte{'|'}, true, false, false) { - return false - } - if !yaml_emitter_write_block_scalar_hints(emitter, value) { - return false - } - if !put_break(emitter) { - return false - } - emitter.indention = true - emitter.whitespace = true - breaks := true - for i := 0; i < len(value); { - if is_break(value, i) { - if !write_break(emitter, value, &i) { - return false - } - emitter.indention = true - breaks = true - } else { - if breaks { - if !yaml_emitter_write_indent(emitter) { - return false - } - } - if !write(emitter, value, &i) { - return false - } - emitter.indention = false - breaks = false - } - } - - return true -} - -func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) bool { - if !yaml_emitter_write_indicator(emitter, []byte{'>'}, true, false, false) { - return false - } - if !yaml_emitter_write_block_scalar_hints(emitter, value) { - return false - } - - if !put_break(emitter) { - return false - } - emitter.indention = true - emitter.whitespace = true - - breaks := true - leading_spaces := true - for i := 0; i < len(value); { - if is_break(value, i) { - if !breaks && !leading_spaces && value[i] == '\n' { - k := 0 - for is_break(value, k) { - k += width(value[k]) - } - if !is_blankz(value, k) { - if !put_break(emitter) { - return false - } - } - } - if !write_break(emitter, value, &i) { - return false - } - emitter.indention = true - breaks = true - } else { - if breaks { - if !yaml_emitter_write_indent(emitter) { - return false - } - leading_spaces = is_blank(value, i) - } - if !breaks && is_space(value, i) && !is_space(value, i+1) && emitter.column > emitter.best_width { - if !yaml_emitter_write_indent(emitter) { - return false - } - i += width(value[i]) - } else { - if !write(emitter, value, &i) { - return false - } - } - emitter.indention = false - breaks = false - } - } - return true -} diff --git a/vendor/go.yaml.in/yaml/v2/encode.go b/vendor/go.yaml.in/yaml/v2/encode.go deleted file mode 100644 index 0ee738e11b..0000000000 --- a/vendor/go.yaml.in/yaml/v2/encode.go +++ /dev/null @@ -1,390 +0,0 @@ -package yaml - -import ( - "encoding" - "fmt" - "io" - "reflect" - "regexp" - "sort" - "strconv" - "strings" - "time" - "unicode/utf8" -) - -// jsonNumber is the interface of the encoding/json.Number datatype. -// Repeating the interface here avoids a dependency on encoding/json, and also -// supports other libraries like jsoniter, which use a similar datatype with -// the same interface. Detecting this interface is useful when dealing with -// structures containing json.Number, which is a string under the hood. The -// encoder should prefer the use of Int64(), Float64() and string(), in that -// order, when encoding this type. -type jsonNumber interface { - Float64() (float64, error) - Int64() (int64, error) - String() string -} - -type encoder struct { - emitter yaml_emitter_t - event yaml_event_t - out []byte - flow bool - // doneInit holds whether the initial stream_start_event has been - // emitted. - doneInit bool -} - -func newEncoder() *encoder { - e := &encoder{} - yaml_emitter_initialize(&e.emitter) - yaml_emitter_set_output_string(&e.emitter, &e.out) - yaml_emitter_set_unicode(&e.emitter, true) - return e -} - -func newEncoderWithWriter(w io.Writer) *encoder { - e := &encoder{} - yaml_emitter_initialize(&e.emitter) - yaml_emitter_set_output_writer(&e.emitter, w) - yaml_emitter_set_unicode(&e.emitter, true) - return e -} - -func (e *encoder) init() { - if e.doneInit { - return - } - yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING) - e.emit() - e.doneInit = true -} - -func (e *encoder) finish() { - e.emitter.open_ended = false - yaml_stream_end_event_initialize(&e.event) - e.emit() -} - -func (e *encoder) destroy() { - yaml_emitter_delete(&e.emitter) -} - -func (e *encoder) emit() { - // This will internally delete the e.event value. - e.must(yaml_emitter_emit(&e.emitter, &e.event)) -} - -func (e *encoder) must(ok bool) { - if !ok { - msg := e.emitter.problem - if msg == "" { - msg = "unknown problem generating YAML content" - } - failf("%s", msg) - } -} - -func (e *encoder) marshalDoc(tag string, in reflect.Value) { - e.init() - yaml_document_start_event_initialize(&e.event, nil, nil, true) - e.emit() - e.marshal(tag, in) - yaml_document_end_event_initialize(&e.event, true) - e.emit() -} - -func (e *encoder) marshal(tag string, in reflect.Value) { - if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() { - e.nilv() - return - } - iface := in.Interface() - switch m := iface.(type) { - case jsonNumber: - integer, err := m.Int64() - if err == nil { - // In this case the json.Number is a valid int64 - in = reflect.ValueOf(integer) - break - } - float, err := m.Float64() - if err == nil { - // In this case the json.Number is a valid float64 - in = reflect.ValueOf(float) - break - } - // fallback case - no number could be obtained - in = reflect.ValueOf(m.String()) - case time.Time, *time.Time: - // Although time.Time implements TextMarshaler, - // we don't want to treat it as a string for YAML - // purposes because YAML has special support for - // timestamps. - case Marshaler: - v, err := m.MarshalYAML() - if err != nil { - fail(err) - } - if v == nil { - e.nilv() - return - } - in = reflect.ValueOf(v) - case encoding.TextMarshaler: - text, err := m.MarshalText() - if err != nil { - fail(err) - } - in = reflect.ValueOf(string(text)) - case nil: - e.nilv() - return - } - switch in.Kind() { - case reflect.Interface: - e.marshal(tag, in.Elem()) - case reflect.Map: - e.mapv(tag, in) - case reflect.Ptr: - if in.Type() == ptrTimeType { - e.timev(tag, in.Elem()) - } else { - e.marshal(tag, in.Elem()) - } - case reflect.Struct: - if in.Type() == timeType { - e.timev(tag, in) - } else { - e.structv(tag, in) - } - case reflect.Slice, reflect.Array: - if in.Type().Elem() == mapItemType { - e.itemsv(tag, in) - } else { - e.slicev(tag, in) - } - case reflect.String: - e.stringv(tag, in) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if in.Type() == durationType { - e.stringv(tag, reflect.ValueOf(iface.(time.Duration).String())) - } else { - e.intv(tag, in) - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - e.uintv(tag, in) - case reflect.Float32, reflect.Float64: - e.floatv(tag, in) - case reflect.Bool: - e.boolv(tag, in) - default: - panic("cannot marshal type: " + in.Type().String()) - } -} - -func (e *encoder) mapv(tag string, in reflect.Value) { - e.mappingv(tag, func() { - keys := keyList(in.MapKeys()) - sort.Sort(keys) - for _, k := range keys { - e.marshal("", k) - e.marshal("", in.MapIndex(k)) - } - }) -} - -func (e *encoder) itemsv(tag string, in reflect.Value) { - e.mappingv(tag, func() { - slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem) - for _, item := range slice { - e.marshal("", reflect.ValueOf(item.Key)) - e.marshal("", reflect.ValueOf(item.Value)) - } - }) -} - -func (e *encoder) structv(tag string, in reflect.Value) { - sinfo, err := getStructInfo(in.Type()) - if err != nil { - panic(err) - } - e.mappingv(tag, func() { - for _, info := range sinfo.FieldsList { - var value reflect.Value - if info.Inline == nil { - value = in.Field(info.Num) - } else { - value = in.FieldByIndex(info.Inline) - } - if info.OmitEmpty && isZero(value) { - continue - } - e.marshal("", reflect.ValueOf(info.Key)) - e.flow = info.Flow - e.marshal("", value) - } - if sinfo.InlineMap >= 0 { - m := in.Field(sinfo.InlineMap) - if m.Len() > 0 { - e.flow = false - keys := keyList(m.MapKeys()) - sort.Sort(keys) - for _, k := range keys { - if _, found := sinfo.FieldsMap[k.String()]; found { - panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String())) - } - e.marshal("", k) - e.flow = false - e.marshal("", m.MapIndex(k)) - } - } - } - }) -} - -func (e *encoder) mappingv(tag string, f func()) { - implicit := tag == "" - style := yaml_BLOCK_MAPPING_STYLE - if e.flow { - e.flow = false - style = yaml_FLOW_MAPPING_STYLE - } - yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style) - e.emit() - f() - yaml_mapping_end_event_initialize(&e.event) - e.emit() -} - -func (e *encoder) slicev(tag string, in reflect.Value) { - implicit := tag == "" - style := yaml_BLOCK_SEQUENCE_STYLE - if e.flow { - e.flow = false - style = yaml_FLOW_SEQUENCE_STYLE - } - e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)) - e.emit() - n := in.Len() - for i := 0; i < n; i++ { - e.marshal("", in.Index(i)) - } - e.must(yaml_sequence_end_event_initialize(&e.event)) - e.emit() -} - -// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1. -// -// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported -// in YAML 1.2 and by this package, but these should be marshalled quoted for -// the time being for compatibility with other parsers. -func isBase60Float(s string) (result bool) { - // Fast path. - if s == "" { - return false - } - c := s[0] - if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 { - return false - } - // Do the full match. - return base60float.MatchString(s) -} - -// From http://yaml.org/type/float.html, except the regular expression there -// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix. -var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`) - -func (e *encoder) stringv(tag string, in reflect.Value) { - var style yaml_scalar_style_t - s := in.String() - canUsePlain := true - switch { - case !utf8.ValidString(s): - if tag == yaml_BINARY_TAG { - failf("explicitly tagged !!binary data must be base64-encoded") - } - if tag != "" { - failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag)) - } - // It can't be encoded directly as YAML so use a binary tag - // and encode it as base64. - tag = yaml_BINARY_TAG - s = encodeBase64(s) - case tag == "": - // Check to see if it would resolve to a specific - // tag when encoded unquoted. If it doesn't, - // there's no need to quote it. - rtag, _ := resolve("", s) - canUsePlain = rtag == yaml_STR_TAG && !isBase60Float(s) - } - // Note: it's possible for user code to emit invalid YAML - // if they explicitly specify a tag and a string containing - // text that's incompatible with that tag. - switch { - case strings.Contains(s, "\n"): - style = yaml_LITERAL_SCALAR_STYLE - case canUsePlain: - style = yaml_PLAIN_SCALAR_STYLE - default: - style = yaml_DOUBLE_QUOTED_SCALAR_STYLE - } - e.emitScalar(s, "", tag, style) -} - -func (e *encoder) boolv(tag string, in reflect.Value) { - var s string - if in.Bool() { - s = "true" - } else { - s = "false" - } - e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) -} - -func (e *encoder) intv(tag string, in reflect.Value) { - s := strconv.FormatInt(in.Int(), 10) - e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) -} - -func (e *encoder) uintv(tag string, in reflect.Value) { - s := strconv.FormatUint(in.Uint(), 10) - e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) -} - -func (e *encoder) timev(tag string, in reflect.Value) { - t := in.Interface().(time.Time) - s := t.Format(time.RFC3339Nano) - e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) -} - -func (e *encoder) floatv(tag string, in reflect.Value) { - // Issue #352: When formatting, use the precision of the underlying value - precision := 64 - if in.Kind() == reflect.Float32 { - precision = 32 - } - - s := strconv.FormatFloat(in.Float(), 'g', -1, precision) - switch s { - case "+Inf": - s = ".inf" - case "-Inf": - s = "-.inf" - case "NaN": - s = ".nan" - } - e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) -} - -func (e *encoder) nilv() { - e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE) -} - -func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) { - implicit := tag == "" - e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style)) - e.emit() -} diff --git a/vendor/go.yaml.in/yaml/v2/parserc.go b/vendor/go.yaml.in/yaml/v2/parserc.go deleted file mode 100644 index 81d05dfe57..0000000000 --- a/vendor/go.yaml.in/yaml/v2/parserc.go +++ /dev/null @@ -1,1095 +0,0 @@ -package yaml - -import ( - "bytes" -) - -// The parser implements the following grammar: -// -// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END -// implicit_document ::= block_node DOCUMENT-END* -// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* -// block_node_or_indentless_sequence ::= -// ALIAS -// | properties (block_content | indentless_block_sequence)? -// | block_content -// | indentless_block_sequence -// block_node ::= ALIAS -// | properties block_content? -// | block_content -// flow_node ::= ALIAS -// | properties flow_content? -// | flow_content -// properties ::= TAG ANCHOR? | ANCHOR TAG? -// block_content ::= block_collection | flow_collection | SCALAR -// flow_content ::= flow_collection | SCALAR -// block_collection ::= block_sequence | block_mapping -// flow_collection ::= flow_sequence | flow_mapping -// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END -// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ -// block_mapping ::= BLOCK-MAPPING_START -// ((KEY block_node_or_indentless_sequence?)? -// (VALUE block_node_or_indentless_sequence?)?)* -// BLOCK-END -// flow_sequence ::= FLOW-SEQUENCE-START -// (flow_sequence_entry FLOW-ENTRY)* -// flow_sequence_entry? -// FLOW-SEQUENCE-END -// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -// flow_mapping ::= FLOW-MAPPING-START -// (flow_mapping_entry FLOW-ENTRY)* -// flow_mapping_entry? -// FLOW-MAPPING-END -// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? - -// Peek the next token in the token queue. -func peek_token(parser *yaml_parser_t) *yaml_token_t { - if parser.token_available || yaml_parser_fetch_more_tokens(parser) { - return &parser.tokens[parser.tokens_head] - } - return nil -} - -// Remove the next token from the queue (must be called after peek_token). -func skip_token(parser *yaml_parser_t) { - parser.token_available = false - parser.tokens_parsed++ - parser.stream_end_produced = parser.tokens[parser.tokens_head].typ == yaml_STREAM_END_TOKEN - parser.tokens_head++ -} - -// Get the next event. -func yaml_parser_parse(parser *yaml_parser_t, event *yaml_event_t) bool { - // Erase the event object. - *event = yaml_event_t{} - - // No events after the end of the stream or error. - if parser.stream_end_produced || parser.error != yaml_NO_ERROR || parser.state == yaml_PARSE_END_STATE { - return true - } - - // Generate the next event. - return yaml_parser_state_machine(parser, event) -} - -// Set parser error. -func yaml_parser_set_parser_error(parser *yaml_parser_t, problem string, problem_mark yaml_mark_t) bool { - parser.error = yaml_PARSER_ERROR - parser.problem = problem - parser.problem_mark = problem_mark - return false -} - -func yaml_parser_set_parser_error_context(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string, problem_mark yaml_mark_t) bool { - parser.error = yaml_PARSER_ERROR - parser.context = context - parser.context_mark = context_mark - parser.problem = problem - parser.problem_mark = problem_mark - return false -} - -// State dispatcher. -func yaml_parser_state_machine(parser *yaml_parser_t, event *yaml_event_t) bool { - //trace("yaml_parser_state_machine", "state:", parser.state.String()) - - switch parser.state { - case yaml_PARSE_STREAM_START_STATE: - return yaml_parser_parse_stream_start(parser, event) - - case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: - return yaml_parser_parse_document_start(parser, event, true) - - case yaml_PARSE_DOCUMENT_START_STATE: - return yaml_parser_parse_document_start(parser, event, false) - - case yaml_PARSE_DOCUMENT_CONTENT_STATE: - return yaml_parser_parse_document_content(parser, event) - - case yaml_PARSE_DOCUMENT_END_STATE: - return yaml_parser_parse_document_end(parser, event) - - case yaml_PARSE_BLOCK_NODE_STATE: - return yaml_parser_parse_node(parser, event, true, false) - - case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: - return yaml_parser_parse_node(parser, event, true, true) - - case yaml_PARSE_FLOW_NODE_STATE: - return yaml_parser_parse_node(parser, event, false, false) - - case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: - return yaml_parser_parse_block_sequence_entry(parser, event, true) - - case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: - return yaml_parser_parse_block_sequence_entry(parser, event, false) - - case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: - return yaml_parser_parse_indentless_sequence_entry(parser, event) - - case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: - return yaml_parser_parse_block_mapping_key(parser, event, true) - - case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: - return yaml_parser_parse_block_mapping_key(parser, event, false) - - case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: - return yaml_parser_parse_block_mapping_value(parser, event) - - case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: - return yaml_parser_parse_flow_sequence_entry(parser, event, true) - - case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: - return yaml_parser_parse_flow_sequence_entry(parser, event, false) - - case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: - return yaml_parser_parse_flow_sequence_entry_mapping_key(parser, event) - - case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: - return yaml_parser_parse_flow_sequence_entry_mapping_value(parser, event) - - case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: - return yaml_parser_parse_flow_sequence_entry_mapping_end(parser, event) - - case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: - return yaml_parser_parse_flow_mapping_key(parser, event, true) - - case yaml_PARSE_FLOW_MAPPING_KEY_STATE: - return yaml_parser_parse_flow_mapping_key(parser, event, false) - - case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: - return yaml_parser_parse_flow_mapping_value(parser, event, false) - - case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: - return yaml_parser_parse_flow_mapping_value(parser, event, true) - - default: - panic("invalid parser state") - } -} - -// Parse the production: -// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END -// ************ -func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) bool { - token := peek_token(parser) - if token == nil { - return false - } - if token.typ != yaml_STREAM_START_TOKEN { - return yaml_parser_set_parser_error(parser, "did not find expected ", token.start_mark) - } - parser.state = yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE - *event = yaml_event_t{ - typ: yaml_STREAM_START_EVENT, - start_mark: token.start_mark, - end_mark: token.end_mark, - encoding: token.encoding, - } - skip_token(parser) - return true -} - -// Parse the productions: -// implicit_document ::= block_node DOCUMENT-END* -// * -// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* -// ************************* -func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t, implicit bool) bool { - - token := peek_token(parser) - if token == nil { - return false - } - - // Parse extra document end indicators. - if !implicit { - for token.typ == yaml_DOCUMENT_END_TOKEN { - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - } - } - - if implicit && token.typ != yaml_VERSION_DIRECTIVE_TOKEN && - token.typ != yaml_TAG_DIRECTIVE_TOKEN && - token.typ != yaml_DOCUMENT_START_TOKEN && - token.typ != yaml_STREAM_END_TOKEN { - // Parse an implicit document. - if !yaml_parser_process_directives(parser, nil, nil) { - return false - } - parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) - parser.state = yaml_PARSE_BLOCK_NODE_STATE - - *event = yaml_event_t{ - typ: yaml_DOCUMENT_START_EVENT, - start_mark: token.start_mark, - end_mark: token.end_mark, - } - - } else if token.typ != yaml_STREAM_END_TOKEN { - // Parse an explicit document. - var version_directive *yaml_version_directive_t - var tag_directives []yaml_tag_directive_t - start_mark := token.start_mark - if !yaml_parser_process_directives(parser, &version_directive, &tag_directives) { - return false - } - token = peek_token(parser) - if token == nil { - return false - } - if token.typ != yaml_DOCUMENT_START_TOKEN { - yaml_parser_set_parser_error(parser, - "did not find expected ", token.start_mark) - return false - } - parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) - parser.state = yaml_PARSE_DOCUMENT_CONTENT_STATE - end_mark := token.end_mark - - *event = yaml_event_t{ - typ: yaml_DOCUMENT_START_EVENT, - start_mark: start_mark, - end_mark: end_mark, - version_directive: version_directive, - tag_directives: tag_directives, - implicit: false, - } - skip_token(parser) - - } else { - // Parse the stream end. - parser.state = yaml_PARSE_END_STATE - *event = yaml_event_t{ - typ: yaml_STREAM_END_EVENT, - start_mark: token.start_mark, - end_mark: token.end_mark, - } - skip_token(parser) - } - - return true -} - -// Parse the productions: -// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* -// *********** -// -func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event_t) bool { - token := peek_token(parser) - if token == nil { - return false - } - if token.typ == yaml_VERSION_DIRECTIVE_TOKEN || - token.typ == yaml_TAG_DIRECTIVE_TOKEN || - token.typ == yaml_DOCUMENT_START_TOKEN || - token.typ == yaml_DOCUMENT_END_TOKEN || - token.typ == yaml_STREAM_END_TOKEN { - parser.state = parser.states[len(parser.states)-1] - parser.states = parser.states[:len(parser.states)-1] - return yaml_parser_process_empty_scalar(parser, event, - token.start_mark) - } - return yaml_parser_parse_node(parser, event, true, false) -} - -// Parse the productions: -// implicit_document ::= block_node DOCUMENT-END* -// ************* -// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* -// -func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t) bool { - token := peek_token(parser) - if token == nil { - return false - } - - start_mark := token.start_mark - end_mark := token.start_mark - - implicit := true - if token.typ == yaml_DOCUMENT_END_TOKEN { - end_mark = token.end_mark - skip_token(parser) - implicit = false - } - - parser.tag_directives = parser.tag_directives[:0] - - parser.state = yaml_PARSE_DOCUMENT_START_STATE - *event = yaml_event_t{ - typ: yaml_DOCUMENT_END_EVENT, - start_mark: start_mark, - end_mark: end_mark, - implicit: implicit, - } - return true -} - -// Parse the productions: -// block_node_or_indentless_sequence ::= -// ALIAS -// ***** -// | properties (block_content | indentless_block_sequence)? -// ********** * -// | block_content | indentless_block_sequence -// * -// block_node ::= ALIAS -// ***** -// | properties block_content? -// ********** * -// | block_content -// * -// flow_node ::= ALIAS -// ***** -// | properties flow_content? -// ********** * -// | flow_content -// * -// properties ::= TAG ANCHOR? | ANCHOR TAG? -// ************************* -// block_content ::= block_collection | flow_collection | SCALAR -// ****** -// flow_content ::= flow_collection | SCALAR -// ****** -func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, indentless_sequence bool) bool { - //defer trace("yaml_parser_parse_node", "block:", block, "indentless_sequence:", indentless_sequence)() - - token := peek_token(parser) - if token == nil { - return false - } - - if token.typ == yaml_ALIAS_TOKEN { - parser.state = parser.states[len(parser.states)-1] - parser.states = parser.states[:len(parser.states)-1] - *event = yaml_event_t{ - typ: yaml_ALIAS_EVENT, - start_mark: token.start_mark, - end_mark: token.end_mark, - anchor: token.value, - } - skip_token(parser) - return true - } - - start_mark := token.start_mark - end_mark := token.start_mark - - var tag_token bool - var tag_handle, tag_suffix, anchor []byte - var tag_mark yaml_mark_t - if token.typ == yaml_ANCHOR_TOKEN { - anchor = token.value - start_mark = token.start_mark - end_mark = token.end_mark - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - if token.typ == yaml_TAG_TOKEN { - tag_token = true - tag_handle = token.value - tag_suffix = token.suffix - tag_mark = token.start_mark - end_mark = token.end_mark - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - } - } else if token.typ == yaml_TAG_TOKEN { - tag_token = true - tag_handle = token.value - tag_suffix = token.suffix - start_mark = token.start_mark - tag_mark = token.start_mark - end_mark = token.end_mark - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - if token.typ == yaml_ANCHOR_TOKEN { - anchor = token.value - end_mark = token.end_mark - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - } - } - - var tag []byte - if tag_token { - if len(tag_handle) == 0 { - tag = tag_suffix - tag_suffix = nil - } else { - for i := range parser.tag_directives { - if bytes.Equal(parser.tag_directives[i].handle, tag_handle) { - tag = append([]byte(nil), parser.tag_directives[i].prefix...) - tag = append(tag, tag_suffix...) - break - } - } - if len(tag) == 0 { - yaml_parser_set_parser_error_context(parser, - "while parsing a node", start_mark, - "found undefined tag handle", tag_mark) - return false - } - } - } - - implicit := len(tag) == 0 - if indentless_sequence && token.typ == yaml_BLOCK_ENTRY_TOKEN { - end_mark = token.end_mark - parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE - *event = yaml_event_t{ - typ: yaml_SEQUENCE_START_EVENT, - start_mark: start_mark, - end_mark: end_mark, - anchor: anchor, - tag: tag, - implicit: implicit, - style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), - } - return true - } - if token.typ == yaml_SCALAR_TOKEN { - var plain_implicit, quoted_implicit bool - end_mark = token.end_mark - if (len(tag) == 0 && token.style == yaml_PLAIN_SCALAR_STYLE) || (len(tag) == 1 && tag[0] == '!') { - plain_implicit = true - } else if len(tag) == 0 { - quoted_implicit = true - } - parser.state = parser.states[len(parser.states)-1] - parser.states = parser.states[:len(parser.states)-1] - - *event = yaml_event_t{ - typ: yaml_SCALAR_EVENT, - start_mark: start_mark, - end_mark: end_mark, - anchor: anchor, - tag: tag, - value: token.value, - implicit: plain_implicit, - quoted_implicit: quoted_implicit, - style: yaml_style_t(token.style), - } - skip_token(parser) - return true - } - if token.typ == yaml_FLOW_SEQUENCE_START_TOKEN { - // [Go] Some of the events below can be merged as they differ only on style. - end_mark = token.end_mark - parser.state = yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE - *event = yaml_event_t{ - typ: yaml_SEQUENCE_START_EVENT, - start_mark: start_mark, - end_mark: end_mark, - anchor: anchor, - tag: tag, - implicit: implicit, - style: yaml_style_t(yaml_FLOW_SEQUENCE_STYLE), - } - return true - } - if token.typ == yaml_FLOW_MAPPING_START_TOKEN { - end_mark = token.end_mark - parser.state = yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE - *event = yaml_event_t{ - typ: yaml_MAPPING_START_EVENT, - start_mark: start_mark, - end_mark: end_mark, - anchor: anchor, - tag: tag, - implicit: implicit, - style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), - } - return true - } - if block && token.typ == yaml_BLOCK_SEQUENCE_START_TOKEN { - end_mark = token.end_mark - parser.state = yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE - *event = yaml_event_t{ - typ: yaml_SEQUENCE_START_EVENT, - start_mark: start_mark, - end_mark: end_mark, - anchor: anchor, - tag: tag, - implicit: implicit, - style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), - } - return true - } - if block && token.typ == yaml_BLOCK_MAPPING_START_TOKEN { - end_mark = token.end_mark - parser.state = yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE - *event = yaml_event_t{ - typ: yaml_MAPPING_START_EVENT, - start_mark: start_mark, - end_mark: end_mark, - anchor: anchor, - tag: tag, - implicit: implicit, - style: yaml_style_t(yaml_BLOCK_MAPPING_STYLE), - } - return true - } - if len(anchor) > 0 || len(tag) > 0 { - parser.state = parser.states[len(parser.states)-1] - parser.states = parser.states[:len(parser.states)-1] - - *event = yaml_event_t{ - typ: yaml_SCALAR_EVENT, - start_mark: start_mark, - end_mark: end_mark, - anchor: anchor, - tag: tag, - implicit: implicit, - quoted_implicit: false, - style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), - } - return true - } - - context := "while parsing a flow node" - if block { - context = "while parsing a block node" - } - yaml_parser_set_parser_error_context(parser, context, start_mark, - "did not find expected node content", token.start_mark) - return false -} - -// Parse the productions: -// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END -// ******************** *********** * ********* -// -func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { - if first { - token := peek_token(parser) - parser.marks = append(parser.marks, token.start_mark) - skip_token(parser) - } - - token := peek_token(parser) - if token == nil { - return false - } - - if token.typ == yaml_BLOCK_ENTRY_TOKEN { - mark := token.end_mark - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - if token.typ != yaml_BLOCK_ENTRY_TOKEN && token.typ != yaml_BLOCK_END_TOKEN { - parser.states = append(parser.states, yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE) - return yaml_parser_parse_node(parser, event, true, false) - } else { - parser.state = yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE - return yaml_parser_process_empty_scalar(parser, event, mark) - } - } - if token.typ == yaml_BLOCK_END_TOKEN { - parser.state = parser.states[len(parser.states)-1] - parser.states = parser.states[:len(parser.states)-1] - parser.marks = parser.marks[:len(parser.marks)-1] - - *event = yaml_event_t{ - typ: yaml_SEQUENCE_END_EVENT, - start_mark: token.start_mark, - end_mark: token.end_mark, - } - - skip_token(parser) - return true - } - - context_mark := parser.marks[len(parser.marks)-1] - parser.marks = parser.marks[:len(parser.marks)-1] - return yaml_parser_set_parser_error_context(parser, - "while parsing a block collection", context_mark, - "did not find expected '-' indicator", token.start_mark) -} - -// Parse the productions: -// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ -// *********** * -func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *yaml_event_t) bool { - token := peek_token(parser) - if token == nil { - return false - } - - if token.typ == yaml_BLOCK_ENTRY_TOKEN { - mark := token.end_mark - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - if token.typ != yaml_BLOCK_ENTRY_TOKEN && - token.typ != yaml_KEY_TOKEN && - token.typ != yaml_VALUE_TOKEN && - token.typ != yaml_BLOCK_END_TOKEN { - parser.states = append(parser.states, yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE) - return yaml_parser_parse_node(parser, event, true, false) - } - parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE - return yaml_parser_process_empty_scalar(parser, event, mark) - } - parser.state = parser.states[len(parser.states)-1] - parser.states = parser.states[:len(parser.states)-1] - - *event = yaml_event_t{ - typ: yaml_SEQUENCE_END_EVENT, - start_mark: token.start_mark, - end_mark: token.start_mark, // [Go] Shouldn't this be token.end_mark? - } - return true -} - -// Parse the productions: -// block_mapping ::= BLOCK-MAPPING_START -// ******************* -// ((KEY block_node_or_indentless_sequence?)? -// *** * -// (VALUE block_node_or_indentless_sequence?)?)* -// -// BLOCK-END -// ********* -// -func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { - if first { - token := peek_token(parser) - parser.marks = append(parser.marks, token.start_mark) - skip_token(parser) - } - - token := peek_token(parser) - if token == nil { - return false - } - - if token.typ == yaml_KEY_TOKEN { - mark := token.end_mark - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - if token.typ != yaml_KEY_TOKEN && - token.typ != yaml_VALUE_TOKEN && - token.typ != yaml_BLOCK_END_TOKEN { - parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_VALUE_STATE) - return yaml_parser_parse_node(parser, event, true, true) - } else { - parser.state = yaml_PARSE_BLOCK_MAPPING_VALUE_STATE - return yaml_parser_process_empty_scalar(parser, event, mark) - } - } else if token.typ == yaml_BLOCK_END_TOKEN { - parser.state = parser.states[len(parser.states)-1] - parser.states = parser.states[:len(parser.states)-1] - parser.marks = parser.marks[:len(parser.marks)-1] - *event = yaml_event_t{ - typ: yaml_MAPPING_END_EVENT, - start_mark: token.start_mark, - end_mark: token.end_mark, - } - skip_token(parser) - return true - } - - context_mark := parser.marks[len(parser.marks)-1] - parser.marks = parser.marks[:len(parser.marks)-1] - return yaml_parser_set_parser_error_context(parser, - "while parsing a block mapping", context_mark, - "did not find expected key", token.start_mark) -} - -// Parse the productions: -// block_mapping ::= BLOCK-MAPPING_START -// -// ((KEY block_node_or_indentless_sequence?)? -// -// (VALUE block_node_or_indentless_sequence?)?)* -// ***** * -// BLOCK-END -// -// -func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { - token := peek_token(parser) - if token == nil { - return false - } - if token.typ == yaml_VALUE_TOKEN { - mark := token.end_mark - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - if token.typ != yaml_KEY_TOKEN && - token.typ != yaml_VALUE_TOKEN && - token.typ != yaml_BLOCK_END_TOKEN { - parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_KEY_STATE) - return yaml_parser_parse_node(parser, event, true, true) - } - parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE - return yaml_parser_process_empty_scalar(parser, event, mark) - } - parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE - return yaml_parser_process_empty_scalar(parser, event, token.start_mark) -} - -// Parse the productions: -// flow_sequence ::= FLOW-SEQUENCE-START -// ******************* -// (flow_sequence_entry FLOW-ENTRY)* -// * ********** -// flow_sequence_entry? -// * -// FLOW-SEQUENCE-END -// ***************** -// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -// * -// -func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { - if first { - token := peek_token(parser) - parser.marks = append(parser.marks, token.start_mark) - skip_token(parser) - } - token := peek_token(parser) - if token == nil { - return false - } - if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { - if !first { - if token.typ == yaml_FLOW_ENTRY_TOKEN { - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - } else { - context_mark := parser.marks[len(parser.marks)-1] - parser.marks = parser.marks[:len(parser.marks)-1] - return yaml_parser_set_parser_error_context(parser, - "while parsing a flow sequence", context_mark, - "did not find expected ',' or ']'", token.start_mark) - } - } - - if token.typ == yaml_KEY_TOKEN { - parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE - *event = yaml_event_t{ - typ: yaml_MAPPING_START_EVENT, - start_mark: token.start_mark, - end_mark: token.end_mark, - implicit: true, - style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), - } - skip_token(parser) - return true - } else if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { - parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE) - return yaml_parser_parse_node(parser, event, false, false) - } - } - - parser.state = parser.states[len(parser.states)-1] - parser.states = parser.states[:len(parser.states)-1] - parser.marks = parser.marks[:len(parser.marks)-1] - - *event = yaml_event_t{ - typ: yaml_SEQUENCE_END_EVENT, - start_mark: token.start_mark, - end_mark: token.end_mark, - } - - skip_token(parser) - return true -} - -// -// Parse the productions: -// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -// *** * -// -func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, event *yaml_event_t) bool { - token := peek_token(parser) - if token == nil { - return false - } - if token.typ != yaml_VALUE_TOKEN && - token.typ != yaml_FLOW_ENTRY_TOKEN && - token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { - parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE) - return yaml_parser_parse_node(parser, event, false, false) - } - mark := token.end_mark - skip_token(parser) - parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE - return yaml_parser_process_empty_scalar(parser, event, mark) -} - -// Parse the productions: -// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -// ***** * -// -func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { - token := peek_token(parser) - if token == nil { - return false - } - if token.typ == yaml_VALUE_TOKEN { - skip_token(parser) - token := peek_token(parser) - if token == nil { - return false - } - if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { - parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE) - return yaml_parser_parse_node(parser, event, false, false) - } - } - parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE - return yaml_parser_process_empty_scalar(parser, event, token.start_mark) -} - -// Parse the productions: -// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -// * -// -func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, event *yaml_event_t) bool { - token := peek_token(parser) - if token == nil { - return false - } - parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE - *event = yaml_event_t{ - typ: yaml_MAPPING_END_EVENT, - start_mark: token.start_mark, - end_mark: token.start_mark, // [Go] Shouldn't this be end_mark? - } - return true -} - -// Parse the productions: -// flow_mapping ::= FLOW-MAPPING-START -// ****************** -// (flow_mapping_entry FLOW-ENTRY)* -// * ********** -// flow_mapping_entry? -// ****************** -// FLOW-MAPPING-END -// **************** -// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -// * *** * -// -func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { - if first { - token := peek_token(parser) - parser.marks = append(parser.marks, token.start_mark) - skip_token(parser) - } - - token := peek_token(parser) - if token == nil { - return false - } - - if token.typ != yaml_FLOW_MAPPING_END_TOKEN { - if !first { - if token.typ == yaml_FLOW_ENTRY_TOKEN { - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - } else { - context_mark := parser.marks[len(parser.marks)-1] - parser.marks = parser.marks[:len(parser.marks)-1] - return yaml_parser_set_parser_error_context(parser, - "while parsing a flow mapping", context_mark, - "did not find expected ',' or '}'", token.start_mark) - } - } - - if token.typ == yaml_KEY_TOKEN { - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - if token.typ != yaml_VALUE_TOKEN && - token.typ != yaml_FLOW_ENTRY_TOKEN && - token.typ != yaml_FLOW_MAPPING_END_TOKEN { - parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_VALUE_STATE) - return yaml_parser_parse_node(parser, event, false, false) - } else { - parser.state = yaml_PARSE_FLOW_MAPPING_VALUE_STATE - return yaml_parser_process_empty_scalar(parser, event, token.start_mark) - } - } else if token.typ != yaml_FLOW_MAPPING_END_TOKEN { - parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE) - return yaml_parser_parse_node(parser, event, false, false) - } - } - - parser.state = parser.states[len(parser.states)-1] - parser.states = parser.states[:len(parser.states)-1] - parser.marks = parser.marks[:len(parser.marks)-1] - *event = yaml_event_t{ - typ: yaml_MAPPING_END_EVENT, - start_mark: token.start_mark, - end_mark: token.end_mark, - } - skip_token(parser) - return true -} - -// Parse the productions: -// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -// * ***** * -// -func yaml_parser_parse_flow_mapping_value(parser *yaml_parser_t, event *yaml_event_t, empty bool) bool { - token := peek_token(parser) - if token == nil { - return false - } - if empty { - parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE - return yaml_parser_process_empty_scalar(parser, event, token.start_mark) - } - if token.typ == yaml_VALUE_TOKEN { - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_MAPPING_END_TOKEN { - parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_KEY_STATE) - return yaml_parser_parse_node(parser, event, false, false) - } - } - parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE - return yaml_parser_process_empty_scalar(parser, event, token.start_mark) -} - -// Generate an empty scalar event. -func yaml_parser_process_empty_scalar(parser *yaml_parser_t, event *yaml_event_t, mark yaml_mark_t) bool { - *event = yaml_event_t{ - typ: yaml_SCALAR_EVENT, - start_mark: mark, - end_mark: mark, - value: nil, // Empty - implicit: true, - style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), - } - return true -} - -var default_tag_directives = []yaml_tag_directive_t{ - {[]byte("!"), []byte("!")}, - {[]byte("!!"), []byte("tag:yaml.org,2002:")}, -} - -// Parse directives. -func yaml_parser_process_directives(parser *yaml_parser_t, - version_directive_ref **yaml_version_directive_t, - tag_directives_ref *[]yaml_tag_directive_t) bool { - - var version_directive *yaml_version_directive_t - var tag_directives []yaml_tag_directive_t - - token := peek_token(parser) - if token == nil { - return false - } - - for token.typ == yaml_VERSION_DIRECTIVE_TOKEN || token.typ == yaml_TAG_DIRECTIVE_TOKEN { - if token.typ == yaml_VERSION_DIRECTIVE_TOKEN { - if version_directive != nil { - yaml_parser_set_parser_error(parser, - "found duplicate %YAML directive", token.start_mark) - return false - } - if token.major != 1 || token.minor != 1 { - yaml_parser_set_parser_error(parser, - "found incompatible YAML document", token.start_mark) - return false - } - version_directive = &yaml_version_directive_t{ - major: token.major, - minor: token.minor, - } - } else if token.typ == yaml_TAG_DIRECTIVE_TOKEN { - value := yaml_tag_directive_t{ - handle: token.value, - prefix: token.prefix, - } - if !yaml_parser_append_tag_directive(parser, value, false, token.start_mark) { - return false - } - tag_directives = append(tag_directives, value) - } - - skip_token(parser) - token = peek_token(parser) - if token == nil { - return false - } - } - - for i := range default_tag_directives { - if !yaml_parser_append_tag_directive(parser, default_tag_directives[i], true, token.start_mark) { - return false - } - } - - if version_directive_ref != nil { - *version_directive_ref = version_directive - } - if tag_directives_ref != nil { - *tag_directives_ref = tag_directives - } - return true -} - -// Append a tag directive to the directives stack. -func yaml_parser_append_tag_directive(parser *yaml_parser_t, value yaml_tag_directive_t, allow_duplicates bool, mark yaml_mark_t) bool { - for i := range parser.tag_directives { - if bytes.Equal(value.handle, parser.tag_directives[i].handle) { - if allow_duplicates { - return true - } - return yaml_parser_set_parser_error(parser, "found duplicate %TAG directive", mark) - } - } - - // [Go] I suspect the copy is unnecessary. This was likely done - // because there was no way to track ownership of the data. - value_copy := yaml_tag_directive_t{ - handle: make([]byte, len(value.handle)), - prefix: make([]byte, len(value.prefix)), - } - copy(value_copy.handle, value.handle) - copy(value_copy.prefix, value.prefix) - parser.tag_directives = append(parser.tag_directives, value_copy) - return true -} diff --git a/vendor/go.yaml.in/yaml/v2/readerc.go b/vendor/go.yaml.in/yaml/v2/readerc.go deleted file mode 100644 index 7c1f5fac3d..0000000000 --- a/vendor/go.yaml.in/yaml/v2/readerc.go +++ /dev/null @@ -1,412 +0,0 @@ -package yaml - -import ( - "io" -) - -// Set the reader error and return 0. -func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool { - parser.error = yaml_READER_ERROR - parser.problem = problem - parser.problem_offset = offset - parser.problem_value = value - return false -} - -// Byte order marks. -const ( - bom_UTF8 = "\xef\xbb\xbf" - bom_UTF16LE = "\xff\xfe" - bom_UTF16BE = "\xfe\xff" -) - -// Determine the input stream encoding by checking the BOM symbol. If no BOM is -// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure. -func yaml_parser_determine_encoding(parser *yaml_parser_t) bool { - // Ensure that we had enough bytes in the raw buffer. - for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 { - if !yaml_parser_update_raw_buffer(parser) { - return false - } - } - - // Determine the encoding. - buf := parser.raw_buffer - pos := parser.raw_buffer_pos - avail := len(buf) - pos - if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] { - parser.encoding = yaml_UTF16LE_ENCODING - parser.raw_buffer_pos += 2 - parser.offset += 2 - } else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] { - parser.encoding = yaml_UTF16BE_ENCODING - parser.raw_buffer_pos += 2 - parser.offset += 2 - } else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] { - parser.encoding = yaml_UTF8_ENCODING - parser.raw_buffer_pos += 3 - parser.offset += 3 - } else { - parser.encoding = yaml_UTF8_ENCODING - } - return true -} - -// Update the raw buffer. -func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool { - size_read := 0 - - // Return if the raw buffer is full. - if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) { - return true - } - - // Return on EOF. - if parser.eof { - return true - } - - // Move the remaining bytes in the raw buffer to the beginning. - if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) { - copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:]) - } - parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos] - parser.raw_buffer_pos = 0 - - // Call the read handler to fill the buffer. - size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)]) - parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read] - if err == io.EOF { - parser.eof = true - } else if err != nil { - return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1) - } - return true -} - -// Ensure that the buffer contains at least `length` characters. -// Return true on success, false on failure. -// -// The length is supposed to be significantly less that the buffer size. -func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool { - if parser.read_handler == nil { - panic("read handler must be set") - } - - // [Go] This function was changed to guarantee the requested length size at EOF. - // The fact we need to do this is pretty awful, but the description above implies - // for that to be the case, and there are tests - - // If the EOF flag is set and the raw buffer is empty, do nothing. - if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) { - // [Go] ACTUALLY! Read the documentation of this function above. - // This is just broken. To return true, we need to have the - // given length in the buffer. Not doing that means every single - // check that calls this function to make sure the buffer has a - // given length is Go) panicking; or C) accessing invalid memory. - //return true - } - - // Return if the buffer contains enough characters. - if parser.unread >= length { - return true - } - - // Determine the input encoding if it is not known yet. - if parser.encoding == yaml_ANY_ENCODING { - if !yaml_parser_determine_encoding(parser) { - return false - } - } - - // Move the unread characters to the beginning of the buffer. - buffer_len := len(parser.buffer) - if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len { - copy(parser.buffer, parser.buffer[parser.buffer_pos:]) - buffer_len -= parser.buffer_pos - parser.buffer_pos = 0 - } else if parser.buffer_pos == buffer_len { - buffer_len = 0 - parser.buffer_pos = 0 - } - - // Open the whole buffer for writing, and cut it before returning. - parser.buffer = parser.buffer[:cap(parser.buffer)] - - // Fill the buffer until it has enough characters. - first := true - for parser.unread < length { - - // Fill the raw buffer if necessary. - if !first || parser.raw_buffer_pos == len(parser.raw_buffer) { - if !yaml_parser_update_raw_buffer(parser) { - parser.buffer = parser.buffer[:buffer_len] - return false - } - } - first = false - - // Decode the raw buffer. - inner: - for parser.raw_buffer_pos != len(parser.raw_buffer) { - var value rune - var width int - - raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos - - // Decode the next character. - switch parser.encoding { - case yaml_UTF8_ENCODING: - // Decode a UTF-8 character. Check RFC 3629 - // (http://www.ietf.org/rfc/rfc3629.txt) for more details. - // - // The following table (taken from the RFC) is used for - // decoding. - // - // Char. number range | UTF-8 octet sequence - // (hexadecimal) | (binary) - // --------------------+------------------------------------ - // 0000 0000-0000 007F | 0xxxxxxx - // 0000 0080-0000 07FF | 110xxxxx 10xxxxxx - // 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx - // 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - // - // Additionally, the characters in the range 0xD800-0xDFFF - // are prohibited as they are reserved for use with UTF-16 - // surrogate pairs. - - // Determine the length of the UTF-8 sequence. - octet := parser.raw_buffer[parser.raw_buffer_pos] - switch { - case octet&0x80 == 0x00: - width = 1 - case octet&0xE0 == 0xC0: - width = 2 - case octet&0xF0 == 0xE0: - width = 3 - case octet&0xF8 == 0xF0: - width = 4 - default: - // The leading octet is invalid. - return yaml_parser_set_reader_error(parser, - "invalid leading UTF-8 octet", - parser.offset, int(octet)) - } - - // Check if the raw buffer contains an incomplete character. - if width > raw_unread { - if parser.eof { - return yaml_parser_set_reader_error(parser, - "incomplete UTF-8 octet sequence", - parser.offset, -1) - } - break inner - } - - // Decode the leading octet. - switch { - case octet&0x80 == 0x00: - value = rune(octet & 0x7F) - case octet&0xE0 == 0xC0: - value = rune(octet & 0x1F) - case octet&0xF0 == 0xE0: - value = rune(octet & 0x0F) - case octet&0xF8 == 0xF0: - value = rune(octet & 0x07) - default: - value = 0 - } - - // Check and decode the trailing octets. - for k := 1; k < width; k++ { - octet = parser.raw_buffer[parser.raw_buffer_pos+k] - - // Check if the octet is valid. - if (octet & 0xC0) != 0x80 { - return yaml_parser_set_reader_error(parser, - "invalid trailing UTF-8 octet", - parser.offset+k, int(octet)) - } - - // Decode the octet. - value = (value << 6) + rune(octet&0x3F) - } - - // Check the length of the sequence against the value. - switch { - case width == 1: - case width == 2 && value >= 0x80: - case width == 3 && value >= 0x800: - case width == 4 && value >= 0x10000: - default: - return yaml_parser_set_reader_error(parser, - "invalid length of a UTF-8 sequence", - parser.offset, -1) - } - - // Check the range of the value. - if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF { - return yaml_parser_set_reader_error(parser, - "invalid Unicode character", - parser.offset, int(value)) - } - - case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING: - var low, high int - if parser.encoding == yaml_UTF16LE_ENCODING { - low, high = 0, 1 - } else { - low, high = 1, 0 - } - - // The UTF-16 encoding is not as simple as one might - // naively think. Check RFC 2781 - // (http://www.ietf.org/rfc/rfc2781.txt). - // - // Normally, two subsequent bytes describe a Unicode - // character. However a special technique (called a - // surrogate pair) is used for specifying character - // values larger than 0xFFFF. - // - // A surrogate pair consists of two pseudo-characters: - // high surrogate area (0xD800-0xDBFF) - // low surrogate area (0xDC00-0xDFFF) - // - // The following formulas are used for decoding - // and encoding characters using surrogate pairs: - // - // U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF) - // U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF) - // W1 = 110110yyyyyyyyyy - // W2 = 110111xxxxxxxxxx - // - // where U is the character value, W1 is the high surrogate - // area, W2 is the low surrogate area. - - // Check for incomplete UTF-16 character. - if raw_unread < 2 { - if parser.eof { - return yaml_parser_set_reader_error(parser, - "incomplete UTF-16 character", - parser.offset, -1) - } - break inner - } - - // Get the character. - value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) + - (rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8) - - // Check for unexpected low surrogate area. - if value&0xFC00 == 0xDC00 { - return yaml_parser_set_reader_error(parser, - "unexpected low surrogate area", - parser.offset, int(value)) - } - - // Check for a high surrogate area. - if value&0xFC00 == 0xD800 { - width = 4 - - // Check for incomplete surrogate pair. - if raw_unread < 4 { - if parser.eof { - return yaml_parser_set_reader_error(parser, - "incomplete UTF-16 surrogate pair", - parser.offset, -1) - } - break inner - } - - // Get the next character. - value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) + - (rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8) - - // Check for a low surrogate area. - if value2&0xFC00 != 0xDC00 { - return yaml_parser_set_reader_error(parser, - "expected low surrogate area", - parser.offset+2, int(value2)) - } - - // Generate the value of the surrogate pair. - value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF) - } else { - width = 2 - } - - default: - panic("impossible") - } - - // Check if the character is in the allowed range: - // #x9 | #xA | #xD | [#x20-#x7E] (8 bit) - // | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit) - // | [#x10000-#x10FFFF] (32 bit) - switch { - case value == 0x09: - case value == 0x0A: - case value == 0x0D: - case value >= 0x20 && value <= 0x7E: - case value == 0x85: - case value >= 0xA0 && value <= 0xD7FF: - case value >= 0xE000 && value <= 0xFFFD: - case value >= 0x10000 && value <= 0x10FFFF: - default: - return yaml_parser_set_reader_error(parser, - "control characters are not allowed", - parser.offset, int(value)) - } - - // Move the raw pointers. - parser.raw_buffer_pos += width - parser.offset += width - - // Finally put the character into the buffer. - if value <= 0x7F { - // 0000 0000-0000 007F . 0xxxxxxx - parser.buffer[buffer_len+0] = byte(value) - buffer_len += 1 - } else if value <= 0x7FF { - // 0000 0080-0000 07FF . 110xxxxx 10xxxxxx - parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6)) - parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F)) - buffer_len += 2 - } else if value <= 0xFFFF { - // 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx - parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12)) - parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F)) - parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F)) - buffer_len += 3 - } else { - // 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18)) - parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F)) - parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F)) - parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F)) - buffer_len += 4 - } - - parser.unread++ - } - - // On EOF, put NUL into the buffer and return. - if parser.eof { - parser.buffer[buffer_len] = 0 - buffer_len++ - parser.unread++ - break - } - } - // [Go] Read the documentation of this function above. To return true, - // we need to have the given length in the buffer. Not doing that means - // every single check that calls this function to make sure the buffer - // has a given length is Go) panicking; or C) accessing invalid memory. - // This happens here due to the EOF above breaking early. - for buffer_len < length { - parser.buffer[buffer_len] = 0 - buffer_len++ - } - parser.buffer = parser.buffer[:buffer_len] - return true -} diff --git a/vendor/go.yaml.in/yaml/v2/resolve.go b/vendor/go.yaml.in/yaml/v2/resolve.go deleted file mode 100644 index 4120e0c916..0000000000 --- a/vendor/go.yaml.in/yaml/v2/resolve.go +++ /dev/null @@ -1,258 +0,0 @@ -package yaml - -import ( - "encoding/base64" - "math" - "regexp" - "strconv" - "strings" - "time" -) - -type resolveMapItem struct { - value interface{} - tag string -} - -var resolveTable = make([]byte, 256) -var resolveMap = make(map[string]resolveMapItem) - -func init() { - t := resolveTable - t[int('+')] = 'S' // Sign - t[int('-')] = 'S' - for _, c := range "0123456789" { - t[int(c)] = 'D' // Digit - } - for _, c := range "yYnNtTfFoO~" { - t[int(c)] = 'M' // In map - } - t[int('.')] = '.' // Float (potentially in map) - - var resolveMapList = []struct { - v interface{} - tag string - l []string - }{ - {true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}}, - {true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}}, - {true, yaml_BOOL_TAG, []string{"on", "On", "ON"}}, - {false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}}, - {false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}}, - {false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}}, - {nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}}, - {math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}}, - {math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}}, - {math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}}, - {math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}}, - {"<<", yaml_MERGE_TAG, []string{"<<"}}, - } - - m := resolveMap - for _, item := range resolveMapList { - for _, s := range item.l { - m[s] = resolveMapItem{item.v, item.tag} - } - } -} - -const longTagPrefix = "tag:yaml.org,2002:" - -func shortTag(tag string) string { - // TODO This can easily be made faster and produce less garbage. - if strings.HasPrefix(tag, longTagPrefix) { - return "!!" + tag[len(longTagPrefix):] - } - return tag -} - -func longTag(tag string) string { - if strings.HasPrefix(tag, "!!") { - return longTagPrefix + tag[2:] - } - return tag -} - -func resolvableTag(tag string) bool { - switch tag { - case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG: - return true - } - return false -} - -var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`) - -func resolve(tag string, in string) (rtag string, out interface{}) { - if !resolvableTag(tag) { - return tag, in - } - - defer func() { - switch tag { - case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG: - return - case yaml_FLOAT_TAG: - if rtag == yaml_INT_TAG { - switch v := out.(type) { - case int64: - rtag = yaml_FLOAT_TAG - out = float64(v) - return - case int: - rtag = yaml_FLOAT_TAG - out = float64(v) - return - } - } - } - failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)) - }() - - // Any data is accepted as a !!str or !!binary. - // Otherwise, the prefix is enough of a hint about what it might be. - hint := byte('N') - if in != "" { - hint = resolveTable[in[0]] - } - if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG { - // Handle things we can lookup in a map. - if item, ok := resolveMap[in]; ok { - return item.tag, item.value - } - - // Base 60 floats are a bad idea, were dropped in YAML 1.2, and - // are purposefully unsupported here. They're still quoted on - // the way out for compatibility with other parser, though. - - switch hint { - case 'M': - // We've already checked the map above. - - case '.': - // Not in the map, so maybe a normal float. - floatv, err := strconv.ParseFloat(in, 64) - if err == nil { - return yaml_FLOAT_TAG, floatv - } - - case 'D', 'S': - // Int, float, or timestamp. - // Only try values as a timestamp if the value is unquoted or there's an explicit - // !!timestamp tag. - if tag == "" || tag == yaml_TIMESTAMP_TAG { - t, ok := parseTimestamp(in) - if ok { - return yaml_TIMESTAMP_TAG, t - } - } - - plain := strings.Replace(in, "_", "", -1) - intv, err := strconv.ParseInt(plain, 0, 64) - if err == nil { - if intv == int64(int(intv)) { - return yaml_INT_TAG, int(intv) - } else { - return yaml_INT_TAG, intv - } - } - uintv, err := strconv.ParseUint(plain, 0, 64) - if err == nil { - return yaml_INT_TAG, uintv - } - if yamlStyleFloat.MatchString(plain) { - floatv, err := strconv.ParseFloat(plain, 64) - if err == nil { - return yaml_FLOAT_TAG, floatv - } - } - if strings.HasPrefix(plain, "0b") { - intv, err := strconv.ParseInt(plain[2:], 2, 64) - if err == nil { - if intv == int64(int(intv)) { - return yaml_INT_TAG, int(intv) - } else { - return yaml_INT_TAG, intv - } - } - uintv, err := strconv.ParseUint(plain[2:], 2, 64) - if err == nil { - return yaml_INT_TAG, uintv - } - } else if strings.HasPrefix(plain, "-0b") { - intv, err := strconv.ParseInt("-" + plain[3:], 2, 64) - if err == nil { - if true || intv == int64(int(intv)) { - return yaml_INT_TAG, int(intv) - } else { - return yaml_INT_TAG, intv - } - } - } - default: - panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")") - } - } - return yaml_STR_TAG, in -} - -// encodeBase64 encodes s as base64 that is broken up into multiple lines -// as appropriate for the resulting length. -func encodeBase64(s string) string { - const lineLen = 70 - encLen := base64.StdEncoding.EncodedLen(len(s)) - lines := encLen/lineLen + 1 - buf := make([]byte, encLen*2+lines) - in := buf[0:encLen] - out := buf[encLen:] - base64.StdEncoding.Encode(in, []byte(s)) - k := 0 - for i := 0; i < len(in); i += lineLen { - j := i + lineLen - if j > len(in) { - j = len(in) - } - k += copy(out[k:], in[i:j]) - if lines > 1 { - out[k] = '\n' - k++ - } - } - return string(out[:k]) -} - -// This is a subset of the formats allowed by the regular expression -// defined at http://yaml.org/type/timestamp.html. -var allowedTimestampFormats = []string{ - "2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields. - "2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t". - "2006-1-2 15:4:5.999999999", // space separated with no time zone - "2006-1-2", // date only - // Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5" - // from the set of examples. -} - -// parseTimestamp parses s as a timestamp string and -// returns the timestamp and reports whether it succeeded. -// Timestamp formats are defined at http://yaml.org/type/timestamp.html -func parseTimestamp(s string) (time.Time, bool) { - // TODO write code to check all the formats supported by - // http://yaml.org/type/timestamp.html instead of using time.Parse. - - // Quick check: all date formats start with YYYY-. - i := 0 - for ; i < len(s); i++ { - if c := s[i]; c < '0' || c > '9' { - break - } - } - if i != 4 || i == len(s) || s[i] != '-' { - return time.Time{}, false - } - for _, format := range allowedTimestampFormats { - if t, err := time.Parse(format, s); err == nil { - return t, true - } - } - return time.Time{}, false -} diff --git a/vendor/go.yaml.in/yaml/v2/scannerc.go b/vendor/go.yaml.in/yaml/v2/scannerc.go deleted file mode 100644 index 0b9bb6030a..0000000000 --- a/vendor/go.yaml.in/yaml/v2/scannerc.go +++ /dev/null @@ -1,2711 +0,0 @@ -package yaml - -import ( - "bytes" - "fmt" -) - -// Introduction -// ************ -// -// The following notes assume that you are familiar with the YAML specification -// (http://yaml.org/spec/1.2/spec.html). We mostly follow it, although in -// some cases we are less restrictive that it requires. -// -// The process of transforming a YAML stream into a sequence of events is -// divided on two steps: Scanning and Parsing. -// -// The Scanner transforms the input stream into a sequence of tokens, while the -// parser transform the sequence of tokens produced by the Scanner into a -// sequence of parsing events. -// -// The Scanner is rather clever and complicated. The Parser, on the contrary, -// is a straightforward implementation of a recursive-descendant parser (or, -// LL(1) parser, as it is usually called). -// -// Actually there are two issues of Scanning that might be called "clever", the -// rest is quite straightforward. The issues are "block collection start" and -// "simple keys". Both issues are explained below in details. -// -// Here the Scanning step is explained and implemented. We start with the list -// of all the tokens produced by the Scanner together with short descriptions. -// -// Now, tokens: -// -// STREAM-START(encoding) # The stream start. -// STREAM-END # The stream end. -// VERSION-DIRECTIVE(major,minor) # The '%YAML' directive. -// TAG-DIRECTIVE(handle,prefix) # The '%TAG' directive. -// DOCUMENT-START # '---' -// DOCUMENT-END # '...' -// BLOCK-SEQUENCE-START # Indentation increase denoting a block -// BLOCK-MAPPING-START # sequence or a block mapping. -// BLOCK-END # Indentation decrease. -// FLOW-SEQUENCE-START # '[' -// FLOW-SEQUENCE-END # ']' -// BLOCK-SEQUENCE-START # '{' -// BLOCK-SEQUENCE-END # '}' -// BLOCK-ENTRY # '-' -// FLOW-ENTRY # ',' -// KEY # '?' or nothing (simple keys). -// VALUE # ':' -// ALIAS(anchor) # '*anchor' -// ANCHOR(anchor) # '&anchor' -// TAG(handle,suffix) # '!handle!suffix' -// SCALAR(value,style) # A scalar. -// -// The following two tokens are "virtual" tokens denoting the beginning and the -// end of the stream: -// -// STREAM-START(encoding) -// STREAM-END -// -// We pass the information about the input stream encoding with the -// STREAM-START token. -// -// The next two tokens are responsible for tags: -// -// VERSION-DIRECTIVE(major,minor) -// TAG-DIRECTIVE(handle,prefix) -// -// Example: -// -// %YAML 1.1 -// %TAG ! !foo -// %TAG !yaml! tag:yaml.org,2002: -// --- -// -// The correspoding sequence of tokens: -// -// STREAM-START(utf-8) -// VERSION-DIRECTIVE(1,1) -// TAG-DIRECTIVE("!","!foo") -// TAG-DIRECTIVE("!yaml","tag:yaml.org,2002:") -// DOCUMENT-START -// STREAM-END -// -// Note that the VERSION-DIRECTIVE and TAG-DIRECTIVE tokens occupy a whole -// line. -// -// The document start and end indicators are represented by: -// -// DOCUMENT-START -// DOCUMENT-END -// -// Note that if a YAML stream contains an implicit document (without '---' -// and '...' indicators), no DOCUMENT-START and DOCUMENT-END tokens will be -// produced. -// -// In the following examples, we present whole documents together with the -// produced tokens. -// -// 1. An implicit document: -// -// 'a scalar' -// -// Tokens: -// -// STREAM-START(utf-8) -// SCALAR("a scalar",single-quoted) -// STREAM-END -// -// 2. An explicit document: -// -// --- -// 'a scalar' -// ... -// -// Tokens: -// -// STREAM-START(utf-8) -// DOCUMENT-START -// SCALAR("a scalar",single-quoted) -// DOCUMENT-END -// STREAM-END -// -// 3. Several documents in a stream: -// -// 'a scalar' -// --- -// 'another scalar' -// --- -// 'yet another scalar' -// -// Tokens: -// -// STREAM-START(utf-8) -// SCALAR("a scalar",single-quoted) -// DOCUMENT-START -// SCALAR("another scalar",single-quoted) -// DOCUMENT-START -// SCALAR("yet another scalar",single-quoted) -// STREAM-END -// -// We have already introduced the SCALAR token above. The following tokens are -// used to describe aliases, anchors, tag, and scalars: -// -// ALIAS(anchor) -// ANCHOR(anchor) -// TAG(handle,suffix) -// SCALAR(value,style) -// -// The following series of examples illustrate the usage of these tokens: -// -// 1. A recursive sequence: -// -// &A [ *A ] -// -// Tokens: -// -// STREAM-START(utf-8) -// ANCHOR("A") -// FLOW-SEQUENCE-START -// ALIAS("A") -// FLOW-SEQUENCE-END -// STREAM-END -// -// 2. A tagged scalar: -// -// !!float "3.14" # A good approximation. -// -// Tokens: -// -// STREAM-START(utf-8) -// TAG("!!","float") -// SCALAR("3.14",double-quoted) -// STREAM-END -// -// 3. Various scalar styles: -// -// --- # Implicit empty plain scalars do not produce tokens. -// --- a plain scalar -// --- 'a single-quoted scalar' -// --- "a double-quoted scalar" -// --- |- -// a literal scalar -// --- >- -// a folded -// scalar -// -// Tokens: -// -// STREAM-START(utf-8) -// DOCUMENT-START -// DOCUMENT-START -// SCALAR("a plain scalar",plain) -// DOCUMENT-START -// SCALAR("a single-quoted scalar",single-quoted) -// DOCUMENT-START -// SCALAR("a double-quoted scalar",double-quoted) -// DOCUMENT-START -// SCALAR("a literal scalar",literal) -// DOCUMENT-START -// SCALAR("a folded scalar",folded) -// STREAM-END -// -// Now it's time to review collection-related tokens. We will start with -// flow collections: -// -// FLOW-SEQUENCE-START -// FLOW-SEQUENCE-END -// FLOW-MAPPING-START -// FLOW-MAPPING-END -// FLOW-ENTRY -// KEY -// VALUE -// -// The tokens FLOW-SEQUENCE-START, FLOW-SEQUENCE-END, FLOW-MAPPING-START, and -// FLOW-MAPPING-END represent the indicators '[', ']', '{', and '}' -// correspondingly. FLOW-ENTRY represent the ',' indicator. Finally the -// indicators '?' and ':', which are used for denoting mapping keys and values, -// are represented by the KEY and VALUE tokens. -// -// The following examples show flow collections: -// -// 1. A flow sequence: -// -// [item 1, item 2, item 3] -// -// Tokens: -// -// STREAM-START(utf-8) -// FLOW-SEQUENCE-START -// SCALAR("item 1",plain) -// FLOW-ENTRY -// SCALAR("item 2",plain) -// FLOW-ENTRY -// SCALAR("item 3",plain) -// FLOW-SEQUENCE-END -// STREAM-END -// -// 2. A flow mapping: -// -// { -// a simple key: a value, # Note that the KEY token is produced. -// ? a complex key: another value, -// } -// -// Tokens: -// -// STREAM-START(utf-8) -// FLOW-MAPPING-START -// KEY -// SCALAR("a simple key",plain) -// VALUE -// SCALAR("a value",plain) -// FLOW-ENTRY -// KEY -// SCALAR("a complex key",plain) -// VALUE -// SCALAR("another value",plain) -// FLOW-ENTRY -// FLOW-MAPPING-END -// STREAM-END -// -// A simple key is a key which is not denoted by the '?' indicator. Note that -// the Scanner still produce the KEY token whenever it encounters a simple key. -// -// For scanning block collections, the following tokens are used (note that we -// repeat KEY and VALUE here): -// -// BLOCK-SEQUENCE-START -// BLOCK-MAPPING-START -// BLOCK-END -// BLOCK-ENTRY -// KEY -// VALUE -// -// The tokens BLOCK-SEQUENCE-START and BLOCK-MAPPING-START denote indentation -// increase that precedes a block collection (cf. the INDENT token in Python). -// The token BLOCK-END denote indentation decrease that ends a block collection -// (cf. the DEDENT token in Python). However YAML has some syntax pecularities -// that makes detections of these tokens more complex. -// -// The tokens BLOCK-ENTRY, KEY, and VALUE are used to represent the indicators -// '-', '?', and ':' correspondingly. -// -// The following examples show how the tokens BLOCK-SEQUENCE-START, -// BLOCK-MAPPING-START, and BLOCK-END are emitted by the Scanner: -// -// 1. Block sequences: -// -// - item 1 -// - item 2 -// - -// - item 3.1 -// - item 3.2 -// - -// key 1: value 1 -// key 2: value 2 -// -// Tokens: -// -// STREAM-START(utf-8) -// BLOCK-SEQUENCE-START -// BLOCK-ENTRY -// SCALAR("item 1",plain) -// BLOCK-ENTRY -// SCALAR("item 2",plain) -// BLOCK-ENTRY -// BLOCK-SEQUENCE-START -// BLOCK-ENTRY -// SCALAR("item 3.1",plain) -// BLOCK-ENTRY -// SCALAR("item 3.2",plain) -// BLOCK-END -// BLOCK-ENTRY -// BLOCK-MAPPING-START -// KEY -// SCALAR("key 1",plain) -// VALUE -// SCALAR("value 1",plain) -// KEY -// SCALAR("key 2",plain) -// VALUE -// SCALAR("value 2",plain) -// BLOCK-END -// BLOCK-END -// STREAM-END -// -// 2. Block mappings: -// -// a simple key: a value # The KEY token is produced here. -// ? a complex key -// : another value -// a mapping: -// key 1: value 1 -// key 2: value 2 -// a sequence: -// - item 1 -// - item 2 -// -// Tokens: -// -// STREAM-START(utf-8) -// BLOCK-MAPPING-START -// KEY -// SCALAR("a simple key",plain) -// VALUE -// SCALAR("a value",plain) -// KEY -// SCALAR("a complex key",plain) -// VALUE -// SCALAR("another value",plain) -// KEY -// SCALAR("a mapping",plain) -// BLOCK-MAPPING-START -// KEY -// SCALAR("key 1",plain) -// VALUE -// SCALAR("value 1",plain) -// KEY -// SCALAR("key 2",plain) -// VALUE -// SCALAR("value 2",plain) -// BLOCK-END -// KEY -// SCALAR("a sequence",plain) -// VALUE -// BLOCK-SEQUENCE-START -// BLOCK-ENTRY -// SCALAR("item 1",plain) -// BLOCK-ENTRY -// SCALAR("item 2",plain) -// BLOCK-END -// BLOCK-END -// STREAM-END -// -// YAML does not always require to start a new block collection from a new -// line. If the current line contains only '-', '?', and ':' indicators, a new -// block collection may start at the current line. The following examples -// illustrate this case: -// -// 1. Collections in a sequence: -// -// - - item 1 -// - item 2 -// - key 1: value 1 -// key 2: value 2 -// - ? complex key -// : complex value -// -// Tokens: -// -// STREAM-START(utf-8) -// BLOCK-SEQUENCE-START -// BLOCK-ENTRY -// BLOCK-SEQUENCE-START -// BLOCK-ENTRY -// SCALAR("item 1",plain) -// BLOCK-ENTRY -// SCALAR("item 2",plain) -// BLOCK-END -// BLOCK-ENTRY -// BLOCK-MAPPING-START -// KEY -// SCALAR("key 1",plain) -// VALUE -// SCALAR("value 1",plain) -// KEY -// SCALAR("key 2",plain) -// VALUE -// SCALAR("value 2",plain) -// BLOCK-END -// BLOCK-ENTRY -// BLOCK-MAPPING-START -// KEY -// SCALAR("complex key") -// VALUE -// SCALAR("complex value") -// BLOCK-END -// BLOCK-END -// STREAM-END -// -// 2. Collections in a mapping: -// -// ? a sequence -// : - item 1 -// - item 2 -// ? a mapping -// : key 1: value 1 -// key 2: value 2 -// -// Tokens: -// -// STREAM-START(utf-8) -// BLOCK-MAPPING-START -// KEY -// SCALAR("a sequence",plain) -// VALUE -// BLOCK-SEQUENCE-START -// BLOCK-ENTRY -// SCALAR("item 1",plain) -// BLOCK-ENTRY -// SCALAR("item 2",plain) -// BLOCK-END -// KEY -// SCALAR("a mapping",plain) -// VALUE -// BLOCK-MAPPING-START -// KEY -// SCALAR("key 1",plain) -// VALUE -// SCALAR("value 1",plain) -// KEY -// SCALAR("key 2",plain) -// VALUE -// SCALAR("value 2",plain) -// BLOCK-END -// BLOCK-END -// STREAM-END -// -// YAML also permits non-indented sequences if they are included into a block -// mapping. In this case, the token BLOCK-SEQUENCE-START is not produced: -// -// key: -// - item 1 # BLOCK-SEQUENCE-START is NOT produced here. -// - item 2 -// -// Tokens: -// -// STREAM-START(utf-8) -// BLOCK-MAPPING-START -// KEY -// SCALAR("key",plain) -// VALUE -// BLOCK-ENTRY -// SCALAR("item 1",plain) -// BLOCK-ENTRY -// SCALAR("item 2",plain) -// BLOCK-END -// - -// Ensure that the buffer contains the required number of characters. -// Return true on success, false on failure (reader error or memory error). -func cache(parser *yaml_parser_t, length int) bool { - // [Go] This was inlined: !cache(A, B) -> unread < B && !update(A, B) - return parser.unread >= length || yaml_parser_update_buffer(parser, length) -} - -// Advance the buffer pointer. -func skip(parser *yaml_parser_t) { - parser.mark.index++ - parser.mark.column++ - parser.unread-- - parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) -} - -func skip_line(parser *yaml_parser_t) { - if is_crlf(parser.buffer, parser.buffer_pos) { - parser.mark.index += 2 - parser.mark.column = 0 - parser.mark.line++ - parser.unread -= 2 - parser.buffer_pos += 2 - } else if is_break(parser.buffer, parser.buffer_pos) { - parser.mark.index++ - parser.mark.column = 0 - parser.mark.line++ - parser.unread-- - parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) - } -} - -// Copy a character to a string buffer and advance pointers. -func read(parser *yaml_parser_t, s []byte) []byte { - w := width(parser.buffer[parser.buffer_pos]) - if w == 0 { - panic("invalid character sequence") - } - if len(s) == 0 { - s = make([]byte, 0, 32) - } - if w == 1 && len(s)+w <= cap(s) { - s = s[:len(s)+1] - s[len(s)-1] = parser.buffer[parser.buffer_pos] - parser.buffer_pos++ - } else { - s = append(s, parser.buffer[parser.buffer_pos:parser.buffer_pos+w]...) - parser.buffer_pos += w - } - parser.mark.index++ - parser.mark.column++ - parser.unread-- - return s -} - -// Copy a line break character to a string buffer and advance pointers. -func read_line(parser *yaml_parser_t, s []byte) []byte { - buf := parser.buffer - pos := parser.buffer_pos - switch { - case buf[pos] == '\r' && buf[pos+1] == '\n': - // CR LF . LF - s = append(s, '\n') - parser.buffer_pos += 2 - parser.mark.index++ - parser.unread-- - case buf[pos] == '\r' || buf[pos] == '\n': - // CR|LF . LF - s = append(s, '\n') - parser.buffer_pos += 1 - case buf[pos] == '\xC2' && buf[pos+1] == '\x85': - // NEL . LF - s = append(s, '\n') - parser.buffer_pos += 2 - case buf[pos] == '\xE2' && buf[pos+1] == '\x80' && (buf[pos+2] == '\xA8' || buf[pos+2] == '\xA9'): - // LS|PS . LS|PS - s = append(s, buf[parser.buffer_pos:pos+3]...) - parser.buffer_pos += 3 - default: - return s - } - parser.mark.index++ - parser.mark.column = 0 - parser.mark.line++ - parser.unread-- - return s -} - -// Get the next token. -func yaml_parser_scan(parser *yaml_parser_t, token *yaml_token_t) bool { - // Erase the token object. - *token = yaml_token_t{} // [Go] Is this necessary? - - // No tokens after STREAM-END or error. - if parser.stream_end_produced || parser.error != yaml_NO_ERROR { - return true - } - - // Ensure that the tokens queue contains enough tokens. - if !parser.token_available { - if !yaml_parser_fetch_more_tokens(parser) { - return false - } - } - - // Fetch the next token from the queue. - *token = parser.tokens[parser.tokens_head] - parser.tokens_head++ - parser.tokens_parsed++ - parser.token_available = false - - if token.typ == yaml_STREAM_END_TOKEN { - parser.stream_end_produced = true - } - return true -} - -// Set the scanner error and return false. -func yaml_parser_set_scanner_error(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string) bool { - parser.error = yaml_SCANNER_ERROR - parser.context = context - parser.context_mark = context_mark - parser.problem = problem - parser.problem_mark = parser.mark - return false -} - -func yaml_parser_set_scanner_tag_error(parser *yaml_parser_t, directive bool, context_mark yaml_mark_t, problem string) bool { - context := "while parsing a tag" - if directive { - context = "while parsing a %TAG directive" - } - return yaml_parser_set_scanner_error(parser, context, context_mark, problem) -} - -func trace(args ...interface{}) func() { - pargs := append([]interface{}{"+++"}, args...) - fmt.Println(pargs...) - pargs = append([]interface{}{"---"}, args...) - return func() { fmt.Println(pargs...) } -} - -// Ensure that the tokens queue contains at least one token which can be -// returned to the Parser. -func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool { - // While we need more tokens to fetch, do it. - for { - if parser.tokens_head != len(parser.tokens) { - // If queue is non-empty, check if any potential simple key may - // occupy the head position. - head_tok_idx, ok := parser.simple_keys_by_tok[parser.tokens_parsed] - if !ok { - break - } else if valid, ok := yaml_simple_key_is_valid(parser, &parser.simple_keys[head_tok_idx]); !ok { - return false - } else if !valid { - break - } - } - // Fetch the next token. - if !yaml_parser_fetch_next_token(parser) { - return false - } - } - - parser.token_available = true - return true -} - -// The dispatcher for token fetchers. -func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool { - // Ensure that the buffer is initialized. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - - // Check if we just started scanning. Fetch STREAM-START then. - if !parser.stream_start_produced { - return yaml_parser_fetch_stream_start(parser) - } - - // Eat whitespaces and comments until we reach the next token. - if !yaml_parser_scan_to_next_token(parser) { - return false - } - - // Check the indentation level against the current column. - if !yaml_parser_unroll_indent(parser, parser.mark.column) { - return false - } - - // Ensure that the buffer contains at least 4 characters. 4 is the length - // of the longest indicators ('--- ' and '... '). - if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { - return false - } - - // Is it the end of the stream? - if is_z(parser.buffer, parser.buffer_pos) { - return yaml_parser_fetch_stream_end(parser) - } - - // Is it a directive? - if parser.mark.column == 0 && parser.buffer[parser.buffer_pos] == '%' { - return yaml_parser_fetch_directive(parser) - } - - buf := parser.buffer - pos := parser.buffer_pos - - // Is it the document start indicator? - if parser.mark.column == 0 && buf[pos] == '-' && buf[pos+1] == '-' && buf[pos+2] == '-' && is_blankz(buf, pos+3) { - return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_START_TOKEN) - } - - // Is it the document end indicator? - if parser.mark.column == 0 && buf[pos] == '.' && buf[pos+1] == '.' && buf[pos+2] == '.' && is_blankz(buf, pos+3) { - return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_END_TOKEN) - } - - // Is it the flow sequence start indicator? - if buf[pos] == '[' { - return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_SEQUENCE_START_TOKEN) - } - - // Is it the flow mapping start indicator? - if parser.buffer[parser.buffer_pos] == '{' { - return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_MAPPING_START_TOKEN) - } - - // Is it the flow sequence end indicator? - if parser.buffer[parser.buffer_pos] == ']' { - return yaml_parser_fetch_flow_collection_end(parser, - yaml_FLOW_SEQUENCE_END_TOKEN) - } - - // Is it the flow mapping end indicator? - if parser.buffer[parser.buffer_pos] == '}' { - return yaml_parser_fetch_flow_collection_end(parser, - yaml_FLOW_MAPPING_END_TOKEN) - } - - // Is it the flow entry indicator? - if parser.buffer[parser.buffer_pos] == ',' { - return yaml_parser_fetch_flow_entry(parser) - } - - // Is it the block entry indicator? - if parser.buffer[parser.buffer_pos] == '-' && is_blankz(parser.buffer, parser.buffer_pos+1) { - return yaml_parser_fetch_block_entry(parser) - } - - // Is it the key indicator? - if parser.buffer[parser.buffer_pos] == '?' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { - return yaml_parser_fetch_key(parser) - } - - // Is it the value indicator? - if parser.buffer[parser.buffer_pos] == ':' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { - return yaml_parser_fetch_value(parser) - } - - // Is it an alias? - if parser.buffer[parser.buffer_pos] == '*' { - return yaml_parser_fetch_anchor(parser, yaml_ALIAS_TOKEN) - } - - // Is it an anchor? - if parser.buffer[parser.buffer_pos] == '&' { - return yaml_parser_fetch_anchor(parser, yaml_ANCHOR_TOKEN) - } - - // Is it a tag? - if parser.buffer[parser.buffer_pos] == '!' { - return yaml_parser_fetch_tag(parser) - } - - // Is it a literal scalar? - if parser.buffer[parser.buffer_pos] == '|' && parser.flow_level == 0 { - return yaml_parser_fetch_block_scalar(parser, true) - } - - // Is it a folded scalar? - if parser.buffer[parser.buffer_pos] == '>' && parser.flow_level == 0 { - return yaml_parser_fetch_block_scalar(parser, false) - } - - // Is it a single-quoted scalar? - if parser.buffer[parser.buffer_pos] == '\'' { - return yaml_parser_fetch_flow_scalar(parser, true) - } - - // Is it a double-quoted scalar? - if parser.buffer[parser.buffer_pos] == '"' { - return yaml_parser_fetch_flow_scalar(parser, false) - } - - // Is it a plain scalar? - // - // A plain scalar may start with any non-blank characters except - // - // '-', '?', ':', ',', '[', ']', '{', '}', - // '#', '&', '*', '!', '|', '>', '\'', '\"', - // '%', '@', '`'. - // - // In the block context (and, for the '-' indicator, in the flow context - // too), it may also start with the characters - // - // '-', '?', ':' - // - // if it is followed by a non-space character. - // - // The last rule is more restrictive than the specification requires. - // [Go] Make this logic more reasonable. - //switch parser.buffer[parser.buffer_pos] { - //case '-', '?', ':', ',', '?', '-', ',', ':', ']', '[', '}', '{', '&', '#', '!', '*', '>', '|', '"', '\'', '@', '%', '-', '`': - //} - if !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '-' || - parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':' || - parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '[' || - parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || - parser.buffer[parser.buffer_pos] == '}' || parser.buffer[parser.buffer_pos] == '#' || - parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '*' || - parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '|' || - parser.buffer[parser.buffer_pos] == '>' || parser.buffer[parser.buffer_pos] == '\'' || - parser.buffer[parser.buffer_pos] == '"' || parser.buffer[parser.buffer_pos] == '%' || - parser.buffer[parser.buffer_pos] == '@' || parser.buffer[parser.buffer_pos] == '`') || - (parser.buffer[parser.buffer_pos] == '-' && !is_blank(parser.buffer, parser.buffer_pos+1)) || - (parser.flow_level == 0 && - (parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':') && - !is_blankz(parser.buffer, parser.buffer_pos+1)) { - return yaml_parser_fetch_plain_scalar(parser) - } - - // If we don't determine the token type so far, it is an error. - return yaml_parser_set_scanner_error(parser, - "while scanning for the next token", parser.mark, - "found character that cannot start any token") -} - -func yaml_simple_key_is_valid(parser *yaml_parser_t, simple_key *yaml_simple_key_t) (valid, ok bool) { - if !simple_key.possible { - return false, true - } - - // The 1.2 specification says: - // - // "If the ? indicator is omitted, parsing needs to see past the - // implicit key to recognize it as such. To limit the amount of - // lookahead required, the “:” indicator must appear at most 1024 - // Unicode characters beyond the start of the key. In addition, the key - // is restricted to a single line." - // - if simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index { - // Check if the potential simple key to be removed is required. - if simple_key.required { - return false, yaml_parser_set_scanner_error(parser, - "while scanning a simple key", simple_key.mark, - "could not find expected ':'") - } - simple_key.possible = false - return false, true - } - return true, true -} - -// Check if a simple key may start at the current position and add it if -// needed. -func yaml_parser_save_simple_key(parser *yaml_parser_t) bool { - // A simple key is required at the current position if the scanner is in - // the block context and the current column coincides with the indentation - // level. - - required := parser.flow_level == 0 && parser.indent == parser.mark.column - - // - // If the current position may start a simple key, save it. - // - if parser.simple_key_allowed { - simple_key := yaml_simple_key_t{ - possible: true, - required: required, - token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), - mark: parser.mark, - } - - if !yaml_parser_remove_simple_key(parser) { - return false - } - parser.simple_keys[len(parser.simple_keys)-1] = simple_key - parser.simple_keys_by_tok[simple_key.token_number] = len(parser.simple_keys) - 1 - } - return true -} - -// Remove a potential simple key at the current flow level. -func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool { - i := len(parser.simple_keys) - 1 - if parser.simple_keys[i].possible { - // If the key is required, it is an error. - if parser.simple_keys[i].required { - return yaml_parser_set_scanner_error(parser, - "while scanning a simple key", parser.simple_keys[i].mark, - "could not find expected ':'") - } - // Remove the key from the stack. - parser.simple_keys[i].possible = false - delete(parser.simple_keys_by_tok, parser.simple_keys[i].token_number) - } - return true -} - -// max_flow_level limits the flow_level -const max_flow_level = 10000 - -// Increase the flow level and resize the simple key list if needed. -func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { - // Reset the simple key on the next level. - parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{ - possible: false, - required: false, - token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), - mark: parser.mark, - }) - - // Increase the flow level. - parser.flow_level++ - if parser.flow_level > max_flow_level { - return yaml_parser_set_scanner_error(parser, - "while increasing flow level", parser.simple_keys[len(parser.simple_keys)-1].mark, - fmt.Sprintf("exceeded max depth of %d", max_flow_level)) - } - return true -} - -// Decrease the flow level. -func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool { - if parser.flow_level > 0 { - parser.flow_level-- - last := len(parser.simple_keys) - 1 - delete(parser.simple_keys_by_tok, parser.simple_keys[last].token_number) - parser.simple_keys = parser.simple_keys[:last] - } - return true -} - -// max_indents limits the indents stack size -const max_indents = 10000 - -// Push the current indentation level to the stack and set the new level -// the current column is greater than the indentation level. In this case, -// append or insert the specified token into the token queue. -func yaml_parser_roll_indent(parser *yaml_parser_t, column, number int, typ yaml_token_type_t, mark yaml_mark_t) bool { - // In the flow context, do nothing. - if parser.flow_level > 0 { - return true - } - - if parser.indent < column { - // Push the current indentation level to the stack and set the new - // indentation level. - parser.indents = append(parser.indents, parser.indent) - parser.indent = column - if len(parser.indents) > max_indents { - return yaml_parser_set_scanner_error(parser, - "while increasing indent level", parser.simple_keys[len(parser.simple_keys)-1].mark, - fmt.Sprintf("exceeded max depth of %d", max_indents)) - } - - // Create a token and insert it into the queue. - token := yaml_token_t{ - typ: typ, - start_mark: mark, - end_mark: mark, - } - if number > -1 { - number -= parser.tokens_parsed - } - yaml_insert_token(parser, number, &token) - } - return true -} - -// Pop indentation levels from the indents stack until the current level -// becomes less or equal to the column. For each indentation level, append -// the BLOCK-END token. -func yaml_parser_unroll_indent(parser *yaml_parser_t, column int) bool { - // In the flow context, do nothing. - if parser.flow_level > 0 { - return true - } - - // Loop through the indentation levels in the stack. - for parser.indent > column { - // Create a token and append it to the queue. - token := yaml_token_t{ - typ: yaml_BLOCK_END_TOKEN, - start_mark: parser.mark, - end_mark: parser.mark, - } - yaml_insert_token(parser, -1, &token) - - // Pop the indentation level. - parser.indent = parser.indents[len(parser.indents)-1] - parser.indents = parser.indents[:len(parser.indents)-1] - } - return true -} - -// Initialize the scanner and produce the STREAM-START token. -func yaml_parser_fetch_stream_start(parser *yaml_parser_t) bool { - - // Set the initial indentation. - parser.indent = -1 - - // Initialize the simple key stack. - parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) - - parser.simple_keys_by_tok = make(map[int]int) - - // A simple key is allowed at the beginning of the stream. - parser.simple_key_allowed = true - - // We have started. - parser.stream_start_produced = true - - // Create the STREAM-START token and append it to the queue. - token := yaml_token_t{ - typ: yaml_STREAM_START_TOKEN, - start_mark: parser.mark, - end_mark: parser.mark, - encoding: parser.encoding, - } - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce the STREAM-END token and shut down the scanner. -func yaml_parser_fetch_stream_end(parser *yaml_parser_t) bool { - - // Force new line. - if parser.mark.column != 0 { - parser.mark.column = 0 - parser.mark.line++ - } - - // Reset the indentation level. - if !yaml_parser_unroll_indent(parser, -1) { - return false - } - - // Reset simple keys. - if !yaml_parser_remove_simple_key(parser) { - return false - } - - parser.simple_key_allowed = false - - // Create the STREAM-END token and append it to the queue. - token := yaml_token_t{ - typ: yaml_STREAM_END_TOKEN, - start_mark: parser.mark, - end_mark: parser.mark, - } - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce a VERSION-DIRECTIVE or TAG-DIRECTIVE token. -func yaml_parser_fetch_directive(parser *yaml_parser_t) bool { - // Reset the indentation level. - if !yaml_parser_unroll_indent(parser, -1) { - return false - } - - // Reset simple keys. - if !yaml_parser_remove_simple_key(parser) { - return false - } - - parser.simple_key_allowed = false - - // Create the YAML-DIRECTIVE or TAG-DIRECTIVE token. - token := yaml_token_t{} - if !yaml_parser_scan_directive(parser, &token) { - return false - } - // Append the token to the queue. - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce the DOCUMENT-START or DOCUMENT-END token. -func yaml_parser_fetch_document_indicator(parser *yaml_parser_t, typ yaml_token_type_t) bool { - // Reset the indentation level. - if !yaml_parser_unroll_indent(parser, -1) { - return false - } - - // Reset simple keys. - if !yaml_parser_remove_simple_key(parser) { - return false - } - - parser.simple_key_allowed = false - - // Consume the token. - start_mark := parser.mark - - skip(parser) - skip(parser) - skip(parser) - - end_mark := parser.mark - - // Create the DOCUMENT-START or DOCUMENT-END token. - token := yaml_token_t{ - typ: typ, - start_mark: start_mark, - end_mark: end_mark, - } - // Append the token to the queue. - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token. -func yaml_parser_fetch_flow_collection_start(parser *yaml_parser_t, typ yaml_token_type_t) bool { - // The indicators '[' and '{' may start a simple key. - if !yaml_parser_save_simple_key(parser) { - return false - } - - // Increase the flow level. - if !yaml_parser_increase_flow_level(parser) { - return false - } - - // A simple key may follow the indicators '[' and '{'. - parser.simple_key_allowed = true - - // Consume the token. - start_mark := parser.mark - skip(parser) - end_mark := parser.mark - - // Create the FLOW-SEQUENCE-START of FLOW-MAPPING-START token. - token := yaml_token_t{ - typ: typ, - start_mark: start_mark, - end_mark: end_mark, - } - // Append the token to the queue. - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce the FLOW-SEQUENCE-END or FLOW-MAPPING-END token. -func yaml_parser_fetch_flow_collection_end(parser *yaml_parser_t, typ yaml_token_type_t) bool { - // Reset any potential simple key on the current flow level. - if !yaml_parser_remove_simple_key(parser) { - return false - } - - // Decrease the flow level. - if !yaml_parser_decrease_flow_level(parser) { - return false - } - - // No simple keys after the indicators ']' and '}'. - parser.simple_key_allowed = false - - // Consume the token. - - start_mark := parser.mark - skip(parser) - end_mark := parser.mark - - // Create the FLOW-SEQUENCE-END of FLOW-MAPPING-END token. - token := yaml_token_t{ - typ: typ, - start_mark: start_mark, - end_mark: end_mark, - } - // Append the token to the queue. - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce the FLOW-ENTRY token. -func yaml_parser_fetch_flow_entry(parser *yaml_parser_t) bool { - // Reset any potential simple keys on the current flow level. - if !yaml_parser_remove_simple_key(parser) { - return false - } - - // Simple keys are allowed after ','. - parser.simple_key_allowed = true - - // Consume the token. - start_mark := parser.mark - skip(parser) - end_mark := parser.mark - - // Create the FLOW-ENTRY token and append it to the queue. - token := yaml_token_t{ - typ: yaml_FLOW_ENTRY_TOKEN, - start_mark: start_mark, - end_mark: end_mark, - } - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce the BLOCK-ENTRY token. -func yaml_parser_fetch_block_entry(parser *yaml_parser_t) bool { - // Check if the scanner is in the block context. - if parser.flow_level == 0 { - // Check if we are allowed to start a new entry. - if !parser.simple_key_allowed { - return yaml_parser_set_scanner_error(parser, "", parser.mark, - "block sequence entries are not allowed in this context") - } - // Add the BLOCK-SEQUENCE-START token if needed. - if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_SEQUENCE_START_TOKEN, parser.mark) { - return false - } - } else { - // It is an error for the '-' indicator to occur in the flow context, - // but we let the Parser detect and report about it because the Parser - // is able to point to the context. - } - - // Reset any potential simple keys on the current flow level. - if !yaml_parser_remove_simple_key(parser) { - return false - } - - // Simple keys are allowed after '-'. - parser.simple_key_allowed = true - - // Consume the token. - start_mark := parser.mark - skip(parser) - end_mark := parser.mark - - // Create the BLOCK-ENTRY token and append it to the queue. - token := yaml_token_t{ - typ: yaml_BLOCK_ENTRY_TOKEN, - start_mark: start_mark, - end_mark: end_mark, - } - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce the KEY token. -func yaml_parser_fetch_key(parser *yaml_parser_t) bool { - - // In the block context, additional checks are required. - if parser.flow_level == 0 { - // Check if we are allowed to start a new key (not nessesary simple). - if !parser.simple_key_allowed { - return yaml_parser_set_scanner_error(parser, "", parser.mark, - "mapping keys are not allowed in this context") - } - // Add the BLOCK-MAPPING-START token if needed. - if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { - return false - } - } - - // Reset any potential simple keys on the current flow level. - if !yaml_parser_remove_simple_key(parser) { - return false - } - - // Simple keys are allowed after '?' in the block context. - parser.simple_key_allowed = parser.flow_level == 0 - - // Consume the token. - start_mark := parser.mark - skip(parser) - end_mark := parser.mark - - // Create the KEY token and append it to the queue. - token := yaml_token_t{ - typ: yaml_KEY_TOKEN, - start_mark: start_mark, - end_mark: end_mark, - } - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce the VALUE token. -func yaml_parser_fetch_value(parser *yaml_parser_t) bool { - - simple_key := &parser.simple_keys[len(parser.simple_keys)-1] - - // Have we found a simple key? - if valid, ok := yaml_simple_key_is_valid(parser, simple_key); !ok { - return false - - } else if valid { - - // Create the KEY token and insert it into the queue. - token := yaml_token_t{ - typ: yaml_KEY_TOKEN, - start_mark: simple_key.mark, - end_mark: simple_key.mark, - } - yaml_insert_token(parser, simple_key.token_number-parser.tokens_parsed, &token) - - // In the block context, we may need to add the BLOCK-MAPPING-START token. - if !yaml_parser_roll_indent(parser, simple_key.mark.column, - simple_key.token_number, - yaml_BLOCK_MAPPING_START_TOKEN, simple_key.mark) { - return false - } - - // Remove the simple key. - simple_key.possible = false - delete(parser.simple_keys_by_tok, simple_key.token_number) - - // A simple key cannot follow another simple key. - parser.simple_key_allowed = false - - } else { - // The ':' indicator follows a complex key. - - // In the block context, extra checks are required. - if parser.flow_level == 0 { - - // Check if we are allowed to start a complex value. - if !parser.simple_key_allowed { - return yaml_parser_set_scanner_error(parser, "", parser.mark, - "mapping values are not allowed in this context") - } - - // Add the BLOCK-MAPPING-START token if needed. - if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { - return false - } - } - - // Simple keys after ':' are allowed in the block context. - parser.simple_key_allowed = parser.flow_level == 0 - } - - // Consume the token. - start_mark := parser.mark - skip(parser) - end_mark := parser.mark - - // Create the VALUE token and append it to the queue. - token := yaml_token_t{ - typ: yaml_VALUE_TOKEN, - start_mark: start_mark, - end_mark: end_mark, - } - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce the ALIAS or ANCHOR token. -func yaml_parser_fetch_anchor(parser *yaml_parser_t, typ yaml_token_type_t) bool { - // An anchor or an alias could be a simple key. - if !yaml_parser_save_simple_key(parser) { - return false - } - - // A simple key cannot follow an anchor or an alias. - parser.simple_key_allowed = false - - // Create the ALIAS or ANCHOR token and append it to the queue. - var token yaml_token_t - if !yaml_parser_scan_anchor(parser, &token, typ) { - return false - } - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce the TAG token. -func yaml_parser_fetch_tag(parser *yaml_parser_t) bool { - // A tag could be a simple key. - if !yaml_parser_save_simple_key(parser) { - return false - } - - // A simple key cannot follow a tag. - parser.simple_key_allowed = false - - // Create the TAG token and append it to the queue. - var token yaml_token_t - if !yaml_parser_scan_tag(parser, &token) { - return false - } - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce the SCALAR(...,literal) or SCALAR(...,folded) tokens. -func yaml_parser_fetch_block_scalar(parser *yaml_parser_t, literal bool) bool { - // Remove any potential simple keys. - if !yaml_parser_remove_simple_key(parser) { - return false - } - - // A simple key may follow a block scalar. - parser.simple_key_allowed = true - - // Create the SCALAR token and append it to the queue. - var token yaml_token_t - if !yaml_parser_scan_block_scalar(parser, &token, literal) { - return false - } - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce the SCALAR(...,single-quoted) or SCALAR(...,double-quoted) tokens. -func yaml_parser_fetch_flow_scalar(parser *yaml_parser_t, single bool) bool { - // A plain scalar could be a simple key. - if !yaml_parser_save_simple_key(parser) { - return false - } - - // A simple key cannot follow a flow scalar. - parser.simple_key_allowed = false - - // Create the SCALAR token and append it to the queue. - var token yaml_token_t - if !yaml_parser_scan_flow_scalar(parser, &token, single) { - return false - } - yaml_insert_token(parser, -1, &token) - return true -} - -// Produce the SCALAR(...,plain) token. -func yaml_parser_fetch_plain_scalar(parser *yaml_parser_t) bool { - // A plain scalar could be a simple key. - if !yaml_parser_save_simple_key(parser) { - return false - } - - // A simple key cannot follow a flow scalar. - parser.simple_key_allowed = false - - // Create the SCALAR token and append it to the queue. - var token yaml_token_t - if !yaml_parser_scan_plain_scalar(parser, &token) { - return false - } - yaml_insert_token(parser, -1, &token) - return true -} - -// Eat whitespaces and comments until the next token is found. -func yaml_parser_scan_to_next_token(parser *yaml_parser_t) bool { - - // Until the next token is not found. - for { - // Allow the BOM mark to start a line. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - if parser.mark.column == 0 && is_bom(parser.buffer, parser.buffer_pos) { - skip(parser) - } - - // Eat whitespaces. - // Tabs are allowed: - // - in the flow context - // - in the block context, but not at the beginning of the line or - // after '-', '?', or ':' (complex value). - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - - for parser.buffer[parser.buffer_pos] == ' ' || ((parser.flow_level > 0 || !parser.simple_key_allowed) && parser.buffer[parser.buffer_pos] == '\t') { - skip(parser) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - - // Eat a comment until a line break. - if parser.buffer[parser.buffer_pos] == '#' { - for !is_breakz(parser.buffer, parser.buffer_pos) { - skip(parser) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - } - - // If it is a line break, eat it. - if is_break(parser.buffer, parser.buffer_pos) { - if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { - return false - } - skip_line(parser) - - // In the block context, a new line may start a simple key. - if parser.flow_level == 0 { - parser.simple_key_allowed = true - } - } else { - break // We have found a token. - } - } - - return true -} - -// Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token. -// -// Scope: -// %YAML 1.1 # a comment \n -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -// %TAG !yaml! tag:yaml.org,2002: \n -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -// -func yaml_parser_scan_directive(parser *yaml_parser_t, token *yaml_token_t) bool { - // Eat '%'. - start_mark := parser.mark - skip(parser) - - // Scan the directive name. - var name []byte - if !yaml_parser_scan_directive_name(parser, start_mark, &name) { - return false - } - - // Is it a YAML directive? - if bytes.Equal(name, []byte("YAML")) { - // Scan the VERSION directive value. - var major, minor int8 - if !yaml_parser_scan_version_directive_value(parser, start_mark, &major, &minor) { - return false - } - end_mark := parser.mark - - // Create a VERSION-DIRECTIVE token. - *token = yaml_token_t{ - typ: yaml_VERSION_DIRECTIVE_TOKEN, - start_mark: start_mark, - end_mark: end_mark, - major: major, - minor: minor, - } - - // Is it a TAG directive? - } else if bytes.Equal(name, []byte("TAG")) { - // Scan the TAG directive value. - var handle, prefix []byte - if !yaml_parser_scan_tag_directive_value(parser, start_mark, &handle, &prefix) { - return false - } - end_mark := parser.mark - - // Create a TAG-DIRECTIVE token. - *token = yaml_token_t{ - typ: yaml_TAG_DIRECTIVE_TOKEN, - start_mark: start_mark, - end_mark: end_mark, - value: handle, - prefix: prefix, - } - - // Unknown directive. - } else { - yaml_parser_set_scanner_error(parser, "while scanning a directive", - start_mark, "found unknown directive name") - return false - } - - // Eat the rest of the line including any comments. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - - for is_blank(parser.buffer, parser.buffer_pos) { - skip(parser) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - - if parser.buffer[parser.buffer_pos] == '#' { - for !is_breakz(parser.buffer, parser.buffer_pos) { - skip(parser) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - } - - // Check if we are at the end of the line. - if !is_breakz(parser.buffer, parser.buffer_pos) { - yaml_parser_set_scanner_error(parser, "while scanning a directive", - start_mark, "did not find expected comment or line break") - return false - } - - // Eat a line break. - if is_break(parser.buffer, parser.buffer_pos) { - if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { - return false - } - skip_line(parser) - } - - return true -} - -// Scan the directive name. -// -// Scope: -// %YAML 1.1 # a comment \n -// ^^^^ -// %TAG !yaml! tag:yaml.org,2002: \n -// ^^^ -// -func yaml_parser_scan_directive_name(parser *yaml_parser_t, start_mark yaml_mark_t, name *[]byte) bool { - // Consume the directive name. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - - var s []byte - for is_alpha(parser.buffer, parser.buffer_pos) { - s = read(parser, s) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - - // Check if the name is empty. - if len(s) == 0 { - yaml_parser_set_scanner_error(parser, "while scanning a directive", - start_mark, "could not find expected directive name") - return false - } - - // Check for an blank character after the name. - if !is_blankz(parser.buffer, parser.buffer_pos) { - yaml_parser_set_scanner_error(parser, "while scanning a directive", - start_mark, "found unexpected non-alphabetical character") - return false - } - *name = s - return true -} - -// Scan the value of VERSION-DIRECTIVE. -// -// Scope: -// %YAML 1.1 # a comment \n -// ^^^^^^ -func yaml_parser_scan_version_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, major, minor *int8) bool { - // Eat whitespaces. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - for is_blank(parser.buffer, parser.buffer_pos) { - skip(parser) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - - // Consume the major version number. - if !yaml_parser_scan_version_directive_number(parser, start_mark, major) { - return false - } - - // Eat '.'. - if parser.buffer[parser.buffer_pos] != '.' { - return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", - start_mark, "did not find expected digit or '.' character") - } - - skip(parser) - - // Consume the minor version number. - if !yaml_parser_scan_version_directive_number(parser, start_mark, minor) { - return false - } - return true -} - -const max_number_length = 2 - -// Scan the version number of VERSION-DIRECTIVE. -// -// Scope: -// %YAML 1.1 # a comment \n -// ^ -// %YAML 1.1 # a comment \n -// ^ -func yaml_parser_scan_version_directive_number(parser *yaml_parser_t, start_mark yaml_mark_t, number *int8) bool { - - // Repeat while the next character is digit. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - var value, length int8 - for is_digit(parser.buffer, parser.buffer_pos) { - // Check if the number is too long. - length++ - if length > max_number_length { - return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", - start_mark, "found extremely long version number") - } - value = value*10 + int8(as_digit(parser.buffer, parser.buffer_pos)) - skip(parser) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - - // Check if the number was present. - if length == 0 { - return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", - start_mark, "did not find expected version number") - } - *number = value - return true -} - -// Scan the value of a TAG-DIRECTIVE token. -// -// Scope: -// %TAG !yaml! tag:yaml.org,2002: \n -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -// -func yaml_parser_scan_tag_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, handle, prefix *[]byte) bool { - var handle_value, prefix_value []byte - - // Eat whitespaces. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - - for is_blank(parser.buffer, parser.buffer_pos) { - skip(parser) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - - // Scan a handle. - if !yaml_parser_scan_tag_handle(parser, true, start_mark, &handle_value) { - return false - } - - // Expect a whitespace. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - if !is_blank(parser.buffer, parser.buffer_pos) { - yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", - start_mark, "did not find expected whitespace") - return false - } - - // Eat whitespaces. - for is_blank(parser.buffer, parser.buffer_pos) { - skip(parser) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - - // Scan a prefix. - if !yaml_parser_scan_tag_uri(parser, true, nil, start_mark, &prefix_value) { - return false - } - - // Expect a whitespace or line break. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - if !is_blankz(parser.buffer, parser.buffer_pos) { - yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", - start_mark, "did not find expected whitespace or line break") - return false - } - - *handle = handle_value - *prefix = prefix_value - return true -} - -func yaml_parser_scan_anchor(parser *yaml_parser_t, token *yaml_token_t, typ yaml_token_type_t) bool { - var s []byte - - // Eat the indicator character. - start_mark := parser.mark - skip(parser) - - // Consume the value. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - - for is_alpha(parser.buffer, parser.buffer_pos) { - s = read(parser, s) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - - end_mark := parser.mark - - /* - * Check if length of the anchor is greater than 0 and it is followed by - * a whitespace character or one of the indicators: - * - * '?', ':', ',', ']', '}', '%', '@', '`'. - */ - - if len(s) == 0 || - !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '?' || - parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == ',' || - parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '}' || - parser.buffer[parser.buffer_pos] == '%' || parser.buffer[parser.buffer_pos] == '@' || - parser.buffer[parser.buffer_pos] == '`') { - context := "while scanning an alias" - if typ == yaml_ANCHOR_TOKEN { - context = "while scanning an anchor" - } - yaml_parser_set_scanner_error(parser, context, start_mark, - "did not find expected alphabetic or numeric character") - return false - } - - // Create a token. - *token = yaml_token_t{ - typ: typ, - start_mark: start_mark, - end_mark: end_mark, - value: s, - } - - return true -} - -/* - * Scan a TAG token. - */ - -func yaml_parser_scan_tag(parser *yaml_parser_t, token *yaml_token_t) bool { - var handle, suffix []byte - - start_mark := parser.mark - - // Check if the tag is in the canonical form. - if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { - return false - } - - if parser.buffer[parser.buffer_pos+1] == '<' { - // Keep the handle as '' - - // Eat '!<' - skip(parser) - skip(parser) - - // Consume the tag value. - if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { - return false - } - - // Check for '>' and eat it. - if parser.buffer[parser.buffer_pos] != '>' { - yaml_parser_set_scanner_error(parser, "while scanning a tag", - start_mark, "did not find the expected '>'") - return false - } - - skip(parser) - } else { - // The tag has either the '!suffix' or the '!handle!suffix' form. - - // First, try to scan a handle. - if !yaml_parser_scan_tag_handle(parser, false, start_mark, &handle) { - return false - } - - // Check if it is, indeed, handle. - if handle[0] == '!' && len(handle) > 1 && handle[len(handle)-1] == '!' { - // Scan the suffix now. - if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { - return false - } - } else { - // It wasn't a handle after all. Scan the rest of the tag. - if !yaml_parser_scan_tag_uri(parser, false, handle, start_mark, &suffix) { - return false - } - - // Set the handle to '!'. - handle = []byte{'!'} - - // A special case: the '!' tag. Set the handle to '' and the - // suffix to '!'. - if len(suffix) == 0 { - handle, suffix = suffix, handle - } - } - } - - // Check the character which ends the tag. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - if !is_blankz(parser.buffer, parser.buffer_pos) { - yaml_parser_set_scanner_error(parser, "while scanning a tag", - start_mark, "did not find expected whitespace or line break") - return false - } - - end_mark := parser.mark - - // Create a token. - *token = yaml_token_t{ - typ: yaml_TAG_TOKEN, - start_mark: start_mark, - end_mark: end_mark, - value: handle, - suffix: suffix, - } - return true -} - -// Scan a tag handle. -func yaml_parser_scan_tag_handle(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, handle *[]byte) bool { - // Check the initial '!' character. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - if parser.buffer[parser.buffer_pos] != '!' { - yaml_parser_set_scanner_tag_error(parser, directive, - start_mark, "did not find expected '!'") - return false - } - - var s []byte - - // Copy the '!' character. - s = read(parser, s) - - // Copy all subsequent alphabetical and numerical characters. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - for is_alpha(parser.buffer, parser.buffer_pos) { - s = read(parser, s) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - - // Check if the trailing character is '!' and copy it. - if parser.buffer[parser.buffer_pos] == '!' { - s = read(parser, s) - } else { - // It's either the '!' tag or not really a tag handle. If it's a %TAG - // directive, it's an error. If it's a tag token, it must be a part of URI. - if directive && string(s) != "!" { - yaml_parser_set_scanner_tag_error(parser, directive, - start_mark, "did not find expected '!'") - return false - } - } - - *handle = s - return true -} - -// Scan a tag. -func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte, start_mark yaml_mark_t, uri *[]byte) bool { - //size_t length = head ? strlen((char *)head) : 0 - var s []byte - hasTag := len(head) > 0 - - // Copy the head if needed. - // - // Note that we don't copy the leading '!' character. - if len(head) > 1 { - s = append(s, head[1:]...) - } - - // Scan the tag. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - - // The set of characters that may appear in URI is as follows: - // - // '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&', - // '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']', - // '%'. - // [Go] Convert this into more reasonable logic. - for is_alpha(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == ';' || - parser.buffer[parser.buffer_pos] == '/' || parser.buffer[parser.buffer_pos] == '?' || - parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == '@' || - parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '=' || - parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '$' || - parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '.' || - parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '~' || - parser.buffer[parser.buffer_pos] == '*' || parser.buffer[parser.buffer_pos] == '\'' || - parser.buffer[parser.buffer_pos] == '(' || parser.buffer[parser.buffer_pos] == ')' || - parser.buffer[parser.buffer_pos] == '[' || parser.buffer[parser.buffer_pos] == ']' || - parser.buffer[parser.buffer_pos] == '%' { - // Check if it is a URI-escape sequence. - if parser.buffer[parser.buffer_pos] == '%' { - if !yaml_parser_scan_uri_escapes(parser, directive, start_mark, &s) { - return false - } - } else { - s = read(parser, s) - } - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - hasTag = true - } - - if !hasTag { - yaml_parser_set_scanner_tag_error(parser, directive, - start_mark, "did not find expected tag URI") - return false - } - *uri = s - return true -} - -// Decode an URI-escape sequence corresponding to a single UTF-8 character. -func yaml_parser_scan_uri_escapes(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, s *[]byte) bool { - - // Decode the required number of characters. - w := 1024 - for w > 0 { - // Check for a URI-escaped octet. - if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { - return false - } - - if !(parser.buffer[parser.buffer_pos] == '%' && - is_hex(parser.buffer, parser.buffer_pos+1) && - is_hex(parser.buffer, parser.buffer_pos+2)) { - return yaml_parser_set_scanner_tag_error(parser, directive, - start_mark, "did not find URI escaped octet") - } - - // Get the octet. - octet := byte((as_hex(parser.buffer, parser.buffer_pos+1) << 4) + as_hex(parser.buffer, parser.buffer_pos+2)) - - // If it is the leading octet, determine the length of the UTF-8 sequence. - if w == 1024 { - w = width(octet) - if w == 0 { - return yaml_parser_set_scanner_tag_error(parser, directive, - start_mark, "found an incorrect leading UTF-8 octet") - } - } else { - // Check if the trailing octet is correct. - if octet&0xC0 != 0x80 { - return yaml_parser_set_scanner_tag_error(parser, directive, - start_mark, "found an incorrect trailing UTF-8 octet") - } - } - - // Copy the octet and move the pointers. - *s = append(*s, octet) - skip(parser) - skip(parser) - skip(parser) - w-- - } - return true -} - -// Scan a block scalar. -func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, literal bool) bool { - // Eat the indicator '|' or '>'. - start_mark := parser.mark - skip(parser) - - // Scan the additional block scalar indicators. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - - // Check for a chomping indicator. - var chomping, increment int - if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { - // Set the chomping method and eat the indicator. - if parser.buffer[parser.buffer_pos] == '+' { - chomping = +1 - } else { - chomping = -1 - } - skip(parser) - - // Check for an indentation indicator. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - if is_digit(parser.buffer, parser.buffer_pos) { - // Check that the indentation is greater than 0. - if parser.buffer[parser.buffer_pos] == '0' { - yaml_parser_set_scanner_error(parser, "while scanning a block scalar", - start_mark, "found an indentation indicator equal to 0") - return false - } - - // Get the indentation level and eat the indicator. - increment = as_digit(parser.buffer, parser.buffer_pos) - skip(parser) - } - - } else if is_digit(parser.buffer, parser.buffer_pos) { - // Do the same as above, but in the opposite order. - - if parser.buffer[parser.buffer_pos] == '0' { - yaml_parser_set_scanner_error(parser, "while scanning a block scalar", - start_mark, "found an indentation indicator equal to 0") - return false - } - increment = as_digit(parser.buffer, parser.buffer_pos) - skip(parser) - - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { - if parser.buffer[parser.buffer_pos] == '+' { - chomping = +1 - } else { - chomping = -1 - } - skip(parser) - } - } - - // Eat whitespaces and comments to the end of the line. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - for is_blank(parser.buffer, parser.buffer_pos) { - skip(parser) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - if parser.buffer[parser.buffer_pos] == '#' { - for !is_breakz(parser.buffer, parser.buffer_pos) { - skip(parser) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - } - - // Check if we are at the end of the line. - if !is_breakz(parser.buffer, parser.buffer_pos) { - yaml_parser_set_scanner_error(parser, "while scanning a block scalar", - start_mark, "did not find expected comment or line break") - return false - } - - // Eat a line break. - if is_break(parser.buffer, parser.buffer_pos) { - if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { - return false - } - skip_line(parser) - } - - end_mark := parser.mark - - // Set the indentation level if it was specified. - var indent int - if increment > 0 { - if parser.indent >= 0 { - indent = parser.indent + increment - } else { - indent = increment - } - } - - // Scan the leading line breaks and determine the indentation level if needed. - var s, leading_break, trailing_breaks []byte - if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { - return false - } - - // Scan the block scalar content. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - var leading_blank, trailing_blank bool - for parser.mark.column == indent && !is_z(parser.buffer, parser.buffer_pos) { - // We are at the beginning of a non-empty line. - - // Is it a trailing whitespace? - trailing_blank = is_blank(parser.buffer, parser.buffer_pos) - - // Check if we need to fold the leading line break. - if !literal && !leading_blank && !trailing_blank && len(leading_break) > 0 && leading_break[0] == '\n' { - // Do we need to join the lines by space? - if len(trailing_breaks) == 0 { - s = append(s, ' ') - } - } else { - s = append(s, leading_break...) - } - leading_break = leading_break[:0] - - // Append the remaining line breaks. - s = append(s, trailing_breaks...) - trailing_breaks = trailing_breaks[:0] - - // Is it a leading whitespace? - leading_blank = is_blank(parser.buffer, parser.buffer_pos) - - // Consume the current line. - for !is_breakz(parser.buffer, parser.buffer_pos) { - s = read(parser, s) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - - // Consume the line break. - if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { - return false - } - - leading_break = read_line(parser, leading_break) - - // Eat the following indentation spaces and line breaks. - if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { - return false - } - } - - // Chomp the tail. - if chomping != -1 { - s = append(s, leading_break...) - } - if chomping == 1 { - s = append(s, trailing_breaks...) - } - - // Create a token. - *token = yaml_token_t{ - typ: yaml_SCALAR_TOKEN, - start_mark: start_mark, - end_mark: end_mark, - value: s, - style: yaml_LITERAL_SCALAR_STYLE, - } - if !literal { - token.style = yaml_FOLDED_SCALAR_STYLE - } - return true -} - -// Scan indentation spaces and line breaks for a block scalar. Determine the -// indentation level if needed. -func yaml_parser_scan_block_scalar_breaks(parser *yaml_parser_t, indent *int, breaks *[]byte, start_mark yaml_mark_t, end_mark *yaml_mark_t) bool { - *end_mark = parser.mark - - // Eat the indentation spaces and line breaks. - max_indent := 0 - for { - // Eat the indentation spaces. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - for (*indent == 0 || parser.mark.column < *indent) && is_space(parser.buffer, parser.buffer_pos) { - skip(parser) - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - if parser.mark.column > max_indent { - max_indent = parser.mark.column - } - - // Check for a tab character messing the indentation. - if (*indent == 0 || parser.mark.column < *indent) && is_tab(parser.buffer, parser.buffer_pos) { - return yaml_parser_set_scanner_error(parser, "while scanning a block scalar", - start_mark, "found a tab character where an indentation space is expected") - } - - // Have we found a non-empty line? - if !is_break(parser.buffer, parser.buffer_pos) { - break - } - - // Consume the line break. - if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { - return false - } - // [Go] Should really be returning breaks instead. - *breaks = read_line(parser, *breaks) - *end_mark = parser.mark - } - - // Determine the indentation level if needed. - if *indent == 0 { - *indent = max_indent - if *indent < parser.indent+1 { - *indent = parser.indent + 1 - } - if *indent < 1 { - *indent = 1 - } - } - return true -} - -// Scan a quoted scalar. -func yaml_parser_scan_flow_scalar(parser *yaml_parser_t, token *yaml_token_t, single bool) bool { - // Eat the left quote. - start_mark := parser.mark - skip(parser) - - // Consume the content of the quoted scalar. - var s, leading_break, trailing_breaks, whitespaces []byte - for { - // Check that there are no document indicators at the beginning of the line. - if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { - return false - } - - if parser.mark.column == 0 && - ((parser.buffer[parser.buffer_pos+0] == '-' && - parser.buffer[parser.buffer_pos+1] == '-' && - parser.buffer[parser.buffer_pos+2] == '-') || - (parser.buffer[parser.buffer_pos+0] == '.' && - parser.buffer[parser.buffer_pos+1] == '.' && - parser.buffer[parser.buffer_pos+2] == '.')) && - is_blankz(parser.buffer, parser.buffer_pos+3) { - yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", - start_mark, "found unexpected document indicator") - return false - } - - // Check for EOF. - if is_z(parser.buffer, parser.buffer_pos) { - yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", - start_mark, "found unexpected end of stream") - return false - } - - // Consume non-blank characters. - leading_blanks := false - for !is_blankz(parser.buffer, parser.buffer_pos) { - if single && parser.buffer[parser.buffer_pos] == '\'' && parser.buffer[parser.buffer_pos+1] == '\'' { - // Is is an escaped single quote. - s = append(s, '\'') - skip(parser) - skip(parser) - - } else if single && parser.buffer[parser.buffer_pos] == '\'' { - // It is a right single quote. - break - } else if !single && parser.buffer[parser.buffer_pos] == '"' { - // It is a right double quote. - break - - } else if !single && parser.buffer[parser.buffer_pos] == '\\' && is_break(parser.buffer, parser.buffer_pos+1) { - // It is an escaped line break. - if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { - return false - } - skip(parser) - skip_line(parser) - leading_blanks = true - break - - } else if !single && parser.buffer[parser.buffer_pos] == '\\' { - // It is an escape sequence. - code_length := 0 - - // Check the escape character. - switch parser.buffer[parser.buffer_pos+1] { - case '0': - s = append(s, 0) - case 'a': - s = append(s, '\x07') - case 'b': - s = append(s, '\x08') - case 't', '\t': - s = append(s, '\x09') - case 'n': - s = append(s, '\x0A') - case 'v': - s = append(s, '\x0B') - case 'f': - s = append(s, '\x0C') - case 'r': - s = append(s, '\x0D') - case 'e': - s = append(s, '\x1B') - case ' ': - s = append(s, '\x20') - case '"': - s = append(s, '"') - case '\'': - s = append(s, '\'') - case '\\': - s = append(s, '\\') - case 'N': // NEL (#x85) - s = append(s, '\xC2') - s = append(s, '\x85') - case '_': // #xA0 - s = append(s, '\xC2') - s = append(s, '\xA0') - case 'L': // LS (#x2028) - s = append(s, '\xE2') - s = append(s, '\x80') - s = append(s, '\xA8') - case 'P': // PS (#x2029) - s = append(s, '\xE2') - s = append(s, '\x80') - s = append(s, '\xA9') - case 'x': - code_length = 2 - case 'u': - code_length = 4 - case 'U': - code_length = 8 - default: - yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", - start_mark, "found unknown escape character") - return false - } - - skip(parser) - skip(parser) - - // Consume an arbitrary escape code. - if code_length > 0 { - var value int - - // Scan the character value. - if parser.unread < code_length && !yaml_parser_update_buffer(parser, code_length) { - return false - } - for k := 0; k < code_length; k++ { - if !is_hex(parser.buffer, parser.buffer_pos+k) { - yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", - start_mark, "did not find expected hexdecimal number") - return false - } - value = (value << 4) + as_hex(parser.buffer, parser.buffer_pos+k) - } - - // Check the value and write the character. - if (value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF { - yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", - start_mark, "found invalid Unicode character escape code") - return false - } - if value <= 0x7F { - s = append(s, byte(value)) - } else if value <= 0x7FF { - s = append(s, byte(0xC0+(value>>6))) - s = append(s, byte(0x80+(value&0x3F))) - } else if value <= 0xFFFF { - s = append(s, byte(0xE0+(value>>12))) - s = append(s, byte(0x80+((value>>6)&0x3F))) - s = append(s, byte(0x80+(value&0x3F))) - } else { - s = append(s, byte(0xF0+(value>>18))) - s = append(s, byte(0x80+((value>>12)&0x3F))) - s = append(s, byte(0x80+((value>>6)&0x3F))) - s = append(s, byte(0x80+(value&0x3F))) - } - - // Advance the pointer. - for k := 0; k < code_length; k++ { - skip(parser) - } - } - } else { - // It is a non-escaped non-blank character. - s = read(parser, s) - } - if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { - return false - } - } - - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - - // Check if we are at the end of the scalar. - if single { - if parser.buffer[parser.buffer_pos] == '\'' { - break - } - } else { - if parser.buffer[parser.buffer_pos] == '"' { - break - } - } - - // Consume blank characters. - for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { - if is_blank(parser.buffer, parser.buffer_pos) { - // Consume a space or a tab character. - if !leading_blanks { - whitespaces = read(parser, whitespaces) - } else { - skip(parser) - } - } else { - if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { - return false - } - - // Check if it is a first line break. - if !leading_blanks { - whitespaces = whitespaces[:0] - leading_break = read_line(parser, leading_break) - leading_blanks = true - } else { - trailing_breaks = read_line(parser, trailing_breaks) - } - } - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - - // Join the whitespaces or fold line breaks. - if leading_blanks { - // Do we need to fold line breaks? - if len(leading_break) > 0 && leading_break[0] == '\n' { - if len(trailing_breaks) == 0 { - s = append(s, ' ') - } else { - s = append(s, trailing_breaks...) - } - } else { - s = append(s, leading_break...) - s = append(s, trailing_breaks...) - } - trailing_breaks = trailing_breaks[:0] - leading_break = leading_break[:0] - } else { - s = append(s, whitespaces...) - whitespaces = whitespaces[:0] - } - } - - // Eat the right quote. - skip(parser) - end_mark := parser.mark - - // Create a token. - *token = yaml_token_t{ - typ: yaml_SCALAR_TOKEN, - start_mark: start_mark, - end_mark: end_mark, - value: s, - style: yaml_SINGLE_QUOTED_SCALAR_STYLE, - } - if !single { - token.style = yaml_DOUBLE_QUOTED_SCALAR_STYLE - } - return true -} - -// Scan a plain scalar. -func yaml_parser_scan_plain_scalar(parser *yaml_parser_t, token *yaml_token_t) bool { - - var s, leading_break, trailing_breaks, whitespaces []byte - var leading_blanks bool - var indent = parser.indent + 1 - - start_mark := parser.mark - end_mark := parser.mark - - // Consume the content of the plain scalar. - for { - // Check for a document indicator. - if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { - return false - } - if parser.mark.column == 0 && - ((parser.buffer[parser.buffer_pos+0] == '-' && - parser.buffer[parser.buffer_pos+1] == '-' && - parser.buffer[parser.buffer_pos+2] == '-') || - (parser.buffer[parser.buffer_pos+0] == '.' && - parser.buffer[parser.buffer_pos+1] == '.' && - parser.buffer[parser.buffer_pos+2] == '.')) && - is_blankz(parser.buffer, parser.buffer_pos+3) { - break - } - - // Check for a comment. - if parser.buffer[parser.buffer_pos] == '#' { - break - } - - // Consume non-blank characters. - for !is_blankz(parser.buffer, parser.buffer_pos) { - - // Check for indicators that may end a plain scalar. - if (parser.buffer[parser.buffer_pos] == ':' && is_blankz(parser.buffer, parser.buffer_pos+1)) || - (parser.flow_level > 0 && - (parser.buffer[parser.buffer_pos] == ',' || - parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == '[' || - parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || - parser.buffer[parser.buffer_pos] == '}')) { - break - } - - // Check if we need to join whitespaces and breaks. - if leading_blanks || len(whitespaces) > 0 { - if leading_blanks { - // Do we need to fold line breaks? - if leading_break[0] == '\n' { - if len(trailing_breaks) == 0 { - s = append(s, ' ') - } else { - s = append(s, trailing_breaks...) - } - } else { - s = append(s, leading_break...) - s = append(s, trailing_breaks...) - } - trailing_breaks = trailing_breaks[:0] - leading_break = leading_break[:0] - leading_blanks = false - } else { - s = append(s, whitespaces...) - whitespaces = whitespaces[:0] - } - } - - // Copy the character. - s = read(parser, s) - - end_mark = parser.mark - if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { - return false - } - } - - // Is it the end? - if !(is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos)) { - break - } - - // Consume blank characters. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - - for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { - if is_blank(parser.buffer, parser.buffer_pos) { - - // Check for tab characters that abuse indentation. - if leading_blanks && parser.mark.column < indent && is_tab(parser.buffer, parser.buffer_pos) { - yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", - start_mark, "found a tab character that violates indentation") - return false - } - - // Consume a space or a tab character. - if !leading_blanks { - whitespaces = read(parser, whitespaces) - } else { - skip(parser) - } - } else { - if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { - return false - } - - // Check if it is a first line break. - if !leading_blanks { - whitespaces = whitespaces[:0] - leading_break = read_line(parser, leading_break) - leading_blanks = true - } else { - trailing_breaks = read_line(parser, trailing_breaks) - } - } - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - } - - // Check indentation level. - if parser.flow_level == 0 && parser.mark.column < indent { - break - } - } - - // Create a token. - *token = yaml_token_t{ - typ: yaml_SCALAR_TOKEN, - start_mark: start_mark, - end_mark: end_mark, - value: s, - style: yaml_PLAIN_SCALAR_STYLE, - } - - // Note that we change the 'simple_key_allowed' flag. - if leading_blanks { - parser.simple_key_allowed = true - } - return true -} diff --git a/vendor/go.yaml.in/yaml/v2/sorter.go b/vendor/go.yaml.in/yaml/v2/sorter.go deleted file mode 100644 index 4c45e660a8..0000000000 --- a/vendor/go.yaml.in/yaml/v2/sorter.go +++ /dev/null @@ -1,113 +0,0 @@ -package yaml - -import ( - "reflect" - "unicode" -) - -type keyList []reflect.Value - -func (l keyList) Len() int { return len(l) } -func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } -func (l keyList) Less(i, j int) bool { - a := l[i] - b := l[j] - ak := a.Kind() - bk := b.Kind() - for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() { - a = a.Elem() - ak = a.Kind() - } - for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() { - b = b.Elem() - bk = b.Kind() - } - af, aok := keyFloat(a) - bf, bok := keyFloat(b) - if aok && bok { - if af != bf { - return af < bf - } - if ak != bk { - return ak < bk - } - return numLess(a, b) - } - if ak != reflect.String || bk != reflect.String { - return ak < bk - } - ar, br := []rune(a.String()), []rune(b.String()) - for i := 0; i < len(ar) && i < len(br); i++ { - if ar[i] == br[i] { - continue - } - al := unicode.IsLetter(ar[i]) - bl := unicode.IsLetter(br[i]) - if al && bl { - return ar[i] < br[i] - } - if al || bl { - return bl - } - var ai, bi int - var an, bn int64 - if ar[i] == '0' || br[i] == '0' { - for j := i-1; j >= 0 && unicode.IsDigit(ar[j]); j-- { - if ar[j] != '0' { - an = 1 - bn = 1 - break - } - } - } - for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ { - an = an*10 + int64(ar[ai]-'0') - } - for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ { - bn = bn*10 + int64(br[bi]-'0') - } - if an != bn { - return an < bn - } - if ai != bi { - return ai < bi - } - return ar[i] < br[i] - } - return len(ar) < len(br) -} - -// keyFloat returns a float value for v if it is a number/bool -// and whether it is a number/bool or not. -func keyFloat(v reflect.Value) (f float64, ok bool) { - switch v.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return float64(v.Int()), true - case reflect.Float32, reflect.Float64: - return v.Float(), true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return float64(v.Uint()), true - case reflect.Bool: - if v.Bool() { - return 1, true - } - return 0, true - } - return 0, false -} - -// numLess returns whether a < b. -// a and b must necessarily have the same kind. -func numLess(a, b reflect.Value) bool { - switch a.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return a.Int() < b.Int() - case reflect.Float32, reflect.Float64: - return a.Float() < b.Float() - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return a.Uint() < b.Uint() - case reflect.Bool: - return !a.Bool() && b.Bool() - } - panic("not a number") -} diff --git a/vendor/go.yaml.in/yaml/v2/writerc.go b/vendor/go.yaml.in/yaml/v2/writerc.go deleted file mode 100644 index a2dde608cb..0000000000 --- a/vendor/go.yaml.in/yaml/v2/writerc.go +++ /dev/null @@ -1,26 +0,0 @@ -package yaml - -// Set the writer error and return false. -func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool { - emitter.error = yaml_WRITER_ERROR - emitter.problem = problem - return false -} - -// Flush the output buffer. -func yaml_emitter_flush(emitter *yaml_emitter_t) bool { - if emitter.write_handler == nil { - panic("write handler not set") - } - - // Check if the buffer is empty. - if emitter.buffer_pos == 0 { - return true - } - - if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil { - return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error()) - } - emitter.buffer_pos = 0 - return true -} diff --git a/vendor/go.yaml.in/yaml/v2/yaml.go b/vendor/go.yaml.in/yaml/v2/yaml.go deleted file mode 100644 index 5248e1263c..0000000000 --- a/vendor/go.yaml.in/yaml/v2/yaml.go +++ /dev/null @@ -1,478 +0,0 @@ -// Package yaml implements YAML support for the Go language. -// -// Source code and other details for the project are available at GitHub: -// -// https://github.com/yaml/go-yaml -// -package yaml - -import ( - "errors" - "fmt" - "io" - "reflect" - "strings" - "sync" -) - -// MapSlice encodes and decodes as a YAML map. -// The order of keys is preserved when encoding and decoding. -type MapSlice []MapItem - -// MapItem is an item in a MapSlice. -type MapItem struct { - Key, Value interface{} -} - -// The Unmarshaler interface may be implemented by types to customize their -// behavior when being unmarshaled from a YAML document. The UnmarshalYAML -// method receives a function that may be called to unmarshal the original -// YAML value into a field or variable. It is safe to call the unmarshal -// function parameter more than once if necessary. -type Unmarshaler interface { - UnmarshalYAML(unmarshal func(interface{}) error) error -} - -// The Marshaler interface may be implemented by types to customize their -// behavior when being marshaled into a YAML document. The returned value -// is marshaled in place of the original value implementing Marshaler. -// -// If an error is returned by MarshalYAML, the marshaling procedure stops -// and returns with the provided error. -type Marshaler interface { - MarshalYAML() (interface{}, error) -} - -// Unmarshal decodes the first document found within the in byte slice -// and assigns decoded values into the out value. -// -// Maps and pointers (to a struct, string, int, etc) are accepted as out -// values. If an internal pointer within a struct is not initialized, -// the yaml package will initialize it if necessary for unmarshalling -// the provided data. The out parameter must not be nil. -// -// The type of the decoded values should be compatible with the respective -// values in out. If one or more values cannot be decoded due to a type -// mismatches, decoding continues partially until the end of the YAML -// content, and a *yaml.TypeError is returned with details for all -// missed values. -// -// Struct fields are only unmarshalled if they are exported (have an -// upper case first letter), and are unmarshalled using the field name -// lowercased as the default key. Custom keys may be defined via the -// "yaml" name in the field tag: the content preceding the first comma -// is used as the key, and the following comma-separated options are -// used to tweak the marshalling process (see Marshal). -// Conflicting names result in a runtime error. -// -// For example: -// -// type T struct { -// F int `yaml:"a,omitempty"` -// B int -// } -// var t T -// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t) -// -// See the documentation of Marshal for the format of tags and a list of -// supported tag options. -// -func Unmarshal(in []byte, out interface{}) (err error) { - return unmarshal(in, out, false) -} - -// UnmarshalStrict is like Unmarshal except that any fields that are found -// in the data that do not have corresponding struct members, or mapping -// keys that are duplicates, will result in -// an error. -func UnmarshalStrict(in []byte, out interface{}) (err error) { - return unmarshal(in, out, true) -} - -// A Decoder reads and decodes YAML values from an input stream. -type Decoder struct { - strict bool - parser *parser -} - -// NewDecoder returns a new decoder that reads from r. -// -// The decoder introduces its own buffering and may read -// data from r beyond the YAML values requested. -func NewDecoder(r io.Reader) *Decoder { - return &Decoder{ - parser: newParserFromReader(r), - } -} - -// SetStrict sets whether strict decoding behaviour is enabled when -// decoding items in the data (see UnmarshalStrict). By default, decoding is not strict. -func (dec *Decoder) SetStrict(strict bool) { - dec.strict = strict -} - -// Decode reads the next YAML-encoded value from its input -// and stores it in the value pointed to by v. -// -// See the documentation for Unmarshal for details about the -// conversion of YAML into a Go value. -func (dec *Decoder) Decode(v interface{}) (err error) { - d := newDecoder(dec.strict) - defer handleErr(&err) - node := dec.parser.parse() - if node == nil { - return io.EOF - } - out := reflect.ValueOf(v) - if out.Kind() == reflect.Ptr && !out.IsNil() { - out = out.Elem() - } - d.unmarshal(node, out) - if len(d.terrors) > 0 { - return &TypeError{d.terrors} - } - return nil -} - -func unmarshal(in []byte, out interface{}, strict bool) (err error) { - defer handleErr(&err) - d := newDecoder(strict) - p := newParser(in) - defer p.destroy() - node := p.parse() - if node != nil { - v := reflect.ValueOf(out) - if v.Kind() == reflect.Ptr && !v.IsNil() { - v = v.Elem() - } - d.unmarshal(node, v) - } - if len(d.terrors) > 0 { - return &TypeError{d.terrors} - } - return nil -} - -// Marshal serializes the value provided into a YAML document. The structure -// of the generated document will reflect the structure of the value itself. -// Maps and pointers (to struct, string, int, etc) are accepted as the in value. -// -// Struct fields are only marshalled if they are exported (have an upper case -// first letter), and are marshalled using the field name lowercased as the -// default key. Custom keys may be defined via the "yaml" name in the field -// tag: the content preceding the first comma is used as the key, and the -// following comma-separated options are used to tweak the marshalling process. -// Conflicting names result in a runtime error. -// -// The field tag format accepted is: -// -// `(...) yaml:"[][,[,]]" (...)` -// -// The following flags are currently supported: -// -// omitempty Only include the field if it's not set to the zero -// value for the type or to empty slices or maps. -// Zero valued structs will be omitted if all their public -// fields are zero, unless they implement an IsZero -// method (see the IsZeroer interface type), in which -// case the field will be excluded if IsZero returns true. -// -// flow Marshal using a flow style (useful for structs, -// sequences and maps). -// -// inline Inline the field, which must be a struct or a map, -// causing all of its fields or keys to be processed as if -// they were part of the outer struct. For maps, keys must -// not conflict with the yaml keys of other struct fields. -// -// In addition, if the key is "-", the field is ignored. -// -// For example: -// -// type T struct { -// F int `yaml:"a,omitempty"` -// B int -// } -// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n" -// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n" -// -func Marshal(in interface{}) (out []byte, err error) { - defer handleErr(&err) - e := newEncoder() - defer e.destroy() - e.marshalDoc("", reflect.ValueOf(in)) - e.finish() - out = e.out - return -} - -// An Encoder writes YAML values to an output stream. -type Encoder struct { - encoder *encoder -} - -// NewEncoder returns a new encoder that writes to w. -// The Encoder should be closed after use to flush all data -// to w. -func NewEncoder(w io.Writer) *Encoder { - return &Encoder{ - encoder: newEncoderWithWriter(w), - } -} - -// Encode writes the YAML encoding of v to the stream. -// If multiple items are encoded to the stream, the -// second and subsequent document will be preceded -// with a "---" document separator, but the first will not. -// -// See the documentation for Marshal for details about the conversion of Go -// values to YAML. -func (e *Encoder) Encode(v interface{}) (err error) { - defer handleErr(&err) - e.encoder.marshalDoc("", reflect.ValueOf(v)) - return nil -} - -// Close closes the encoder by writing any remaining data. -// It does not write a stream terminating string "...". -func (e *Encoder) Close() (err error) { - defer handleErr(&err) - e.encoder.finish() - return nil -} - -func handleErr(err *error) { - if v := recover(); v != nil { - if e, ok := v.(yamlError); ok { - *err = e.err - } else { - panic(v) - } - } -} - -type yamlError struct { - err error -} - -func fail(err error) { - panic(yamlError{err}) -} - -func failf(format string, args ...interface{}) { - panic(yamlError{fmt.Errorf("yaml: "+format, args...)}) -} - -// A TypeError is returned by Unmarshal when one or more fields in -// the YAML document cannot be properly decoded into the requested -// types. When this error is returned, the value is still -// unmarshaled partially. -type TypeError struct { - Errors []string -} - -func (e *TypeError) Error() string { - return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n ")) -} - -// -------------------------------------------------------------------------- -// Maintain a mapping of keys to structure field indexes - -// The code in this section was copied from mgo/bson. - -// structInfo holds details for the serialization of fields of -// a given struct. -type structInfo struct { - FieldsMap map[string]fieldInfo - FieldsList []fieldInfo - - // InlineMap is the number of the field in the struct that - // contains an ,inline map, or -1 if there's none. - InlineMap int -} - -type fieldInfo struct { - Key string - Num int - OmitEmpty bool - Flow bool - // Id holds the unique field identifier, so we can cheaply - // check for field duplicates without maintaining an extra map. - Id int - - // Inline holds the field index if the field is part of an inlined struct. - Inline []int -} - -var structMap = make(map[reflect.Type]*structInfo) -var fieldMapMutex sync.RWMutex - -func getStructInfo(st reflect.Type) (*structInfo, error) { - fieldMapMutex.RLock() - sinfo, found := structMap[st] - fieldMapMutex.RUnlock() - if found { - return sinfo, nil - } - - n := st.NumField() - fieldsMap := make(map[string]fieldInfo) - fieldsList := make([]fieldInfo, 0, n) - inlineMap := -1 - for i := 0; i != n; i++ { - field := st.Field(i) - if field.PkgPath != "" && !field.Anonymous { - continue // Private field - } - - info := fieldInfo{Num: i} - - tag := field.Tag.Get("yaml") - if tag == "" && strings.Index(string(field.Tag), ":") < 0 { - tag = string(field.Tag) - } - if tag == "-" { - continue - } - - inline := false - fields := strings.Split(tag, ",") - if len(fields) > 1 { - for _, flag := range fields[1:] { - switch flag { - case "omitempty": - info.OmitEmpty = true - case "flow": - info.Flow = true - case "inline": - inline = true - default: - return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)) - } - } - tag = fields[0] - } - - if inline { - switch field.Type.Kind() { - case reflect.Map: - if inlineMap >= 0 { - return nil, errors.New("Multiple ,inline maps in struct " + st.String()) - } - if field.Type.Key() != reflect.TypeOf("") { - return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String()) - } - inlineMap = info.Num - case reflect.Struct: - sinfo, err := getStructInfo(field.Type) - if err != nil { - return nil, err - } - for _, finfo := range sinfo.FieldsList { - if _, found := fieldsMap[finfo.Key]; found { - msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String() - return nil, errors.New(msg) - } - if finfo.Inline == nil { - finfo.Inline = []int{i, finfo.Num} - } else { - finfo.Inline = append([]int{i}, finfo.Inline...) - } - finfo.Id = len(fieldsList) - fieldsMap[finfo.Key] = finfo - fieldsList = append(fieldsList, finfo) - } - default: - //return nil, errors.New("Option ,inline needs a struct value or map field") - return nil, errors.New("Option ,inline needs a struct value field") - } - continue - } - - if tag != "" { - info.Key = tag - } else { - info.Key = strings.ToLower(field.Name) - } - - if _, found = fieldsMap[info.Key]; found { - msg := "Duplicated key '" + info.Key + "' in struct " + st.String() - return nil, errors.New(msg) - } - - info.Id = len(fieldsList) - fieldsList = append(fieldsList, info) - fieldsMap[info.Key] = info - } - - sinfo = &structInfo{ - FieldsMap: fieldsMap, - FieldsList: fieldsList, - InlineMap: inlineMap, - } - - fieldMapMutex.Lock() - structMap[st] = sinfo - fieldMapMutex.Unlock() - return sinfo, nil -} - -// IsZeroer is used to check whether an object is zero to -// determine whether it should be omitted when marshaling -// with the omitempty flag. One notable implementation -// is time.Time. -type IsZeroer interface { - IsZero() bool -} - -func isZero(v reflect.Value) bool { - kind := v.Kind() - if z, ok := v.Interface().(IsZeroer); ok { - if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() { - return true - } - return z.IsZero() - } - switch kind { - case reflect.String: - return len(v.String()) == 0 - case reflect.Interface, reflect.Ptr: - return v.IsNil() - case reflect.Slice: - return v.Len() == 0 - case reflect.Map: - return v.Len() == 0 - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Bool: - return !v.Bool() - case reflect.Struct: - vt := v.Type() - for i := v.NumField() - 1; i >= 0; i-- { - if vt.Field(i).PkgPath != "" { - continue // Private field - } - if !isZero(v.Field(i)) { - return false - } - } - return true - } - return false -} - -// FutureLineWrap globally disables line wrapping when encoding long strings. -// This is a temporary and thus deprecated method introduced to faciliate -// migration towards v3, which offers more control of line lengths on -// individual encodings, and has a default matching the behavior introduced -// by this function. -// -// The default formatting of v2 was erroneously changed in v2.3.0 and reverted -// in v2.4.0, at which point this function was introduced to help migration. -func FutureLineWrap() { - disableLineWrapping = true -} diff --git a/vendor/go.yaml.in/yaml/v2/yamlh.go b/vendor/go.yaml.in/yaml/v2/yamlh.go deleted file mode 100644 index f6a9c8e34b..0000000000 --- a/vendor/go.yaml.in/yaml/v2/yamlh.go +++ /dev/null @@ -1,739 +0,0 @@ -package yaml - -import ( - "fmt" - "io" -) - -// The version directive data. -type yaml_version_directive_t struct { - major int8 // The major version number. - minor int8 // The minor version number. -} - -// The tag directive data. -type yaml_tag_directive_t struct { - handle []byte // The tag handle. - prefix []byte // The tag prefix. -} - -type yaml_encoding_t int - -// The stream encoding. -const ( - // Let the parser choose the encoding. - yaml_ANY_ENCODING yaml_encoding_t = iota - - yaml_UTF8_ENCODING // The default UTF-8 encoding. - yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM. - yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM. -) - -type yaml_break_t int - -// Line break types. -const ( - // Let the parser choose the break type. - yaml_ANY_BREAK yaml_break_t = iota - - yaml_CR_BREAK // Use CR for line breaks (Mac style). - yaml_LN_BREAK // Use LN for line breaks (Unix style). - yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style). -) - -type yaml_error_type_t int - -// Many bad things could happen with the parser and emitter. -const ( - // No error is produced. - yaml_NO_ERROR yaml_error_type_t = iota - - yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory. - yaml_READER_ERROR // Cannot read or decode the input stream. - yaml_SCANNER_ERROR // Cannot scan the input stream. - yaml_PARSER_ERROR // Cannot parse the input stream. - yaml_COMPOSER_ERROR // Cannot compose a YAML document. - yaml_WRITER_ERROR // Cannot write to the output stream. - yaml_EMITTER_ERROR // Cannot emit a YAML stream. -) - -// The pointer position. -type yaml_mark_t struct { - index int // The position index. - line int // The position line. - column int // The position column. -} - -// Node Styles - -type yaml_style_t int8 - -type yaml_scalar_style_t yaml_style_t - -// Scalar styles. -const ( - // Let the emitter choose the style. - yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = iota - - yaml_PLAIN_SCALAR_STYLE // The plain scalar style. - yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style. - yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style. - yaml_LITERAL_SCALAR_STYLE // The literal scalar style. - yaml_FOLDED_SCALAR_STYLE // The folded scalar style. -) - -type yaml_sequence_style_t yaml_style_t - -// Sequence styles. -const ( - // Let the emitter choose the style. - yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota - - yaml_BLOCK_SEQUENCE_STYLE // The block sequence style. - yaml_FLOW_SEQUENCE_STYLE // The flow sequence style. -) - -type yaml_mapping_style_t yaml_style_t - -// Mapping styles. -const ( - // Let the emitter choose the style. - yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota - - yaml_BLOCK_MAPPING_STYLE // The block mapping style. - yaml_FLOW_MAPPING_STYLE // The flow mapping style. -) - -// Tokens - -type yaml_token_type_t int - -// Token types. -const ( - // An empty token. - yaml_NO_TOKEN yaml_token_type_t = iota - - yaml_STREAM_START_TOKEN // A STREAM-START token. - yaml_STREAM_END_TOKEN // A STREAM-END token. - - yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token. - yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token. - yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token. - yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token. - - yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token. - yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token. - yaml_BLOCK_END_TOKEN // A BLOCK-END token. - - yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token. - yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token. - yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token. - yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token. - - yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token. - yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token. - yaml_KEY_TOKEN // A KEY token. - yaml_VALUE_TOKEN // A VALUE token. - - yaml_ALIAS_TOKEN // An ALIAS token. - yaml_ANCHOR_TOKEN // An ANCHOR token. - yaml_TAG_TOKEN // A TAG token. - yaml_SCALAR_TOKEN // A SCALAR token. -) - -func (tt yaml_token_type_t) String() string { - switch tt { - case yaml_NO_TOKEN: - return "yaml_NO_TOKEN" - case yaml_STREAM_START_TOKEN: - return "yaml_STREAM_START_TOKEN" - case yaml_STREAM_END_TOKEN: - return "yaml_STREAM_END_TOKEN" - case yaml_VERSION_DIRECTIVE_TOKEN: - return "yaml_VERSION_DIRECTIVE_TOKEN" - case yaml_TAG_DIRECTIVE_TOKEN: - return "yaml_TAG_DIRECTIVE_TOKEN" - case yaml_DOCUMENT_START_TOKEN: - return "yaml_DOCUMENT_START_TOKEN" - case yaml_DOCUMENT_END_TOKEN: - return "yaml_DOCUMENT_END_TOKEN" - case yaml_BLOCK_SEQUENCE_START_TOKEN: - return "yaml_BLOCK_SEQUENCE_START_TOKEN" - case yaml_BLOCK_MAPPING_START_TOKEN: - return "yaml_BLOCK_MAPPING_START_TOKEN" - case yaml_BLOCK_END_TOKEN: - return "yaml_BLOCK_END_TOKEN" - case yaml_FLOW_SEQUENCE_START_TOKEN: - return "yaml_FLOW_SEQUENCE_START_TOKEN" - case yaml_FLOW_SEQUENCE_END_TOKEN: - return "yaml_FLOW_SEQUENCE_END_TOKEN" - case yaml_FLOW_MAPPING_START_TOKEN: - return "yaml_FLOW_MAPPING_START_TOKEN" - case yaml_FLOW_MAPPING_END_TOKEN: - return "yaml_FLOW_MAPPING_END_TOKEN" - case yaml_BLOCK_ENTRY_TOKEN: - return "yaml_BLOCK_ENTRY_TOKEN" - case yaml_FLOW_ENTRY_TOKEN: - return "yaml_FLOW_ENTRY_TOKEN" - case yaml_KEY_TOKEN: - return "yaml_KEY_TOKEN" - case yaml_VALUE_TOKEN: - return "yaml_VALUE_TOKEN" - case yaml_ALIAS_TOKEN: - return "yaml_ALIAS_TOKEN" - case yaml_ANCHOR_TOKEN: - return "yaml_ANCHOR_TOKEN" - case yaml_TAG_TOKEN: - return "yaml_TAG_TOKEN" - case yaml_SCALAR_TOKEN: - return "yaml_SCALAR_TOKEN" - } - return "" -} - -// The token structure. -type yaml_token_t struct { - // The token type. - typ yaml_token_type_t - - // The start/end of the token. - start_mark, end_mark yaml_mark_t - - // The stream encoding (for yaml_STREAM_START_TOKEN). - encoding yaml_encoding_t - - // The alias/anchor/scalar value or tag/tag directive handle - // (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN). - value []byte - - // The tag suffix (for yaml_TAG_TOKEN). - suffix []byte - - // The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN). - prefix []byte - - // The scalar style (for yaml_SCALAR_TOKEN). - style yaml_scalar_style_t - - // The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN). - major, minor int8 -} - -// Events - -type yaml_event_type_t int8 - -// Event types. -const ( - // An empty event. - yaml_NO_EVENT yaml_event_type_t = iota - - yaml_STREAM_START_EVENT // A STREAM-START event. - yaml_STREAM_END_EVENT // A STREAM-END event. - yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event. - yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event. - yaml_ALIAS_EVENT // An ALIAS event. - yaml_SCALAR_EVENT // A SCALAR event. - yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event. - yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event. - yaml_MAPPING_START_EVENT // A MAPPING-START event. - yaml_MAPPING_END_EVENT // A MAPPING-END event. -) - -var eventStrings = []string{ - yaml_NO_EVENT: "none", - yaml_STREAM_START_EVENT: "stream start", - yaml_STREAM_END_EVENT: "stream end", - yaml_DOCUMENT_START_EVENT: "document start", - yaml_DOCUMENT_END_EVENT: "document end", - yaml_ALIAS_EVENT: "alias", - yaml_SCALAR_EVENT: "scalar", - yaml_SEQUENCE_START_EVENT: "sequence start", - yaml_SEQUENCE_END_EVENT: "sequence end", - yaml_MAPPING_START_EVENT: "mapping start", - yaml_MAPPING_END_EVENT: "mapping end", -} - -func (e yaml_event_type_t) String() string { - if e < 0 || int(e) >= len(eventStrings) { - return fmt.Sprintf("unknown event %d", e) - } - return eventStrings[e] -} - -// The event structure. -type yaml_event_t struct { - - // The event type. - typ yaml_event_type_t - - // The start and end of the event. - start_mark, end_mark yaml_mark_t - - // The document encoding (for yaml_STREAM_START_EVENT). - encoding yaml_encoding_t - - // The version directive (for yaml_DOCUMENT_START_EVENT). - version_directive *yaml_version_directive_t - - // The list of tag directives (for yaml_DOCUMENT_START_EVENT). - tag_directives []yaml_tag_directive_t - - // The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT). - anchor []byte - - // The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). - tag []byte - - // The scalar value (for yaml_SCALAR_EVENT). - value []byte - - // Is the document start/end indicator implicit, or the tag optional? - // (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT). - implicit bool - - // Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT). - quoted_implicit bool - - // The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). - style yaml_style_t -} - -func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) } -func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) } -func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) } - -// Nodes - -const ( - yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null. - yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false. - yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values. - yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values. - yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values. - yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values. - - yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences. - yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping. - - // Not in original libyaml. - yaml_BINARY_TAG = "tag:yaml.org,2002:binary" - yaml_MERGE_TAG = "tag:yaml.org,2002:merge" - - yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str. - yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq. - yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map. -) - -type yaml_node_type_t int - -// Node types. -const ( - // An empty node. - yaml_NO_NODE yaml_node_type_t = iota - - yaml_SCALAR_NODE // A scalar node. - yaml_SEQUENCE_NODE // A sequence node. - yaml_MAPPING_NODE // A mapping node. -) - -// An element of a sequence node. -type yaml_node_item_t int - -// An element of a mapping node. -type yaml_node_pair_t struct { - key int // The key of the element. - value int // The value of the element. -} - -// The node structure. -type yaml_node_t struct { - typ yaml_node_type_t // The node type. - tag []byte // The node tag. - - // The node data. - - // The scalar parameters (for yaml_SCALAR_NODE). - scalar struct { - value []byte // The scalar value. - length int // The length of the scalar value. - style yaml_scalar_style_t // The scalar style. - } - - // The sequence parameters (for YAML_SEQUENCE_NODE). - sequence struct { - items_data []yaml_node_item_t // The stack of sequence items. - style yaml_sequence_style_t // The sequence style. - } - - // The mapping parameters (for yaml_MAPPING_NODE). - mapping struct { - pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value). - pairs_start *yaml_node_pair_t // The beginning of the stack. - pairs_end *yaml_node_pair_t // The end of the stack. - pairs_top *yaml_node_pair_t // The top of the stack. - style yaml_mapping_style_t // The mapping style. - } - - start_mark yaml_mark_t // The beginning of the node. - end_mark yaml_mark_t // The end of the node. - -} - -// The document structure. -type yaml_document_t struct { - - // The document nodes. - nodes []yaml_node_t - - // The version directive. - version_directive *yaml_version_directive_t - - // The list of tag directives. - tag_directives_data []yaml_tag_directive_t - tag_directives_start int // The beginning of the tag directives list. - tag_directives_end int // The end of the tag directives list. - - start_implicit int // Is the document start indicator implicit? - end_implicit int // Is the document end indicator implicit? - - // The start/end of the document. - start_mark, end_mark yaml_mark_t -} - -// The prototype of a read handler. -// -// The read handler is called when the parser needs to read more bytes from the -// source. The handler should write not more than size bytes to the buffer. -// The number of written bytes should be set to the size_read variable. -// -// [in,out] data A pointer to an application data specified by -// yaml_parser_set_input(). -// [out] buffer The buffer to write the data from the source. -// [in] size The size of the buffer. -// [out] size_read The actual number of bytes read from the source. -// -// On success, the handler should return 1. If the handler failed, -// the returned value should be 0. On EOF, the handler should set the -// size_read to 0 and return 1. -type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error) - -// This structure holds information about a potential simple key. -type yaml_simple_key_t struct { - possible bool // Is a simple key possible? - required bool // Is a simple key required? - token_number int // The number of the token. - mark yaml_mark_t // The position mark. -} - -// The states of the parser. -type yaml_parser_state_t int - -const ( - yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota - - yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document. - yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START. - yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document. - yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END. - yaml_PARSE_BLOCK_NODE_STATE // Expect a block node. - yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence. - yaml_PARSE_FLOW_NODE_STATE // Expect a flow node. - yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence. - yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence. - yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence. - yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. - yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key. - yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value. - yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence. - yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence. - yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping. - yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping. - yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry. - yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. - yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. - yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. - yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping. - yaml_PARSE_END_STATE // Expect nothing. -) - -func (ps yaml_parser_state_t) String() string { - switch ps { - case yaml_PARSE_STREAM_START_STATE: - return "yaml_PARSE_STREAM_START_STATE" - case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: - return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE" - case yaml_PARSE_DOCUMENT_START_STATE: - return "yaml_PARSE_DOCUMENT_START_STATE" - case yaml_PARSE_DOCUMENT_CONTENT_STATE: - return "yaml_PARSE_DOCUMENT_CONTENT_STATE" - case yaml_PARSE_DOCUMENT_END_STATE: - return "yaml_PARSE_DOCUMENT_END_STATE" - case yaml_PARSE_BLOCK_NODE_STATE: - return "yaml_PARSE_BLOCK_NODE_STATE" - case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: - return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE" - case yaml_PARSE_FLOW_NODE_STATE: - return "yaml_PARSE_FLOW_NODE_STATE" - case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: - return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE" - case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: - return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE" - case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: - return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE" - case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: - return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE" - case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: - return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE" - case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: - return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE" - case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: - return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE" - case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: - return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE" - case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: - return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE" - case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: - return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE" - case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: - return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE" - case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: - return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE" - case yaml_PARSE_FLOW_MAPPING_KEY_STATE: - return "yaml_PARSE_FLOW_MAPPING_KEY_STATE" - case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: - return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE" - case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: - return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE" - case yaml_PARSE_END_STATE: - return "yaml_PARSE_END_STATE" - } - return "" -} - -// This structure holds aliases data. -type yaml_alias_data_t struct { - anchor []byte // The anchor. - index int // The node id. - mark yaml_mark_t // The anchor mark. -} - -// The parser structure. -// -// All members are internal. Manage the structure using the -// yaml_parser_ family of functions. -type yaml_parser_t struct { - - // Error handling - - error yaml_error_type_t // Error type. - - problem string // Error description. - - // The byte about which the problem occurred. - problem_offset int - problem_value int - problem_mark yaml_mark_t - - // The error context. - context string - context_mark yaml_mark_t - - // Reader stuff - - read_handler yaml_read_handler_t // Read handler. - - input_reader io.Reader // File input data. - input []byte // String input data. - input_pos int - - eof bool // EOF flag - - buffer []byte // The working buffer. - buffer_pos int // The current position of the buffer. - - unread int // The number of unread characters in the buffer. - - raw_buffer []byte // The raw buffer. - raw_buffer_pos int // The current position of the buffer. - - encoding yaml_encoding_t // The input encoding. - - offset int // The offset of the current position (in bytes). - mark yaml_mark_t // The mark of the current position. - - // Scanner stuff - - stream_start_produced bool // Have we started to scan the input stream? - stream_end_produced bool // Have we reached the end of the input stream? - - flow_level int // The number of unclosed '[' and '{' indicators. - - tokens []yaml_token_t // The tokens queue. - tokens_head int // The head of the tokens queue. - tokens_parsed int // The number of tokens fetched from the queue. - token_available bool // Does the tokens queue contain a token ready for dequeueing. - - indent int // The current indentation level. - indents []int // The indentation levels stack. - - simple_key_allowed bool // May a simple key occur at the current position? - simple_keys []yaml_simple_key_t // The stack of simple keys. - simple_keys_by_tok map[int]int // possible simple_key indexes indexed by token_number - - // Parser stuff - - state yaml_parser_state_t // The current parser state. - states []yaml_parser_state_t // The parser states stack. - marks []yaml_mark_t // The stack of marks. - tag_directives []yaml_tag_directive_t // The list of TAG directives. - - // Dumper stuff - - aliases []yaml_alias_data_t // The alias data. - - document *yaml_document_t // The currently parsed document. -} - -// Emitter Definitions - -// The prototype of a write handler. -// -// The write handler is called when the emitter needs to flush the accumulated -// characters to the output. The handler should write @a size bytes of the -// @a buffer to the output. -// -// @param[in,out] data A pointer to an application data specified by -// yaml_emitter_set_output(). -// @param[in] buffer The buffer with bytes to be written. -// @param[in] size The size of the buffer. -// -// @returns On success, the handler should return @c 1. If the handler failed, -// the returned value should be @c 0. -// -type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error - -type yaml_emitter_state_t int - -// The emitter states. -const ( - // Expect STREAM-START. - yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota - - yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END. - yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END. - yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document. - yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END. - yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence. - yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence. - yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. - yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. - yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping. - yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. - yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence. - yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence. - yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. - yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping. - yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping. - yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping. - yaml_EMIT_END_STATE // Expect nothing. -) - -// The emitter structure. -// -// All members are internal. Manage the structure using the @c yaml_emitter_ -// family of functions. -type yaml_emitter_t struct { - - // Error handling - - error yaml_error_type_t // Error type. - problem string // Error description. - - // Writer stuff - - write_handler yaml_write_handler_t // Write handler. - - output_buffer *[]byte // String output data. - output_writer io.Writer // File output data. - - buffer []byte // The working buffer. - buffer_pos int // The current position of the buffer. - - raw_buffer []byte // The raw buffer. - raw_buffer_pos int // The current position of the buffer. - - encoding yaml_encoding_t // The stream encoding. - - // Emitter stuff - - canonical bool // If the output is in the canonical style? - best_indent int // The number of indentation spaces. - best_width int // The preferred width of the output lines. - unicode bool // Allow unescaped non-ASCII characters? - line_break yaml_break_t // The preferred line break. - - state yaml_emitter_state_t // The current emitter state. - states []yaml_emitter_state_t // The stack of states. - - events []yaml_event_t // The event queue. - events_head int // The head of the event queue. - - indents []int // The stack of indentation levels. - - tag_directives []yaml_tag_directive_t // The list of tag directives. - - indent int // The current indentation level. - - flow_level int // The current flow level. - - root_context bool // Is it the document root context? - sequence_context bool // Is it a sequence context? - mapping_context bool // Is it a mapping context? - simple_key_context bool // Is it a simple mapping key context? - - line int // The current line. - column int // The current column. - whitespace bool // If the last character was a whitespace? - indention bool // If the last character was an indentation character (' ', '-', '?', ':')? - open_ended bool // If an explicit document end is required? - - // Anchor analysis. - anchor_data struct { - anchor []byte // The anchor value. - alias bool // Is it an alias? - } - - // Tag analysis. - tag_data struct { - handle []byte // The tag handle. - suffix []byte // The tag suffix. - } - - // Scalar analysis. - scalar_data struct { - value []byte // The scalar value. - multiline bool // Does the scalar contain line breaks? - flow_plain_allowed bool // Can the scalar be expessed in the flow plain style? - block_plain_allowed bool // Can the scalar be expressed in the block plain style? - single_quoted_allowed bool // Can the scalar be expressed in the single quoted style? - block_allowed bool // Can the scalar be expressed in the literal or folded styles? - style yaml_scalar_style_t // The output style. - } - - // Dumper stuff - - opened bool // If the stream was already opened? - closed bool // If the stream was already closed? - - // The information associated with the document nodes. - anchors *struct { - references int // The number of references. - anchor int // The anchor id. - serialized bool // If the node has been emitted? - } - - last_anchor_id int // The last assigned anchor id. - - document *yaml_document_t // The currently emitted document. -} diff --git a/vendor/go.yaml.in/yaml/v2/yamlprivateh.go b/vendor/go.yaml.in/yaml/v2/yamlprivateh.go deleted file mode 100644 index 8110ce3c37..0000000000 --- a/vendor/go.yaml.in/yaml/v2/yamlprivateh.go +++ /dev/null @@ -1,173 +0,0 @@ -package yaml - -const ( - // The size of the input raw buffer. - input_raw_buffer_size = 512 - - // The size of the input buffer. - // It should be possible to decode the whole raw buffer. - input_buffer_size = input_raw_buffer_size * 3 - - // The size of the output buffer. - output_buffer_size = 128 - - // The size of the output raw buffer. - // It should be possible to encode the whole output buffer. - output_raw_buffer_size = (output_buffer_size*2 + 2) - - // The size of other stacks and queues. - initial_stack_size = 16 - initial_queue_size = 16 - initial_string_size = 16 -) - -// Check if the character at the specified position is an alphabetical -// character, a digit, '_', or '-'. -func is_alpha(b []byte, i int) bool { - return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-' -} - -// Check if the character at the specified position is a digit. -func is_digit(b []byte, i int) bool { - return b[i] >= '0' && b[i] <= '9' -} - -// Get the value of a digit. -func as_digit(b []byte, i int) int { - return int(b[i]) - '0' -} - -// Check if the character at the specified position is a hex-digit. -func is_hex(b []byte, i int) bool { - return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f' -} - -// Get the value of a hex-digit. -func as_hex(b []byte, i int) int { - bi := b[i] - if bi >= 'A' && bi <= 'F' { - return int(bi) - 'A' + 10 - } - if bi >= 'a' && bi <= 'f' { - return int(bi) - 'a' + 10 - } - return int(bi) - '0' -} - -// Check if the character is ASCII. -func is_ascii(b []byte, i int) bool { - return b[i] <= 0x7F -} - -// Check if the character at the start of the buffer can be printed unescaped. -func is_printable(b []byte, i int) bool { - return ((b[i] == 0x0A) || // . == #x0A - (b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E - (b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF - (b[i] > 0xC2 && b[i] < 0xED) || - (b[i] == 0xED && b[i+1] < 0xA0) || - (b[i] == 0xEE) || - (b[i] == 0xEF && // #xE000 <= . <= #xFFFD - !(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF - !(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF)))) -} - -// Check if the character at the specified position is NUL. -func is_z(b []byte, i int) bool { - return b[i] == 0x00 -} - -// Check if the beginning of the buffer is a BOM. -func is_bom(b []byte, i int) bool { - return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF -} - -// Check if the character at the specified position is space. -func is_space(b []byte, i int) bool { - return b[i] == ' ' -} - -// Check if the character at the specified position is tab. -func is_tab(b []byte, i int) bool { - return b[i] == '\t' -} - -// Check if the character at the specified position is blank (space or tab). -func is_blank(b []byte, i int) bool { - //return is_space(b, i) || is_tab(b, i) - return b[i] == ' ' || b[i] == '\t' -} - -// Check if the character at the specified position is a line break. -func is_break(b []byte, i int) bool { - return (b[i] == '\r' || // CR (#xD) - b[i] == '\n' || // LF (#xA) - b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) - b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) - b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029) -} - -func is_crlf(b []byte, i int) bool { - return b[i] == '\r' && b[i+1] == '\n' -} - -// Check if the character is a line break or NUL. -func is_breakz(b []byte, i int) bool { - //return is_break(b, i) || is_z(b, i) - return ( // is_break: - b[i] == '\r' || // CR (#xD) - b[i] == '\n' || // LF (#xA) - b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) - b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) - b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) - // is_z: - b[i] == 0) -} - -// Check if the character is a line break, space, or NUL. -func is_spacez(b []byte, i int) bool { - //return is_space(b, i) || is_breakz(b, i) - return ( // is_space: - b[i] == ' ' || - // is_breakz: - b[i] == '\r' || // CR (#xD) - b[i] == '\n' || // LF (#xA) - b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) - b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) - b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) - b[i] == 0) -} - -// Check if the character is a line break, space, tab, or NUL. -func is_blankz(b []byte, i int) bool { - //return is_blank(b, i) || is_breakz(b, i) - return ( // is_blank: - b[i] == ' ' || b[i] == '\t' || - // is_breakz: - b[i] == '\r' || // CR (#xD) - b[i] == '\n' || // LF (#xA) - b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) - b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) - b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) - b[i] == 0) -} - -// Determine the width of the character. -func width(b byte) int { - // Don't replace these by a switch without first - // confirming that it is being inlined. - if b&0x80 == 0x00 { - return 1 - } - if b&0xE0 == 0xC0 { - return 2 - } - if b&0xF0 == 0xE0 { - return 3 - } - if b&0xF8 == 0xF0 { - return 4 - } - return 0 - -} diff --git a/vendor/golang.org/x/crypto/argon2/argon2.go b/vendor/golang.org/x/crypto/argon2/argon2.go index 2b65ec91ac..57ab8371cb 100644 --- a/vendor/golang.org/x/crypto/argon2/argon2.go +++ b/vendor/golang.org/x/crypto/argon2/argon2.go @@ -17,8 +17,8 @@ // It uses data-independent memory access, which is preferred for password // hashing and password-based key derivation. Argon2i requires more passes over // memory than Argon2id to protect from trade-off attacks. The recommended -// parameters (taken from [RFC 9106 Section 7.3]) for non-interactive operations are time=3 and to -// use the maximum available memory. +// parameters (taken from [RFC 9106 Section 7.3]) for non-interactive +// operations are time=3 and to use the maximum available memory. // // # Argon2id // @@ -26,11 +26,14 @@ // Argon2i and Argon2d. It uses data-independent memory access for the first // half of the first iteration over the memory and data-dependent memory access // for the rest. Argon2id is side-channel resistant and provides better brute- -// force cost savings due to time-memory tradeoffs than Argon2i. The recommended -// parameters for non-interactive operations (taken from [RFC 9106 Section 7.3]) are time=1 and to -// use the maximum available memory. +// force cost savings due to time-memory tradeoffs than Argon2i. [RFC 9106 +// Section 4] recommends time=1, memory=2*1024*1024 KiB (2 GiB), and threads=4 +// as the first recommended option. If much less memory is available, it +// recommends time=3, memory=64*1024 KiB (64 MiB), and threads=4 as the second +// recommended option. // // [argon2-specs.pdf]: https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf +// [RFC 9106 Section 4]: https://www.rfc-editor.org/rfc/rfc9106.html#section-4 // [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3 package argon2 @@ -59,9 +62,9 @@ const ( // // key := argon2.Key([]byte("some password"), salt, 3, 32*1024, 4, 32) // -// [RFC 9106 Section 7.3] recommends time=3, and memory=32*1024 as a sensible number. -// If using that amount of memory (32 MB) is not possible in some contexts then -// the time parameter can be increased to compensate. +// The example above uses time=3 and memory=32*1024. Argon2i generally +// requires more passes over memory than Argon2id. If in doubt, prefer IDKey +// and its Argon2id parameter recommendations. // // The time parameter specifies the number of passes over the memory and the // memory parameter specifies the size of the memory in KiB. For example @@ -69,8 +72,6 @@ const ( // adjusted to the number of available CPUs. The cost parameters should be // increased as memory latency and CPU parallelism increases. Remember to get a // good random salt. -// -// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3 func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte { return deriveKey(argon2i, password, salt, nil, nil, time, memory, threads, keyLen) } @@ -83,20 +84,20 @@ func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint3 // For example, you can get a derived key for e.g. AES-256 (which needs a // 32-byte key) by doing: // -// key := argon2.IDKey([]byte("some password"), salt, 1, 64*1024, 4, 32) +// key := argon2.IDKey([]byte("some password"), salt, 1, 2*1024*1024, 4, 32) // -// [RFC 9106 Section 7.3] recommends time=1, and memory=64*1024 as a sensible number. -// If using that amount of memory (64 MB) is not possible in some contexts then -// the time parameter can be increased to compensate. +// The example above uses the first [RFC 9106 Section 4] recommended option. +// If much less memory is available, the second recommended option is time=3, +// memory=64*1024 KiB (64 MiB), and threads=4. // // The time parameter specifies the number of passes over the memory and the // memory parameter specifies the size of the memory in KiB. For example -// memory=64*1024 sets the memory cost to ~64 MB. The number of threads can be -// adjusted to the numbers of available CPUs. The cost parameters should be +// memory=2*1024*1024 sets the memory cost to ~2 GiB. The number of threads can +// be adjusted to the numbers of available CPUs. The cost parameters should be // increased as memory latency and CPU parallelism increases. Remember to get a // good random salt. // -// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3 +// [RFC 9106 Section 4]: https://www.rfc-editor.org/rfc/rfc9106.html#section-4 func IDKey(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte { return deriveKey(argon2id, password, salt, nil, nil, time, memory, threads, keyLen) } diff --git a/vendor/golang.org/x/crypto/ssh/agent/forward.go b/vendor/golang.org/x/crypto/ssh/agent/forward.go index fd24ba900d..5e7a0ea40d 100644 --- a/vendor/golang.org/x/crypto/ssh/agent/forward.go +++ b/vendor/golang.org/x/crypto/ssh/agent/forward.go @@ -41,6 +41,7 @@ func ForwardToAgent(client *ssh.Client, keyring Agent) error { continue } go ssh.DiscardRequests(reqs) + go io.Copy(io.Discard, channel.Stderr()) go func() { ServeAgent(keyring, channel) channel.Close() @@ -72,6 +73,7 @@ func ForwardToRemote(client *ssh.Client, addr string) error { continue } go ssh.DiscardRequests(reqs) + go io.Copy(io.Discard, channel.Stderr()) go forwardUnixSocket(channel, addr) } }() diff --git a/vendor/golang.org/x/crypto/ssh/agent/server.go b/vendor/golang.org/x/crypto/ssh/agent/server.go index f05d22fb3d..782c54eb7c 100644 --- a/vendor/golang.org/x/crypto/ssh/agent/server.go +++ b/vendor/golang.org/x/crypto/ssh/agent/server.go @@ -304,19 +304,60 @@ func parseEd25519Key(req []byte) (*AddedKey, error) { return addedKey, nil } +func checkDSAParams(param *dsa.Parameters) error { + // SSH specifies FIPS 186-2, which only provided a single size + // (1024 bits) DSA key. FIPS 186-3 allows for larger key + // sizes, which would confuse SSH. + if l := param.P.BitLen(); l != 1024 { + return fmt.Errorf("ssh: unsupported DSA key size %d", l) + } + + // FIPS 186-2 specifies that Q must be exactly 160 bits. We must enforce + // this to prevent DoS attacks where an attacker sends a huge Q which makes + // verification slow. + if l := param.Q.BitLen(); l != 160 { + return fmt.Errorf("ssh: unsupported DSA sub-prime size %d", l) + } + + // The generator G is an element of the group, so it must be strictly less + // than the modulus P. + if param.G.Cmp(param.P) >= 0 { + return errors.New("ssh: DSA generator larger than modulus") + } + + // G must be positive. + if param.G.Sign() <= 0 { + return errors.New("ssh: DSA generator must be positive") + } + + return nil +} + func parseDSAKey(req []byte) (*AddedKey, error) { var k dsaKeyMsg if err := ssh.Unmarshal(req, &k); err != nil { return nil, err } + params := dsa.Parameters{ + P: k.P, + Q: k.Q, + G: k.G, + } + if err := checkDSAParams(¶ms); err != nil { + return nil, err + } + + // The public value Y must be a non-zero element of the group, i.e. strictly + // between 0 and P, to prevent a maliciously oversized Y from slowing + // signature operations. + if k.Y.Sign() <= 0 || k.Y.Cmp(k.P) >= 0 { + return nil, errors.New("agent: DSA public value Y out of range") + } + priv := &dsa.PrivateKey{ PublicKey: dsa.PublicKey{ - Parameters: dsa.Parameters{ - P: k.P, - Q: k.Q, - G: k.G, - }, - Y: k.Y, + Parameters: params, + Y: k.Y, }, X: k.X, } diff --git a/vendor/golang.org/x/crypto/ssh/keys.go b/vendor/golang.org/x/crypto/ssh/keys.go index 334861b7f1..64377715e1 100644 --- a/vendor/golang.org/x/crypto/ssh/keys.go +++ b/vendor/golang.org/x/crypto/ssh/keys.go @@ -182,14 +182,19 @@ func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey } hosts := string(keyFields[0]) - // keyFields[1] contains the key type (e.g. “ssh-rsa”). - // However, that information is duplicated inside the - // base64-encoded key and so is ignored here. + // keyFields[1] contains the key type (e.g. "ssh-rsa"). This information + // is duplicated within the base64-encoded key blob. As OpenSSH's + // sshkey_read does, we verify that the declared key type matches the + // type embedded in the key blob. + wantType := string(keyFields[1]) key := bytes.Join(keyFields[2:], []byte(" ")) if pubKey, comment, err = parseAuthorizedKey(key); err != nil { return "", nil, nil, "", nil, err } + if pubKey.Type() != wantType { + return "", nil, nil, "", nil, fmt.Errorf("ssh: known hosts key type mismatch: human-readable type %q, encoded type %q", wantType, pubKey.Type()) + } return marker, strings.Split(hosts, ","), pubKey, comment, rest, nil } @@ -228,10 +233,17 @@ func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []str } if out, comment, err = parseAuthorizedKey(in[i:]); err == nil { - return out, comment, options, rest, nil - } else { - lastErr = err + // The first field contains the declared key type. As OpenSSH's + // sshkey_read does, we verify that it matches the type embedded in + // the key blob. Without this check, a single-token option (e.g. + // "restrict") appearing in the key type position could be silently + // discarded along with its intended effect. + if string(in[:i]) == out.Type() { + return out, comment, options, rest, nil + } + err = fmt.Errorf("ssh: authorized keys key type mismatch: human-readable type %q, encoded type %q", in[:i], out.Type()) } + lastErr = err // No key type recognised. Maybe there's an options field at // the beginning. @@ -271,11 +283,15 @@ func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []str } if out, comment, err = parseAuthorizedKey(in[i:]); err == nil { - options = candidateOptions - return out, comment, options, rest, nil - } else { - lastErr = err + // As above, the declared key type (here following the options + // field) must match the type embedded in the key blob. + if string(in[:i]) == out.Type() { + options = candidateOptions + return out, comment, options, rest, nil + } + err = fmt.Errorf("ssh: authorized keys key type mismatch: human-readable type %q, encoded type %q", in[:i], out.Type()) } + lastErr = err in = rest continue diff --git a/vendor/golang.org/x/crypto/ssh/messages.go b/vendor/golang.org/x/crypto/ssh/messages.go index ab22c3d38d..de86f71cf4 100644 --- a/vendor/golang.org/x/crypto/ssh/messages.go +++ b/vendor/golang.org/x/crypto/ssh/messages.go @@ -44,7 +44,16 @@ type disconnectMsg struct { } func (d *disconnectMsg) Error() string { - return fmt.Sprintf("ssh: disconnect, reason %d: %s", d.Reason, d.Message) + return fmt.Sprintf("ssh: disconnect, reason %d: %q", d.Reason, sanitizeString(d.Message)) +} + +func sanitizeString(s string) string { + return strings.Map(func(r rune) rune { + if r == '\t' || (r >= ' ' && r < 0x7f) { + return r + } + return -1 + }, s) } // See RFC 4253, section 7.1. diff --git a/vendor/golang.org/x/mod/modfile/read.go b/vendor/golang.org/x/mod/modfile/read.go index 5b528c718e..9e35e1ac51 100644 --- a/vendor/golang.org/x/mod/modfile/read.go +++ b/vendor/golang.org/x/mod/modfile/read.go @@ -924,7 +924,7 @@ var ( moduleStr = []byte("module") ) -// ModulePath returns the module path from the gomod file text. +// ModulePath returns the module path from the go.mod file text. // If it cannot find a module path, it returns an empty string. // It is tolerant of unrelated problems in the go.mod file. func ModulePath(mod []byte) string { diff --git a/vendor/golang.org/x/mod/modfile/rule.go b/vendor/golang.org/x/mod/modfile/rule.go index 9ab203b562..20ba825d29 100644 --- a/vendor/golang.org/x/mod/modfile/rule.go +++ b/vendor/golang.org/x/mod/modfile/rule.go @@ -1477,7 +1477,7 @@ func (f *File) setRequireSeparateIndirect(req []*Require, simplify bool) { // Delete requirements we don't want anymore. // Update versions and indirect comments on requirements we want to keep. // If a requirement is in last{Direct,Indirect}Block with the wrong - // indirect marking after this, or if the requirement is in an single + // indirect marking after this, or if the requirement is in a single // uncommented mixed block (oneFlatUncommentedBlock), move it to the // correct block. // @@ -1537,7 +1537,7 @@ func (f *File) DropRequire(path string) error { return nil } -// AddExclude adds a exclude statement to the mod file. Errors if the provided +// AddExclude adds an exclude statement to the mod file. Errors if the provided // version is not a canonical version string func (f *File) AddExclude(path, vers string) error { if err := checkCanonicalVersion(path, vers); err != nil { @@ -1708,7 +1708,7 @@ func (f *File) AddIgnore(path string) error { return nil } -// DropIgnore removes a ignore directive with the given path. +// DropIgnore removes an ignore directive with the given path. // It does nothing if no such ignore directive exists. func (f *File) DropIgnore(path string) error { for _, t := range f.Ignore { diff --git a/vendor/golang.org/x/net/bpf/doc.go b/vendor/golang.org/x/net/bpf/doc.go index 04ec1c8ab5..1ea566b7ff 100644 --- a/vendor/golang.org/x/net/bpf/doc.go +++ b/vendor/golang.org/x/net/bpf/doc.go @@ -49,6 +49,13 @@ to extensions, which are essentially calls to kernel utility functions. Currently, the only extensions supported by this package are the Linux packet filter extensions. +# Security Considerations + +The implementation of the BPF VM in this package is suitable for +testing BPF programs. It aims for consistency with other BPF VM +implementations, but divergence in behavior is not considered a +security issue. + # Examples This packet filter selects all ARP packets. diff --git a/vendor/golang.org/x/net/http2/transport_wrap.go b/vendor/golang.org/x/net/http2/transport_wrap.go index eab2e6b073..534e77ab96 100644 --- a/vendor/golang.org/x/net/http2/transport_wrap.go +++ b/vendor/golang.org/x/net/http2/transport_wrap.go @@ -55,7 +55,7 @@ type transportConfig struct { // Registered is called by net/http.Transport.RegisterProtocol, // to let us know that it understands the registration mechanism we're using. func (t transportConfig) Registered(t1 *http.Transport) { - t.t.t1 = t1 + t.t.lazyt1 = t1 } func (t transportConfig) DisableCompression() bool { @@ -145,29 +145,30 @@ func (t transportConfig) DialFromContext(ctx context.Context, network, address s type transportInternal struct { initOnce sync.Once - t1 *http.Transport + lazyt1 *http.Transport } -func (t *Transport) init() { +func (t *Transport) init() *http.Transport { t.initOnce.Do(func() { - if t.t1 != nil { + if t.lazyt1 != nil { return } t1 := &http.Transport{} t.configure(t1) }) + return t.lazyt1 } func (t *Transport) configure(t1 *http.Transport) { t1.RegisterProtocol("http/2", transportConfig{t}) - // tr2.t1 is set by transportConfig.Registered. - if t.t1 != t1 { + // tr2.lazyt1 is set by transportConfig.Registered. + if t.lazyt1 != t1 { panic("http2: net/http does not support this version of x/net/http2") } } func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) { - t.init() + t1 := t.init() if req.URL.Scheme == "http" && !t.AllowHTTP { return nil, errors.New("http2: unencrypted HTTP/2 not enabled") @@ -188,22 +189,23 @@ func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res ctx := context.WithValue(req.Context(), http2TransportContextKey{}, t) req = req.WithContext(ctx) - return t.t1.RoundTrip(req) + return t1.RoundTrip(req) } func (t *Transport) closeIdleConnections() { - t.init() - t.t1.CloseIdleConnections() + t1 := t.init() + t1.CloseIdleConnections() } func (t *Transport) newUserClientConn(c net.Conn) (*ClientConn, error) { + t1 := t.init() // http.Transport's NewClientConn doesn't provide a supported way to create // a connection from a net.Conn. (This might be useful to add in the future?) // We're going to craftily sneak one in via the context key, with the // scheme of "http/2" telling NewClientConn to look for it. ctx := context.WithValue(context.Background(), netConnContextKey{}, c) - nhcc, err := t.t1.NewClientConn(ctx, "http/2", "") + nhcc, err := t1.NewClientConn(ctx, "http/2", "") if err != nil { return nil, err } diff --git a/vendor/golang.org/x/net/idna/idna.go b/vendor/golang.org/x/net/idna/idna.go index 22767125bf..e2f28fed48 100644 --- a/vendor/golang.org/x/net/idna/idna.go +++ b/vendor/golang.org/x/net/idna/idna.go @@ -400,7 +400,11 @@ func (p *Profile) process(s string, toASCII bool) (string, error) { // Spec says keep the old label. continue } - if unicode16 && err == nil && len(u) > 0 && isASCII(u) { + if err == nil && len(u) > 0 && isASCII(u) { + // UTS 43 pre-revision 33 doesn't classify a xn-- label + // which contains only ASCII characters as an error, + // but that's a specification bug and a security issue. + // Always return an error in this case. err = punyError(enc) } isBidi = isBidi || bidirule.DirectionString(u) != bidi.LeftToRight diff --git a/vendor/golang.org/x/sys/cpu/parse.go b/vendor/golang.org/x/sys/cpu/parse.go index 56a7e1a176..12a99af5c9 100644 --- a/vendor/golang.org/x/sys/cpu/parse.go +++ b/vendor/golang.org/x/sys/cpu/parse.go @@ -6,38 +6,50 @@ package cpu import "strconv" -// parseRelease parses a dot-separated version number. It follows the semver -// syntax, but allows the minor and patch versions to be elided. +// parseRelease parses a dot-separated version number from the prefix +// of rel. It returns ok=true only if at least the major and minor +// components were successfully parsed; the patch component is +// best-effort. Trailing vendor or build suffixes such as +// "-generic", "+", "_hi3535", or "-rc1" are ignored. // // This is a copy of the Go runtime's parseRelease from -// https://golang.org/cl/209597. +// https://golang.org/cl/209597, updated in https://golang.org/cl/781800. func parseRelease(rel string) (major, minor, patch int, ok bool) { - // Strip anything after a dash or plus. - for i := range len(rel) { - if rel[i] == '-' || rel[i] == '+' { - rel = rel[:i] - break + // next consumes a run of decimal digits from the front of rel, + // returning the parsed value. If the digits are followed by a + // '.', it is consumed and more is set so the caller knows to + // parse another component; otherwise scanning terminates and + // the rest of rel is discarded. + next := func() (n int, more, ok bool) { + i := 0 + for i < len(rel) && rel[i] >= '0' && rel[i] <= '9' { + i++ } - } - - next := func() (int, bool) { - for i := range len(rel) { - if rel[i] == '.' { - ver, err := strconv.Atoi(rel[:i]) - rel = rel[i+1:] - return ver, err == nil - } + if i == 0 { + return 0, false, false + } + n, err := strconv.Atoi(rel[:i]) + if err != nil { + return 0, false, false + } + if i < len(rel) && rel[i] == '.' { + rel = rel[i+1:] + return n, true, true } - ver, err := strconv.Atoi(rel) rel = "" - return ver, err == nil + return n, false, true + } + + var more bool + if major, more, ok = next(); !ok || !more { + return 0, 0, 0, false } - if major, ok = next(); !ok || rel == "" { - return + if minor, more, ok = next(); !ok { + return 0, 0, 0, false } - if minor, ok = next(); !ok || rel == "" { - return + if !more { + return major, minor, 0, true } - patch, ok = next() - return + patch, _, _ = next() + return major, minor, patch, true } diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index ce4d7ab1ed..21e2bfa398 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -1874,6 +1874,7 @@ func Dup2(oldfd, newfd int) error { //sys Dup3(oldfd int, newfd int, flags int) (err error) //sysnb EpollCreate1(flag int) (fd int, err error) //sysnb EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error) +//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) = SYS_EPOLL_PWAIT //sys Eventfd(initval uint, flags int) (fd int, err error) = SYS_EVENTFD2 //sys Exit(code int) = SYS_EXIT_GROUP //sys Fallocate(fd int, mode uint32, off int64, len int64) (err error) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_386.go b/vendor/golang.org/x/sys/unix/syscall_linux_386.go index 506dafa7b4..210d545c93 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_386.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_386.go @@ -20,7 +20,6 @@ func setTimeval(sec, usec int64) Timeval { // 64-bit file system and 32-bit uid calls // (386 default is 32-bit file system and 16-bit uid). -//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) //sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64_64 //sys Fchown(fd int, uid int, gid int) (err error) = SYS_FCHOWN32 //sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64 diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_amd64.go b/vendor/golang.org/x/sys/unix/syscall_linux_amd64.go index d557cf8de3..a9a52f2319 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_amd64.go @@ -6,7 +6,6 @@ package unix -//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) //sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64 //sys Fchown(fd int, uid int, gid int) (err error) //sys Fstat(fd int, stat *Stat_t) (err error) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_arm.go b/vendor/golang.org/x/sys/unix/syscall_linux_arm.go index ecf92bfa2a..54474c20fe 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_arm.go @@ -44,7 +44,6 @@ func Seek(fd int, offset int64, whence int) (newoffset int64, err error) { // 64-bit file system and 32-bit uid calls // (16-bit uid calls are not always supported in newer kernels) -//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) //sys Fchown(fd int, uid int, gid int) (err error) = SYS_FCHOWN32 //sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64 //sys Fstatat(dirfd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64 diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go b/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go index 173738077b..e9f30db97d 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go @@ -8,7 +8,6 @@ package unix import "unsafe" -//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) = SYS_EPOLL_PWAIT //sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64 //sys Fchown(fd int, uid int, gid int) (err error) //sys Fstat(fd int, stat *Stat_t) (err error) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go b/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go index a3fd1d0b80..6f09ca200a 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go @@ -8,7 +8,6 @@ package unix import "unsafe" -//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) = SYS_EPOLL_PWAIT //sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64 //sys Fchown(fd int, uid int, gid int) (err error) //sys Fstatfs(fd int, buf *Statfs_t) (err error) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go b/vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go index 70963a95ab..ca3b56597d 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go @@ -6,7 +6,6 @@ package unix -//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) //sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64 //sys Fchown(fd int, uid int, gid int) (err error) //sys Fstatfs(fd int, buf *Statfs_t) (err error) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go b/vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go index c218ebd280..54ba667b1b 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go @@ -13,7 +13,6 @@ import ( func Syscall9(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err syscall.Errno) -//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) //sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64 //sys Fchown(fd int, uid int, gid int) (err error) //sys Ftruncate(fd int, length int64) (err error) = SYS_FTRUNCATE64 diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_ppc.go b/vendor/golang.org/x/sys/unix/syscall_linux_ppc.go index e6c48500ca..ce46285902 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_ppc.go @@ -11,7 +11,6 @@ import ( "unsafe" ) -//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) //sys Fchown(fd int, uid int, gid int) (err error) //sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64 //sys Fstatat(dirfd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64 diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go b/vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go index 7286a9aa88..33f7af3804 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go @@ -6,7 +6,6 @@ package unix -//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) //sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64 //sys Fchown(fd int, uid int, gid int) (err error) //sys Fstat(fd int, stat *Stat_t) (err error) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go b/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go index fc5543c5ff..c658871e30 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go @@ -8,7 +8,6 @@ package unix import "unsafe" -//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) = SYS_EPOLL_PWAIT //sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64 //sys Fchown(fd int, uid int, gid int) (err error) //sys Fstat(fd int, stat *Stat_t) (err error) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_s390x.go b/vendor/golang.org/x/sys/unix/syscall_linux_s390x.go index 66f31210d0..2c8587691b 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_s390x.go @@ -10,7 +10,6 @@ import ( "unsafe" ) -//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) //sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64 //sys Fchown(fd int, uid int, gid int) (err error) //sys Fstat(fd int, stat *Stat_t) (err error) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go b/vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go index 11d1f16986..4964119af8 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go @@ -6,7 +6,6 @@ package unix -//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) //sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64 //sys Fchown(fd int, uid int, gid int) (err error) //sys Fstat(fd int, stat *Stat_t) (err error) diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index 9d72a6b73a..5bb51d7ae1 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -1359,6 +1359,7 @@ const ( FAN_UNLIMITED_MARKS = 0x20 FAN_UNLIMITED_QUEUE = 0x10 FD_CLOEXEC = 0x1 + FD_PIDFS_ROOT = -0x2712 FD_SETSIZE = 0x400 FF0 = 0x0 FIB_RULE_DEV_DETACHED = 0x8 @@ -1970,6 +1971,8 @@ const ( MADV_DONTNEED = 0x4 MADV_DONTNEED_LOCKED = 0x18 MADV_FREE = 0x8 + MADV_GUARD_INSTALL = 0x66 + MADV_GUARD_REMOVE = 0x67 MADV_HUGEPAGE = 0xe MADV_HWPOISON = 0x64 MADV_KEEPONFORK = 0x13 @@ -2114,7 +2117,7 @@ const ( MS_NOSEC = 0x10000000 MS_NOSUID = 0x2 MS_NOSYMFOLLOW = 0x100 - MS_NOUSER = -0x80000000 + MS_NOUSER = 0x80000000 MS_POSIXACL = 0x10000 MS_PRIVATE = 0x40000 MS_RDONLY = 0x1 @@ -3786,6 +3789,9 @@ const ( TCPOPT_TIMESTAMP = 0x8 TCPOPT_TSTAMP_HDR = 0x101080a TCPOPT_WINDOW = 0x3 + TCP_AO_KEYF_EXCLUDE_OPT = 0x2 + TCP_AO_KEYF_IFINDEX = 0x1 + TCP_AO_MAXKEYLEN = 0x50 TCP_CC_INFO = 0x1a TCP_CM_INQ = 0x24 TCP_CONGESTION = 0xd diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/vendor/golang.org/x/sys/unix/zsyscall_linux.go index 80f40e4013..5788c2a58d 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux.go @@ -700,6 +700,23 @@ func EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { + var _p0 unsafe.Pointer + if len(events) > 0 { + _p0 = unsafe.Pointer(&events[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + r0, _, e1 := Syscall6(SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Eventfd(initval uint, flags int) (fd int, err error) { r0, _, e1 := Syscall(SYS_EVENTFD2, uintptr(initval), uintptr(flags), 0) fd = int(r0) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_386.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_386.go index 4def3e9fcb..254f33988f 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_386.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fadvise(fd int, offset int64, length int64, advice int) (err error) { _, _, e1 := Syscall6(SYS_FADVISE64_64, uintptr(fd), uintptr(offset), uintptr(offset>>32), uintptr(length), uintptr(length>>32), uintptr(advice)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go index fef2bc8ba9..27c05db1ac 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fadvise(fd int, offset int64, length int64, advice int) (err error) { _, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go index a9fd76a884..840d85bfc3 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go @@ -213,23 +213,6 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fchown(fd int, uid int, gid int) (err error) { _, _, e1 := Syscall(SYS_FCHOWN32, uintptr(fd), uintptr(uid), uintptr(gid)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go index 4600650280..fe414498b4 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fadvise(fd int, offset int64, length int64, advice int) (err error) { _, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_loong64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_loong64.go index c8987d2646..eb358ce05a 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_loong64.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fadvise(fd int, offset int64, length int64, advice int) (err error) { _, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go index 921f430611..c437622f14 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fadvise(fd int, offset int64, length int64, advice int) (err error) { _, _, e1 := Syscall9(SYS_FADVISE64, uintptr(fd), 0, uintptr(offset>>32), uintptr(offset), uintptr(length>>32), uintptr(length), uintptr(advice), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go index 44f067829c..bc4ca25582 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fadvise(fd int, offset int64, length int64, advice int) (err error) { _, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go index e7fa0abf0d..5051435ce7 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fadvise(fd int, offset int64, length int64, advice int) (err error) { _, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go index 8c5125675e..33aa5418a4 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fadvise(fd int, offset int64, length int64, advice int) (err error) { _, _, e1 := Syscall9(SYS_FADVISE64, uintptr(fd), 0, uintptr(offset), uintptr(offset>>32), uintptr(length), uintptr(length>>32), uintptr(advice), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc.go index 7392fd45e4..3bef8ef1d2 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fchown(fd int, uid int, gid int) (err error) { _, _, e1 := Syscall(SYS_FCHOWN, uintptr(fd), uintptr(uid), uintptr(gid)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go index 41180434e6..fc1bd4e2c4 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fadvise(fd int, offset int64, length int64, advice int) (err error) { _, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go index 40c6ce7ae5..d78fe7dabe 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fadvise(fd int, offset int64, length int64, advice int) (err error) { _, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_riscv64.go index 2cfe34adb1..76dcf87d01 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_riscv64.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fadvise(fd int, offset int64, length int64, advice int) (err error) { _, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go index 61e6f07097..2cf020f2ba 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fadvise(fd int, offset int64, length int64, advice int) (err error) { _, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go index 834b842042..527637623d 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go @@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { - var _p0 unsafe.Pointer - if len(events) > 0 { - _p0 = unsafe.Pointer(&events[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fadvise(fd int, offset int64, length int64, advice int) (err error) { _, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/windows/security_windows.go b/vendor/golang.org/x/sys/windows/security_windows.go index 6c955cea15..783621561e 100644 --- a/vendor/golang.org/x/sys/windows/security_windows.go +++ b/vendor/golang.org/x/sys/windows/security_windows.go @@ -1109,17 +1109,53 @@ const ( ) // This type is the union inside of TRUSTEE and must be created using one of the TrusteeValueFrom* functions. +// +// Go pointers stored in a TrusteeValue must be pinned using [runtime.Pinner] +// for the lifetime of the TrusteeValue. type TrusteeValue uintptr +// TrusteeValueFromString is unsafe and should not be used. +// +// It returns a uintptr containing a reference to newly-allocated memory +// which will be freed by the garbage collector. +// There is no way for the caller to safely reference this memory. +// +// To create a [TrusteeValue] from a string, use: +// +// p, err := windows.UTF16PtrFromString(s) +// if err != nil { +// // handle error +// } +// +// // Pin the string for as long as it is used. +// var pinner runtime.Pinner +// pinner.Pin(p) +// defer pinner.Unpin() +// +// tv := TrusteeValue(unsafe.Pointer(p)) +// +// Deprecated: TrusteeValueFromString is unsafe and should not be used. func TrusteeValueFromString(str string) TrusteeValue { return TrusteeValue(unsafe.Pointer(StringToUTF16Ptr(str))) } + +// TrusteeValueFromSID returns a [TrusteeValue] referencing sid. +// +// The caller must pin sid using a [runtime.Pinner] for the lifetime of the TrusteeValue. func TrusteeValueFromSID(sid *SID) TrusteeValue { return TrusteeValue(unsafe.Pointer(sid)) } + +// TrusteeValueFromObjectsAndSid returns a [TrusteeValue] referencing objectsAndSid. +// +// The caller must pin objectsAndSid using a [runtime.Pinner] for the lifetime of the TrusteeValue. func TrusteeValueFromObjectsAndSid(objectsAndSid *OBJECTS_AND_SID) TrusteeValue { return TrusteeValue(unsafe.Pointer(objectsAndSid)) } + +// TrusteeValueFromObjectsAndName returns a [TrusteeValue] referencing objectsAndName. +// +// The caller must pin objectsAndName using a [runtime.Pinner] for the lifetime of the TrusteeValue. func TrusteeValueFromObjectsAndName(objectsAndName *OBJECTS_AND_NAME) TrusteeValue { return TrusteeValue(unsafe.Pointer(objectsAndName)) } diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index 9755bca9fd..e6966b4c32 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -1728,11 +1728,15 @@ func (s *NTUnicodeString) String() string { // the more common *uint16 string type. func NewNTString(s string) (*NTString, error) { var nts NTString - s8, err := BytePtrFromString(s) + s8, err := ByteSliceFromString(s) if err != nil { return nil, err } - RtlInitString(&nts, s8) + // The source string plus its terminating NUL must fit within MAX_USHORT. + if len(s8) > MAX_USHORT { + return nil, syscall.EINVAL + } + RtlInitString(&nts, &s8[0]) return &nts, nil } diff --git a/vendor/golang.org/x/sys/windows/types_windows.go b/vendor/golang.org/x/sys/windows/types_windows.go index d2574a73ee..75a50b3165 100644 --- a/vendor/golang.org/x/sys/windows/types_windows.go +++ b/vendor/golang.org/x/sys/windows/types_windows.go @@ -169,6 +169,7 @@ const ( FORMAT_MESSAGE_ARGUMENT_ARRAY = 8192 FORMAT_MESSAGE_MAX_WIDTH_MASK = 255 + MAX_USHORT = 0xffff MAX_PATH = 260 MAX_LONG_PATH = 32768 diff --git a/vendor/golang.org/x/text/unicode/norm/forminfo.go b/vendor/golang.org/x/text/unicode/norm/forminfo.go index f3a234e5f5..b3cf5d9bde 100644 --- a/vendor/golang.org/x/text/unicode/norm/forminfo.go +++ b/vendor/golang.org/x/text/unicode/norm/forminfo.go @@ -121,8 +121,12 @@ func (p Properties) BoundaryAfter() bool { // // When all 6 bits are zero, the character is inert, meaning it is never // influenced by normalization. +// +// We set flags to 0x80 (high bit 7 unused in quick check data) to indicate an invalid rune. type qcInfo uint8 +func (p Properties) isInvalid() bool { return p.flags == 0x80 } + func (p Properties) isYesC() bool { return p.flags&0x10 == 0 } func (p Properties) isYesD() bool { return p.flags&0x4 == 0 } @@ -247,6 +251,9 @@ func (f Form) PropertiesString(s string) Properties { // to a Properties. See the comment at the top of the file // for more information on the format. func compInfo(v uint16, sz int) Properties { + if sz == 0 { + return Properties{flags: 0x80, size: 1} + } if v == 0 { return Properties{size: uint8(sz)} } else if v >= 0x8000 { @@ -254,7 +261,7 @@ func compInfo(v uint16, sz int) Properties { size: uint8(sz), ccc: uint8(v), tccc: uint8(v), - flags: qcInfo(v >> 8), + flags: qcInfo(v>>8) & 0x3f, } if p.ccc > 0 || p.combinesBackward() { p.nLead = uint8(p.flags & 0x3) diff --git a/vendor/golang.org/x/text/unicode/norm/iter.go b/vendor/golang.org/x/text/unicode/norm/iter.go index 417c6b2689..3cc059224d 100644 --- a/vendor/golang.org/x/text/unicode/norm/iter.go +++ b/vendor/golang.org/x/text/unicode/norm/iter.go @@ -376,16 +376,12 @@ func nextComposed(i *Iter) []byte { goto doNorm } prevCC = i.info.tccc - sz := int(i.info.size) - if sz == 0 { - sz = 1 // illegal rune: copy byte-by-byte - } - p := outp + sz + p := outp + int(i.info.size) if p > len(i.buf) { break } outp = p - i.p += sz + i.p += int(i.info.size) if i.p >= i.rb.nsrc { i.setDone() break diff --git a/vendor/golang.org/x/text/unicode/norm/normalize.go b/vendor/golang.org/x/text/unicode/norm/normalize.go index 4747ad07a8..60b1511caa 100644 --- a/vendor/golang.org/x/text/unicode/norm/normalize.go +++ b/vendor/golang.org/x/text/unicode/norm/normalize.go @@ -148,7 +148,7 @@ func (f Form) IsNormalString(s string) bool { // patched buffer and whether the decomposition is still in progress. func patchTail(rb *reorderBuffer) bool { info, p := lastRuneStart(&rb.f, rb.out) - if p == -1 || info.size == 0 { + if p == -1 || info.isInvalid() { return true } end := p + int(info.size) @@ -225,7 +225,7 @@ func doAppend(rb *reorderBuffer, out []byte, p int) []byte { } fd := &rb.f if doMerge { - var info Properties + info := Properties{flags: 0x80, size: 1} // invalid rune if p < n { info = fd.info(src, p) if !info.BoundaryBefore() || info.nLeadingNonStarters() > 0 { @@ -235,7 +235,7 @@ func doAppend(rb *reorderBuffer, out []byte, p int) []byte { p = decomposeSegment(rb, p, true) } } - if info.size == 0 { + if info.isInvalid() { rb.doFlush() // Append incomplete UTF-8 encoding. return src.appendSlice(rb.out, p, n) @@ -314,7 +314,7 @@ func (f *formInfo) quickSpan(src input, i, end int, atEOF bool) (n int, ok bool) continue } info := f.info(src, i) - if info.size == 0 { + if info.isInvalid() { if atEOF { // include incomplete runes return n, true @@ -379,7 +379,7 @@ func (f Form) firstBoundary(src input, nsrc int) int { // CGJ insertion points correctly. Luckily it doesn't have to. for { info := fd.info(src, i) - if info.size == 0 { + if info.isInvalid() { return -1 } if s := ss.next(info); s != ssSuccess { @@ -424,7 +424,7 @@ func (f Form) nextBoundary(src input, nsrc int, atEOF bool) int { } fd := formTable[f] info := fd.info(src, 0) - if info.size == 0 { + if info.isInvalid() { if atEOF { return 1 } @@ -435,7 +435,7 @@ func (f Form) nextBoundary(src input, nsrc int, atEOF bool) int { for i := int(info.size); i < nsrc; i += int(info.size) { info = fd.info(src, i) - if info.size == 0 { + if info.isInvalid() { if atEOF { return i } @@ -465,7 +465,7 @@ func lastBoundary(fd *formInfo, b []byte) int { if p == -1 { return -1 } - if info.size == 0 { // ends with incomplete rune + if info.isInvalid() { // ends with incomplete rune if p == 0 { // starts with incomplete rune return -1 } @@ -504,7 +504,7 @@ func lastBoundary(fd *formInfo, b []byte) int { func decomposeSegment(rb *reorderBuffer, sp int, atEOF bool) int { // Force one character to be consumed. info := rb.f.info(rb.src, sp) - if info.size == 0 { + if info.isInvalid() { return 0 } if s := rb.ss.next(info); s == ssStarter { @@ -528,7 +528,7 @@ func decomposeSegment(rb *reorderBuffer, sp int, atEOF bool) int { break } info = rb.f.info(rb.src, sp) - if info.size == 0 { + if info.isInvalid() { if !atEOF { return int(iShortSrc) } diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index de683684ab..1e5549abe4 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -815,6 +815,12 @@ func (ld *loader) refine(response *DriverResponse) ([]*Package, error) { needsrc: needsrc, goVersion: response.GoVersion, } + // Don't trust the driver to respond with duplicate-free + // package names (go.dev/issue/63822). + if _, ok := ld.pkgs[lpkg.ID]; ok { + return nil, fmt.Errorf("%s response contained duplicate packages for ID %q", + cond(ld.externalDriver, "go/packages driver", "go list"), lpkg.ID) + } ld.pkgs[lpkg.ID] = lpkg if rootIndex >= 0 { initial[rootIndex] = lpkg @@ -1589,3 +1595,11 @@ func usesExportData(cfg *Config) bool { } type unit struct{} + +func cond[T any](cond bool, t, f T) T { + if cond { + return t + } else { + return f + } +} diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go index 4c9450f4ee..686f171d3b 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go @@ -823,6 +823,9 @@ func (p *iexporter) doDecl(obj types.Object) { w.pos(m.Pos()) w.string(m.Name()) sig, _ := m.Type().(*types.Signature) + if w.p.version >= iexportVersionGenericMethods && w.bool(sig.TypeParams().Len() > 0) { + w.tparamList(obj.Name()+"."+m.Name(), sig.TypeParams(), obj.Pkg()) + } // Receiver type parameters are type arguments of the receiver type, so // their name must be qualified before exporting recv. diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go index 1ee4e93549..7b1723e0fc 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go @@ -48,13 +48,14 @@ func (r *intReader) uint64() uint64 { // Keep this in sync with constants in iexport.go. const ( - iexportVersionGo1_11 = 0 - iexportVersionPosCol = 1 - iexportVersionGo1_18 = 2 - iexportVersionGenerics = 2 - iexportVersion = iexportVersionGenerics - - iexportVersionCurrent = 2 + iexportVersionGo1_11 = 0 + iexportVersionPosCol = 1 + iexportVersionGo1_18 = 2 + iexportVersionGenerics = 2 + iexportVersionGenericMethods = 3 + iexportVersion = iexportVersionGenericMethods + + iexportVersionCurrent = 3 ) type ident struct { @@ -179,9 +180,9 @@ func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte version = int64(r.uint64()) switch version { - case iexportVersionGo1_18, iexportVersionPosCol, iexportVersionGo1_11: + case iexportVersionGenericMethods, iexportVersionGo1_18, iexportVersionPosCol, iexportVersionGo1_11: default: - if version > iexportVersionGo1_18 { + if version > iexportVersionGenericMethods { errorf("unstable iexport format version %d, just rebuild compiler and std library", version) } else { errorf("unknown iexport format version %d", version) @@ -614,6 +615,10 @@ func (r *importReader) obj(pkg *types.Package, name string) { for n := r.uint64(); n > 0; n-- { mpos := r.pos() mname := r.ident() + var tpars []*types.TypeParam + if r.p.version >= iexportVersionGenericMethods && r.bool() { + tpars = r.tparamList() + } recv := r.param(pkg) // If the receiver has any targs, set those as the @@ -628,8 +633,7 @@ func (r *importReader) obj(pkg *types.Package, name string) { rparams[i] = types.Unalias(targs.At(i)).(*types.TypeParam) } } - msig := r.signature(pkg, recv, rparams, nil) - + msig := r.signature(pkg, recv, rparams, tpars) named.AddMethod(types.NewFunc(mpos, pkg, mname, msig)) } } diff --git a/vendor/golang.org/x/tools/internal/imports/fix.go b/vendor/golang.org/x/tools/internal/imports/fix.go index d29ecfb017..b99ea6c914 100644 --- a/vendor/golang.org/x/tools/internal/imports/fix.go +++ b/vendor/golang.org/x/tools/internal/imports/fix.go @@ -273,7 +273,6 @@ func (p *pass) loadPackageNames(ctx context.Context, imports []*ImportInfo) erro } unknown = append(unknown, imp.ImportPath) } - names, err := p.source.LoadPackageNames(ctx, p.srcDir, unknown) if err != nil { return err diff --git a/vendor/golang.org/x/tools/internal/imports/imports.go b/vendor/golang.org/x/tools/internal/imports/imports.go index b5f5218b5c..072293835d 100644 --- a/vendor/golang.org/x/tools/internal/imports/imports.go +++ b/vendor/golang.org/x/tools/internal/imports/imports.go @@ -74,6 +74,10 @@ func Process(filename string, src []byte, opt *Options) (formatted []byte, err e // Note that filename's directory influences which imports can be chosen, // so it is important that filename be accurate. func FixImports(ctx context.Context, filename string, src []byte, goroot string, logf func(string, ...any), source Source) (fixes []*ImportFix, err error) { + if source == nil { + // In case someone adds a defective call from a new place + panic("source is nil") + } ctx, done := event.Start(ctx, "imports.FixImports") defer done() diff --git a/vendor/golang.org/x/tools/internal/stdlib/deps.go b/vendor/golang.org/x/tools/internal/stdlib/deps.go index dacfc1dfff..0f73c719cd 100644 --- a/vendor/golang.org/x/tools/internal/stdlib/deps.go +++ b/vendor/golang.org/x/tools/internal/stdlib/deps.go @@ -12,366 +12,386 @@ type pkginfo struct { } var deps = [...]pkginfo{ - {"archive/tar", "\x03q\x03F=\x01\n\x01$\x01\x01\x02\x05\b\x02\x01\x02\x02\r"}, - {"archive/zip", "\x02\x04g\a\x03\x13\x021=\x01+\x05\x01\x0f\x03\x02\x0f\x04"}, - {"bufio", "\x03q\x86\x01D\x15"}, - {"bytes", "t+[\x03\fH\x02\x02"}, + {"archive/tar", "\x03{\x03F>\x01\n\x01&\x01\x01\x02\x05\b\x02\x01\x02\x02\r"}, + {"archive/zip", "\x02\x04j\x0e\x03\x12\x022>\x01-\x05\x01\x0f\x03\x02\x0f\x04"}, + {"bufio", "\x03{\x87\x01F\x15"}, + {"bytes", "~*]\x03\fJ\x02\x02"}, {"cmp", ""}, - {"compress/bzip2", "\x02\x02\xf6\x01A"}, - {"compress/flate", "\x02r\x03\x83\x01\f\x033\x01\x03"}, - {"compress/gzip", "\x02\x04g\a\x03\x15nU"}, - {"compress/lzw", "\x02r\x03\x83\x01"}, - {"compress/zlib", "\x02\x04g\a\x03\x13\x01o"}, - {"container/heap", "\xbc\x02"}, + {"compress/bzip2", "\x02\x02\x81\x02C"}, + {"compress/flate", "\x02|\x03\x84\x01\f\x034\x02\x03"}, + {"compress/gzip", "\x02\x04j\x0e\x03\x14pW"}, + {"compress/lzw", "\x02|\x03\x84\x01"}, + {"compress/zlib", "\x02\x04j\x0e\x03\x12\x01q"}, + {"container/heap", "\xc9\x02"}, {"container/list", ""}, {"container/ring", ""}, - {"context", "t\\p\x01\x0e"}, - {"crypto", "\x8a\x01pC"}, - {"crypto/aes", "\x10\v\t\x99\x02"}, - {"crypto/cipher", "\x03!\x01\x01 \x12\x1c,Z"}, - {"crypto/des", "\x10\x16 .,\x9d\x01\x03"}, - {"crypto/dsa", "F\x03+\x86\x01\r"}, - {"crypto/ecdh", "\x03\v\r\x10\x04\x17\x03\x0f\x1c\x86\x01"}, - {"crypto/ecdsa", "\x0e\x05\x03\x05\x01\x10\b\v\x06\x01\x03\x0e\x01\x1c\x86\x01\r\x05L\x01"}, - {"crypto/ed25519", "\x0e\x1f\x12\a\x03\b\a\x1cI=C"}, - {"crypto/elliptic", "4@\x86\x01\r9"}, - {"crypto/fips140", "#\x05\x95\x01\x98\x01"}, - {"crypto/hkdf", "0\x15\x01.\x16"}, - {"crypto/hmac", "\x1b\x16\x14\x01\x122"}, - {"crypto/hpke", "\x03\v\x02\x03\x04\x01\f\x01\x05\x1f\x05\a\x01\x01\x1d\x03\x13\x16\x9b\x01\x1c"}, - {"crypto/internal/boring", "\x0e\x02\x0el"}, - {"crypto/internal/boring/bbig", "\x1b\xec\x01N"}, - {"crypto/internal/boring/bcache", "\xc1\x02\x14"}, + {"context", "~]r\x01\x0e"}, + {"crypto", "\x93\x01rE"}, + {"crypto/aes", "\x10\v\n\xa5\x02"}, + {"crypto/cipher", "\x03\"\x01\x01 \x13$+\\"}, + {"crypto/des", "\x10\x17 7+\xa1\x01\x03"}, + {"crypto/dsa", "G\x034\x87\x01\r"}, + {"crypto/ecdh", "\x03\v\r\x11\x04\x17\x03\x10$\x87\x01"}, + {"crypto/ecdsa", "\x0e\x05\x03\x05\x01\x11\b\v\x06\x01\x03\x0f\x01$\x87\x01\r\x05O\x01"}, + {"crypto/ed25519", "\x0e \x12\a\x03\t\a$I>E"}, + {"crypto/elliptic", "5I\x87\x01\r;"}, + {"crypto/fips140", "$\x05\x9e\x01\x9b\x01"}, + {"crypto/hkdf", "1\x15\x017\x15"}, + {"crypto/hmac", "\x1b\x17\x14\x01\x139"}, + {"crypto/hpke", "\x03\v\x02\x03\x04\x01\r\x01\x05\x1f\x06\a\x01\x01%\x03\x12\x16\x9f\x01\x1d"}, + {"crypto/internal/boring", "\x0e\x02\x0eu"}, + {"crypto/internal/boring/bbig", "\x1b\xf7\x01P"}, + {"crypto/internal/boring/bcache", "\xce\x02\x14"}, {"crypto/internal/boring/sig", ""}, {"crypto/internal/constanttime", ""}, - {"crypto/internal/cryptotest", "\x03\r\v\b%\x10\x19\x06\x13\x12 \x04\x06\t\x19\x01\x11\x11\x1b\x01\a\x05\b\x03\x05\f"}, - {"crypto/internal/entropy", "K"}, - {"crypto/internal/entropy/v1.0.0", "D0\x95\x018\x14"}, - {"crypto/internal/fips140", "C1\xbf\x01\v\x17"}, - {"crypto/internal/fips140/aes", "\x03 \x03\x02\x14\x05\x01\x01\x05,\x95\x014"}, - {"crypto/internal/fips140/aes/gcm", "#\x01\x02\x02\x02\x12\x05\x01\x06,\x92\x01"}, - {"crypto/internal/fips140/alias", "\xd5\x02"}, - {"crypto/internal/fips140/bigmod", "(\x19\x01\x06,\x95\x01"}, - {"crypto/internal/fips140/check", "#\x0e\a\t\x02\xb7\x01["}, - {"crypto/internal/fips140/check/checktest", "(\x8b\x02\""}, - {"crypto/internal/fips140/drbg", "\x03\x1f\x01\x01\x04\x14\x05\n)\x86\x01\x0f7\x01"}, - {"crypto/internal/fips140/ecdh", "\x03 \x05\x02\n\r3\x86\x01\x0f7"}, - {"crypto/internal/fips140/ecdsa", "\x03 \x04\x01\x02\a\x03\x06:\x16pF"}, - {"crypto/internal/fips140/ed25519", "\x03 \x05\x02\x04\f:\xc9\x01\x03"}, - {"crypto/internal/fips140/edwards25519", "\x1f\t\a\x123\x95\x017"}, - {"crypto/internal/fips140/edwards25519/field", "(\x14\x053\x95\x01"}, - {"crypto/internal/fips140/hkdf", "\x03 \x05\t\a<\x16"}, - {"crypto/internal/fips140/hmac", "\x03 \x15\x01\x01:\x16"}, - {"crypto/internal/fips140/mldsa", "\x03\x1c\x04\x05\x02\x0e\x01\x03\x053\x95\x017"}, - {"crypto/internal/fips140/mlkem", "\x03 \x05\x02\x0f\x03\x053\xcc\x01"}, - {"crypto/internal/fips140/nistec", "\x1f\t\r\f3\x95\x01*\r\x15"}, - {"crypto/internal/fips140/nistec/fiat", "(\x148\x95\x01"}, - {"crypto/internal/fips140/pbkdf2", "\x03 \x05\t\a<\x16"}, - {"crypto/internal/fips140/rsa", "\x03\x1c\x04\x04\x01\x02\x0e\x01\x01\x028\x16pF"}, - {"crypto/internal/fips140/sha256", "\x03 \x1e\x01\x06,\x16\x7f"}, - {"crypto/internal/fips140/sha3", "\x03 \x19\x05\x012\x95\x01L"}, - {"crypto/internal/fips140/sha512", "\x03 \x1e\x01\x06,\x16\x7f"}, - {"crypto/internal/fips140/ssh", "(b"}, - {"crypto/internal/fips140/subtle", "\x1f\a\x1b\xc8\x01"}, - {"crypto/internal/fips140/tls12", "\x03 \x05\t\a\x02:\x16"}, - {"crypto/internal/fips140/tls13", "\x03 \x05\b\b\t3\x16"}, - {"crypto/internal/fips140cache", "\xb3\x02\r'"}, + {"crypto/internal/cryptotest", "\x03\r\v\t%\x11\x1a\r\x12\x12!\x04\x06\n\x19\x01\x11\x11\x1d\x01\a\x03\x02\b\x02\x01\x05\f"}, + {"crypto/internal/cryptotest/wycheproof", "\x0e\x12S\x01\f\x01r@\x05\x03\x15\x10"}, + {"crypto/internal/entropy", "L"}, + {"crypto/internal/entropy/v1.0.0", "E9\x96\x01:\x14"}, + {"crypto/internal/fips140", "D:\xc2\x01\v\x17"}, + {"crypto/internal/fips140/aes", "\x03!\x03\x02\x14\x05\x01\x01\x055\x96\x016"}, + {"crypto/internal/fips140/aes/gcm", "$\x01\x02\x02\x02\x12\x05\x01\x065\x93\x01"}, + {"crypto/internal/fips140/alias", "\xe2\x02"}, + {"crypto/internal/fips140/bigmod", ")\x19\x01\x065\x96\x01"}, + {"crypto/internal/fips140/check", "$\x0e\a\t\x02\xc1\x01]"}, + {"crypto/internal/fips140/check/checktest", ")\x97\x02\""}, + {"crypto/internal/fips140/drbg", "\x03 \x01\x01\x04\x14\x05\n2\x87\x01\x0f9\x01"}, + {"crypto/internal/fips140/ecdh", "\x03!\x05\x02\n\r<\x87\x01\x0f9"}, + {"crypto/internal/fips140/ecdsa", "\x03!\x04\x01\x02\a\x03\x06C\x15r\x0f9"}, + {"crypto/internal/fips140/ed25519", "\x03!\x05\x02\x04\fC\xcc\x01\x03"}, + {"crypto/internal/fips140/edwards25519", "\x1f\n\a\x12<\x96\x019"}, + {"crypto/internal/fips140/edwards25519/field", ")\x14\x05<\x96\x01"}, + {"crypto/internal/fips140/hkdf", "\x03!\x05\t\aE\x15"}, + {"crypto/internal/fips140/hmac", "\x03!\x15\x01\x01C\x15"}, + {"crypto/internal/fips140/mldsa", "\x03\x1c\x05\x05\x02\x0e\x01\x03\x05<\x96\x019"}, + {"crypto/internal/fips140/mlkem", "\x03!\x05\x02\x0f\x03\x05<\xcf\x01"}, + {"crypto/internal/fips140/nistec", "\x1f\n\r\f<\x96\x01,\r\x15"}, + {"crypto/internal/fips140/nistec/fiat", ")\x14A\x96\x01"}, + {"crypto/internal/fips140/pbkdf2", "\x03!\x05\t\aE\x15"}, + {"crypto/internal/fips140/rsa", "\x03\x1c\x05\x04\x01\x02\x0e\x01\x01\x02A\x15rH"}, + {"crypto/internal/fips140/sha256", "\x03!\x1e\x01\x065\x15\x81\x01"}, + {"crypto/internal/fips140/sha3", "\x03!\x19\x05\x01;\x96\x01N"}, + {"crypto/internal/fips140/sha512", "\x03!\x1e\x01\x065\x15\x81\x01"}, + {"crypto/internal/fips140/ssh", ")j"}, + {"crypto/internal/fips140/subtle", "\x1f\b\x1b\xd2\x01"}, + {"crypto/internal/fips140/tls12", "\x03!\x05\t\a\x02C\x15"}, + {"crypto/internal/fips140/tls13", "\x03!\x05\b\b\t<\x15"}, + {"crypto/internal/fips140cache", "\xc0\x02\r."}, {"crypto/internal/fips140deps", ""}, - {"crypto/internal/fips140deps/byteorder", "\xa0\x01"}, - {"crypto/internal/fips140deps/cpu", "\xb5\x01\a"}, - {"crypto/internal/fips140deps/godebug", "\xbd\x01"}, - {"crypto/internal/fips140deps/time", "\xcf\x02"}, - {"crypto/internal/fips140hash", "9\x1d4\xcb\x01"}, - {"crypto/internal/fips140only", "\x17\x13\x0e\x01\x01Pp"}, + {"crypto/internal/fips140deps/byteorder", "\xa9\x01"}, + {"crypto/internal/fips140deps/cpu", "\xbe\x01\b"}, + {"crypto/internal/fips140deps/godebug", "\xc7\x01"}, + {"crypto/internal/fips140deps/time", "\xe2\x02"}, + {"crypto/internal/fips140hash", ":\x1e;\xcf\x01"}, + {"crypto/internal/fips140only", "\x17\x14\x0e\x01\x01Xr"}, {"crypto/internal/fips140test", ""}, - {"crypto/internal/impl", "\xbe\x02"}, - {"crypto/internal/rand", "\x1b\x0f s=["}, - {"crypto/internal/randutil", "\xfa\x01\x12"}, - {"crypto/internal/sysrand", "tq! \r\r\x01\x01\r\x06"}, - {"crypto/internal/sysrand/internal/seccomp", "t"}, - {"crypto/md5", "\x0e8.\x16\x16i"}, - {"crypto/mlkem", "\x0e%"}, - {"crypto/mlkem/mlkemtest", "3\x13\b&"}, - {"crypto/pbkdf2", "6\x0f\x01.\x16"}, - {"crypto/rand", "\x1b\x0f\x1c\x03+\x86\x01\rN"}, - {"crypto/rc4", "& .\xc9\x01"}, - {"crypto/rsa", "\x0e\r\x01\v\x10\x0e\x01\x03\b\a\x1c\x03\x133=\f\x01"}, - {"crypto/sha1", "\x0e\r+\x02,\x16\x16\x15T"}, - {"crypto/sha256", "\x0e\r\x1dR"}, - {"crypto/sha3", "\x0e+Q\xcb\x01"}, - {"crypto/sha512", "\x0e\r\x1fP"}, - {"crypto/subtle", "\x1f\x1d\x9f\x01z"}, - {"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x02\x01\x01\x01\t\x01\x18\x01\x0f\x01\x03\x01\x01\x01\x01\x02\x01\x02\x01\x17\x02\x03\x13\x16\x15\b=\x16\x16\r\b\x01\x01\x01\x02\x01\x0e\x06\x02\x01\x0f"}, - {"crypto/tls/internal/fips140tls", "\x17\xaa\x02"}, - {"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x017\x06\x01\x01\x02\x05\x0e\x06\x02\x02\x03F\x03:\x01\x02\b\x01\x01\x02\a\x10\x05\x01\x06\a\b\x02\x01\x02\x0f\x02\x01\x01\x02\x03\x01"}, - {"crypto/x509/pkix", "j\x06\a\x90\x01H"}, - {"database/sql", "\x03\nQ\x16\x03\x83\x01\v\a\"\x05\b\x02\x03\x01\x0e\x02\x02\x02"}, - {"database/sql/driver", "\rg\x03\xb7\x01\x0f\x12"}, - {"debug/buildinfo", "\x03^\x02\x01\x01\b\a\x03g\x1a\x02\x01+\x0f "}, - {"debug/dwarf", "\x03j\a\x03\x83\x011\x11\x01\x01"}, - {"debug/elf", "\x03\x06W\r\a\x03g\x1b\x01\f \x17\x01\x17"}, - {"debug/gosym", "\x03j\n$\xa1\x01\x01\x01\x02"}, - {"debug/macho", "\x03\x06W\r\ng\x1c,\x17\x01"}, - {"debug/pe", "\x03\x06W\r\a\x03g\x1c,\x17\x01\x17"}, - {"debug/plan9obj", "m\a\x03g\x1c,"}, - {"embed", "t+B\x19\x01T"}, + {"crypto/internal/impl", "\xcb\x02"}, + {"crypto/internal/rand", "\x1b\x10 |>]"}, + {"crypto/internal/randutil", "\x85\x02\x12"}, + {"crypto/internal/sysrand", "~r!\"\r\r\x01\x01\r\x06"}, + {"crypto/internal/sysrand/internal/seccomp", "~"}, + {"crypto/md5", "\x0e97\x15\x16k"}, + {"crypto/mldsa", "\x0e%K\x87\x01"}, + {"crypto/mlkem", "\x0e&"}, + {"crypto/mlkem/mlkemtest", "4\x13\t."}, + {"crypto/pbkdf2", "7\x0f\x017\x15"}, + {"crypto/rand", "\x1b\x10\x1c\x034\x87\x01\rP"}, + {"crypto/rc4", "' 7\xcc\x01"}, + {"crypto/rsa", "\x0e\r\x01\f\x10\x0e\x01\x03\t\a$\x03\x124>\f\x01"}, + {"crypto/sha1", "\x0e\r,\x025\x15\x16\x15V"}, + {"crypto/sha256", "\x0e\r\x1eZ"}, + {"crypto/sha3", "\x0e,Y\xcf\x01"}, + {"crypto/sha512", "\x0e\r X"}, + {"crypto/subtle", "\x1f\x1e\xa9\x01|"}, + {"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x01\x01\x01\x01\x01\n\x01\x18\x01\x0f\x01\x01\x03\x01\x01\x01\x01\x02\x01\x02\x01\x1f\x02\x03\x12\x16\x15\t>\x16\x18\r\b\x01\x01\x01\x02\x01\x0e\x06\x03\x01\x15"}, + {"crypto/tls/internal/fips140tls", "\x17\xb7\x02"}, + {"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x01\x017\x01\x06\x01\x01\x02\x05\x0f\x06\t\x02\x03F\x03;\x01\x02\b\x01\x01\x02\a\x12\x05\x01\x06\a\b\x02\x01\x02\x0f\x02\x01\x01\x02\x04\x01"}, + {"crypto/x509/pkix", "m\x06\x0e\x91\x019\x11"}, + {"database/sql", "\x03\nS\x01\x1d\x03\x84\x01\v\a$\x05\b\x02\x03\x01\x0e\x02\x02\x02\x01"}, + {"database/sql/driver", "\rT\x1d\x03\xba\x01\x0f\x12\a"}, + {"database/sql/internal", ""}, + {"debug/buildinfo", "\x03a\x02\x01\x01\b\x0e\x03h\x1a\x02\x01-\x0f "}, + {"debug/dwarf", "\x03m\x0e\x03\x84\x013\x11\x01\x01"}, + {"debug/elf", "\x03\x06Z\r\x0e\x03h\x1b\x01\f\"\x17\x01\x17"}, + {"debug/gosym", "\x03m\x11#\xa5\x01\x01\x01\x02"}, + {"debug/macho", "\x03\x06Z\r\x11h\x1c.\x17\x01"}, + {"debug/pe", "\x03\x06Z\r\x0e\x03h\x1c.\x17\x01\x17"}, + {"debug/plan9obj", "p\x0e\x03h\x1c."}, + {"embed", "~*D\x19\x01V"}, {"embed/internal/embedtest", ""}, {"encoding", ""}, - {"encoding/ascii85", "\xfa\x01C"}, - {"encoding/asn1", "\x03q\x03g(\x01'\r\x02\x01\x11\x03\x01"}, - {"encoding/base32", "\xfa\x01A\x02"}, - {"encoding/base64", "\xa0\x01ZA\x02"}, - {"encoding/binary", "t\x86\x01\f(\r\x05"}, - {"encoding/csv", "\x02\x01q\x03\x83\x01D\x13\x02"}, - {"encoding/gob", "\x02f\x05\a\x03g\x1c\v\x01\x03\x1d\b\x12\x01\x10\x02"}, - {"encoding/hex", "t\x03\x83\x01A\x03"}, - {"encoding/json", "\x03\x01d\x04\b\x03\x83\x01\f(\r\x02\x01\x02\x11\x01\x01\x02"}, - {"encoding/pem", "\x03i\b\x86\x01A\x03"}, - {"encoding/xml", "\x02\x01e\f\x03\x83\x014\x05\n\x01\x02\x11\x02"}, - {"errors", "\xd0\x01\x85\x01"}, - {"expvar", "qLA\b\v\x15\r\b\x02\x03\x01\x12"}, - {"flag", "h\f\x03\x83\x01,\b\x05\b\x02\x01\x11"}, - {"fmt", "tF'\x19\f \b\r\x02\x03\x13"}, - {"go/ast", "\x03\x01s\x0f\x01s\x03)\b\r\x02\x01\x13\x02"}, - {"go/build", "\x02\x01q\x03\x01\x02\x02\b\x02\x01\x17\x1f\x04\x02\b\x1c\x13\x01+\x01\x04\x01\a\b\x02\x01\x13\x02\x02"}, - {"go/build/constraint", "t\xc9\x01\x01\x13\x02"}, - {"go/constant", "w\x10\x7f\x01\x024\x01\x02\x13"}, - {"go/doc", "\x04s\x01\x05\n=61\x10\x02\x01\x13\x02"}, - {"go/doc/comment", "\x03t\xc4\x01\x01\x01\x01\x13\x02"}, - {"go/format", "\x03t\x01\f\x01\x02sD"}, - {"go/importer", "y\a\x01\x02\x04\x01r9"}, - {"go/internal/gccgoimporter", "\x02\x01^\x13\x03\x04\f\x01p\x02,\x01\x05\x11\x01\r\b"}, - {"go/internal/gcimporter", "\x02u\x10\x010\x05\r0,\x15\x03\x02"}, - {"go/internal/scannerhooks", "\x87\x01"}, - {"go/internal/srcimporter", "w\x01\x01\v\x03\x01r,\x01\x05\x12\x02\x15"}, - {"go/parser", "\x03q\x03\x01\x02\b\x04\x01s\x01+\x06\x12"}, - {"go/printer", "w\x01\x02\x03\ns\f \x15\x02\x01\x02\f\x05\x02"}, - {"go/scanner", "\x03t\v\x05s2\x10\x01\x14\x02"}, - {"go/token", "\x04s\x86\x01>\x02\x03\x01\x10\x02"}, - {"go/types", "\x03\x01\x06j\x03\x01\x03\t\x03\x024\x063\x04\x03\t \x06\a\b\x01\x01\x01\x02\x01\x10\x02\x02"}, - {"go/version", "\xc2\x01|"}, - {"hash", "\xfa\x01"}, - {"hash/adler32", "t\x16\x16"}, - {"hash/crc32", "t\x16\x16\x15\x8b\x01\x01\x14"}, - {"hash/crc64", "t\x16\x16\xa0\x01"}, - {"hash/fnv", "t\x16\x16i"}, - {"hash/maphash", "\x8a\x01\x11<~"}, - {"html", "\xbe\x02\x02\x13"}, - {"html/template", "\x03n\x06\x19-=\x01\n!\x05\x01\x02\x03\f\x01\x02\r\x01\x03\x02"}, - {"image", "\x02r\x1fg\x0f4\x03\x01"}, + {"encoding/ascii85", "\x85\x02E"}, + {"encoding/asn1", "\x03{\x03h(\x01)\r\x02\x01\x11\x03\x01"}, + {"encoding/base32", "\x85\x02C\x02"}, + {"encoding/base64", "\xa9\x01\\C\x02"}, + {"encoding/binary", "~\x87\x01\f*\r\x05"}, + {"encoding/csv", "\x02\x01{\x03\x84\x01F\x13\x02"}, + {"encoding/gob", "\x02i\x05\x0e\x03h\x1c\v\x01\x03\x1f\b\x12\x01\x10\x02"}, + {"encoding/hex", "~\x03\x84\x01C\x03"}, + {"encoding/json", "\x03\x01g\n\x01\x01\x02\x01\x01\x03\x03\x84\x016\x0f\x01"}, + {"encoding/json/internal", "~"}, + {"encoding/json/internal/jsonflags", "u"}, + {"encoding/json/internal/jsonopts", "u\x01"}, + {"encoding/json/internal/jsontest", "\x03f\x15\x03\x83\x01\x01\x012\b\b\x03\x02\x0f"}, + {"encoding/json/internal/jsonwire", "\x04r\b\x87\x01\f7\x02\x01\x13\x01\x01"}, + {"encoding/json/jsontext", "\x03r\x01\x01\x02\x05\x87\x01\x03\t\x034\x02\x01\x02\x13"}, + {"encoding/json/v2", "\x03\x01g\x03\x01\x01\x03\x02\x01\x01\x02\x01\x04\x03\x84\x01\f\x03'\r\x02\x01\x02\x0f\x02\x02"}, + {"encoding/pem", "\x03l\x0f\x87\x01C\x03"}, + {"encoding/xml", "\x02\x01h\x13\x03\x84\x016\x05\n\x01\x02\x11\x02"}, + {"errors", "\xdb\x01\x87\x01"}, + {"expvar", "tSB\b\v\x17\r\b\x02\x03\x01\x12"}, + {"flag", "k\x13\x03\x84\x01.\b\x05\b\x02\x01\x11"}, + {"fmt", "~E)\x19\f\"\b\r\x02\x03\x13"}, + {"go/ast", "\x03\x01}\x0e\x01u\x03+\b\r\x02\x01\x13\x02"}, + {"go/build", "\x02\x01{\x03\x01\x02\x02\a\x02\x01\x17 \x04\x02\t\x1c\x13\x01-\x01\x04\x01\a\b\x02\x01\x13\x02\x02"}, + {"go/build/constraint", "~\xcc\x01\x01\x13\x02"}, + {"go/constant", "\x81\x01\x0f\x81\x01\x01\x026\x01\x02\x13"}, + {"go/doc", "\x04}\x01\x05\t>73\x10\x02\x01\x13\x02"}, + {"go/doc/comment", "\x03~\xc7\x01\x01\x01\x01\x13\x02"}, + {"go/format", "\x03~\x01\v\x01\x02uF"}, + {"go/importer", "\x83\x01\a\x01\x01\x04\x01t;"}, + {"go/internal/gccgoimporter", "\x02\x01a\x1a\x03\x04\v\x01r\x02.\x01\x05\x11\x01\r\b"}, + {"go/internal/gcimporter", "\x02\x7f\x0f\x010\x140.\x15\x03\x02"}, + {"go/internal/srcimporter", "\x81\x01\x01\x01\n\x03\x01t.\x01\x05\x12\x02\x15"}, + {"go/parser", "\x03{\x03\x01\x02\v\x01u\x01-\x06\x12"}, + {"go/printer", "\x81\x01\x01\x02\x03\tu\f\"\x15\x02\x01\x02\f\x05\x02"}, + {"go/scanner", "\x03~\x0fu4\x10\x01\x14\x02"}, + {"go/token", "\x04}\x87\x01@\x02\x03\x01\x10\x02"}, + {"go/types", "\x03\x01\x06t\x03\x01\x03\b\x03\x02\x0654\x04\x03\t\"\x06\a\b\x01\x01\x01\x02\x01\x10\x02\x02"}, + {"go/version", "\xcc\x01\x7f"}, + {"hash", "\x85\x02"}, + {"hash/adler32", "~\x15\x16"}, + {"hash/crc32", "~\x15\x16\x15\x8f\x01\x01\x14"}, + {"hash/crc64", "~\x15\x16\xa4\x01"}, + {"hash/fnv", "~\x15\x16k"}, + {"hash/maphash", "\x93\x01\x11>\x80\x01"}, + {"html", "\xcb\x02\x02\x13"}, + {"html/template", "\x03q\r\x18.>\x01\n#\x05\x01\x02\x03\n\x02\x01\x02\r\x01\x03\x02"}, + {"image", "\x02|\x1ei\x0f6\x03\x01"}, {"image/color", ""}, - {"image/color/palette", "\x93\x01"}, - {"image/draw", "\x92\x01\x01\x04"}, - {"image/gif", "\x02\x01\x05l\x03\x1b\x01\x01\x01\vZ\x0f"}, - {"image/internal/imageutil", "\x92\x01"}, - {"image/jpeg", "\x02r\x1e\x01\x04c"}, - {"image/png", "\x02\ad\n\x13\x02\x06\x01gC"}, - {"index/suffixarray", "\x03j\a\x86\x01\f+\n\x01"}, - {"internal/abi", "\xbc\x01\x99\x01"}, - {"internal/asan", "\xd5\x02"}, - {"internal/bisect", "\xb3\x02\r\x01"}, - {"internal/buildcfg", "wHg\x06\x02\x05\n\x01"}, - {"internal/bytealg", "\xb5\x01\xa0\x01"}, + {"image/color/palette", "\x9c\x01"}, + {"image/draw", "\x9b\x01\x01\x04"}, + {"image/gif", "\x02\x01\x05v\x03\x1a\x01\x01\x01\v\\\x0f"}, + {"image/internal/imageutil", "\x9b\x01"}, + {"image/jpeg", "\x02|\x1d\x01\x04e"}, + {"image/png", "\x02\ag\x11\x12\x02\x06\x01iE"}, + {"index/suffixarray", "\x03m\x0e\x87\x01\f-\n\x01"}, + {"internal/abi", "\xc6\x01\x9c\x01"}, + {"internal/asan", "\xe2\x02"}, + {"internal/bisect", "\xc0\x02\r\x01"}, + {"internal/buildcfg", "\x81\x01Hj\x06\x02\x05\n\x01"}, + {"internal/bytealg", "\xbe\x01\xa4\x01"}, {"internal/byteorder", ""}, {"internal/cfg", ""}, - {"internal/cgrouptest", "w[T\x06\x0f\x02\x01\x04\x01"}, - {"internal/chacha8rand", "\xa0\x01\x15\a\x99\x01"}, + {"internal/cgrouptest", "\x81\x01\\V\x06\x0f\x02\x01\x04\x01"}, + {"internal/chacha8rand", "\xa9\x01\x15\b\x9c\x01"}, {"internal/copyright", ""}, {"internal/coverage", ""}, {"internal/coverage/calloc", ""}, - {"internal/coverage/cfile", "q\x06\x17\x17\x01\x02\x01\x01\x01\x01\x01\x01\x01\"\x02',\x06\a\n\x01\x03\x0e\x06"}, - {"internal/coverage/cformat", "\x04s.\x04Q\v6\x01\x02\x0e"}, - {"internal/coverage/cmerge", "w.a"}, - {"internal/coverage/decodecounter", "m\n.\v\x02H,\x17\x18"}, - {"internal/coverage/decodemeta", "\x02k\n\x17\x17\v\x02H,"}, - {"internal/coverage/encodecounter", "\x02k\n.\f\x01\x02F\v!\x15"}, - {"internal/coverage/encodemeta", "\x02\x01j\n\x13\x04\x17\r\x02F,/"}, - {"internal/coverage/pods", "\x04s.\x81\x01\x06\x05\n\x02\x01"}, - {"internal/coverage/rtcov", "\xd5\x02"}, - {"internal/coverage/slicereader", "m\n\x83\x01["}, - {"internal/coverage/slicewriter", "w\x83\x01"}, - {"internal/coverage/stringtab", "w9\x04F"}, + {"internal/coverage/cfile", "t\r\x16\x17\x01\x02\x01\x01\x01\x01\x01\x01\x01$\x02'.\x06\a\n\x01\x03\x0e\x06"}, + {"internal/coverage/cformat", "\x04}-\x04S\v8\x01\x02\x0e"}, + {"internal/coverage/cmerge", "\x81\x01-c"}, + {"internal/coverage/decodecounter", "p\x11-\v\x02J.\x17\x18"}, + {"internal/coverage/decodemeta", "\x02n\x11\x16\x17\v\x02J."}, + {"internal/coverage/encodecounter", "\x02n\x11-\f\x01\x02H\v#\x15"}, + {"internal/coverage/encodemeta", "\x02\x01m\x11\x12\x04\x17\r\x02H./"}, + {"internal/coverage/pods", "\x04}-\x85\x01\x06\x05\n\x02\x01"}, + {"internal/coverage/rtcov", "\xe2\x02"}, + {"internal/coverage/slicereader", "p\x11\x84\x01]"}, + {"internal/coverage/slicewriter", "\x81\x01\x84\x01"}, + {"internal/coverage/stringtab", "\x81\x018\x04H"}, {"internal/coverage/test", ""}, {"internal/coverage/uleb128", ""}, - {"internal/cpu", "\xd5\x02"}, - {"internal/dag", "\x04s\xc4\x01\x03"}, - {"internal/diff", "\x03t\xc5\x01\x02"}, - {"internal/exportdata", "\x02\x01q\x03\x02e\x1c,\x01\x05\x11\x01\x02"}, - {"internal/filepathlite", "t+B\x1a@"}, - {"internal/fmtsort", "\x04\xaa\x02\r"}, - {"internal/fuzz", "\x03\nH\x18\x04\x03\x03\x01\f\x036=\f\x03\x1d\x01\x05\x02\x05\n\x01\x02\x01\x01\r\x04\x02"}, + {"internal/cpu", "\xe2\x02"}, + {"internal/dag", "\x04}\xc7\x01\x03"}, + {"internal/diff", "\x03~\xc8\x01\x02"}, + {"internal/exportdata", "\x02\x01{\x03\x02f\x1c.\x01\x05\x11\x01\x02"}, + {"internal/filepathlite", "~*D\x1aB"}, + {"internal/fmtsort", "\x04\xb7\x02\r"}, + {"internal/fuzz", "\x03\nJ\x19\x04\n\x03\x01\v\x037>\f\x03\x1f\x01\x05\x02\x05\n\x01\x02\x01\x01\r\x04\x02"}, + {"internal/gate", "\r"}, {"internal/goarch", ""}, - {"internal/godebug", "\x9d\x01!\x82\x01\x01\x14"}, + {"internal/godebug", "\xa6\x01\"\x85\x01\x01\x14"}, {"internal/godebugs", ""}, {"internal/goexperiment", ""}, {"internal/goos", ""}, - {"internal/goroot", "\xa6\x02\x01\x05\x12\x02"}, + {"internal/goroot", "\xb3\x02\x01\x05\x12\x02"}, {"internal/gover", "\x04"}, {"internal/goversion", ""}, - {"internal/lazyregexp", "\xa6\x02\v\r\x02"}, - {"internal/lazytemplate", "\xfa\x01,\x18\x02\r"}, - {"internal/msan", "\xd5\x02"}, + {"internal/lazyregexp", "\xb3\x02\v\r\x02"}, + {"internal/lazytemplate", "\x85\x02.\x18\x02\r"}, + {"internal/msan", "\xe2\x02"}, + {"internal/nettest", "\x03\nqG@\f\n\x12\x06\x15\x05\x0f"}, {"internal/nettrace", ""}, - {"internal/obscuretestdata", "l\x8e\x01,"}, - {"internal/oserror", "t"}, - {"internal/pkgbits", "\x03R\x18\a\x03\x04\fs\r\x1f\r\n\x01"}, + {"internal/obscuretestdata", "o\x96\x01."}, + {"internal/oserror", "~"}, + {"internal/pkgbits", "\x03T\x19\x0e\x03\x04\vu\r!\r\n\x01"}, {"internal/platform", ""}, - {"internal/poll", "tl\x05\x159\r\x01\x01\r\x06"}, - {"internal/profile", "\x03\x04m\x03\x83\x017\n\x01\x01\x01\x11"}, + {"internal/poll", "~m\x05\x15;\r\x01\x01\r\x06"}, + {"internal/profile", "\x03\x04w\x03\x84\x019\n\x01\x01\x01\x11"}, {"internal/profilerecord", ""}, - {"internal/race", "\x9b\x01\xba\x01"}, - {"internal/reflectlite", "\x9b\x01!;<\""}, - {"internal/runtime/atomic", "\xbc\x01\x99\x01"}, - {"internal/runtime/cgroup", "\x9f\x01=\x04u"}, - {"internal/runtime/exithook", "\xd1\x01\x84\x01"}, - {"internal/runtime/gc", "\xbc\x01"}, - {"internal/runtime/gc/internal/gen", "\nc\n\x18k\x04\v\x1d\b\x10\x02"}, - {"internal/runtime/gc/scan", "\xb5\x01\a\x18\az"}, - {"internal/runtime/maps", "\x9b\x01\x01 \n\t\t\x03z"}, - {"internal/runtime/math", "\xbc\x01"}, + {"internal/race", "\xa4\x01\xbe\x01"}, + {"internal/reflectlite", "\xa4\x01\"<>\""}, + {"internal/runtime/atomic", "\xc6\x01\x9c\x01"}, + {"internal/runtime/cgroup", "\xa8\x01?\x04w"}, + {"internal/runtime/exithook", "\xdc\x01\x86\x01"}, + {"internal/runtime/gc", "\xc6\x01"}, + {"internal/runtime/gc/internal/gen", "\nf\x11\x17m\x04\v\x1f\b\x10\x02"}, + {"internal/runtime/gc/scan", "\xbe\x01\b\x19\a|"}, + {"internal/runtime/maps", "\xa4\x01\x01\x04\x15\b\x03\a\n\t\x03.N"}, + {"internal/runtime/math", "\xc6\x01"}, {"internal/runtime/pprof/label", ""}, {"internal/runtime/startlinetest", ""}, - {"internal/runtime/sys", "\xbc\x01\x04"}, - {"internal/runtime/syscall/linux", "\xbc\x01\x99\x01"}, + {"internal/runtime/sys", "\xc6\x01\x04"}, + {"internal/runtime/syscall/linux", "\xc6\x01\x9c\x01"}, {"internal/runtime/wasitest", ""}, - {"internal/saferio", "\xfa\x01["}, - {"internal/singleflight", "\xc0\x02"}, - {"internal/strconv", "\x89\x02L"}, - {"internal/stringslite", "\x9f\x01\xb6\x01"}, - {"internal/sync", "\x9b\x01!\x13r\x14"}, - {"internal/synctest", "\x9b\x01\xba\x01"}, - {"internal/syscall/execenv", "\xc2\x02"}, - {"internal/syscall/unix", "\xb3\x02\x0e\x01\x13"}, - {"internal/sysinfo", "\x02\x01\xb2\x01E,\x18\x02"}, + {"internal/saferio", "\x85\x02]"}, + {"internal/singleflight", "\xcd\x02"}, + {"internal/strconv", "\x94\x02N"}, + {"internal/stringslite", "\xa8\x01\xba\x01"}, + {"internal/sync", "\xa4\x01\"\x14t\x14"}, + {"internal/synctest", "\xa4\x01\xbe\x01"}, + {"internal/syscall/execenv", "\xcf\x02"}, + {"internal/syscall/unix", "\xeb\x01U\x0e\x01\x13"}, + {"internal/sysinfo", "\x02\x01\xbb\x01G.\x18\x02"}, {"internal/syslist", ""}, - {"internal/testenv", "\x03\ng\x02\x01*\x1b\x0f0+\x01\x05\a\n\x01\x02\x02\x01\f"}, - {"internal/testhash", "\x03\x87\x01p\x118\f"}, - {"internal/testlog", "\xc0\x02\x01\x14"}, - {"internal/testpty", "t\x03\xaf\x01"}, - {"internal/trace", "\x02\x01\x01\x06c\a\x03w\x03\x03\x06\x03\t+\n\x01\x01\x01\x11\x06"}, - {"internal/trace/internal/testgen", "\x03j\nu\x03\x02\x03\x011\v\r\x11"}, - {"internal/trace/internal/tracev1", "\x03\x01i\a\x03}\x06\f5\x01"}, - {"internal/trace/raw", "\x02k\nz\x03\x06C\x01\x13"}, - {"internal/trace/testtrace", "\x02\x01q\x03q\x04\x03\x05\x01\x05,\v\x02\b\x02\x01\x05"}, + {"internal/testenv", "\x03\nq\x02\x01)\x1c\x100-\x01\x05\a\n\x01\x02\x02\x01\f"}, + {"internal/testhash", "\x03\x90\x01r\x11:\f"}, + {"internal/testlog", "\xcd\x02\x01\x14"}, + {"internal/testpty", "~\x03\xb2\x01"}, + {"internal/trace", "\x02\x01\x01\x06f\x0e\x03x\x03\x03\x06\x03\t-\n\x01\x01\x01\x11\x06"}, + {"internal/trace/internal/testgen", "\x03m\x11v\x03\x02\x03\x013\v\r\x11"}, + {"internal/trace/internal/tracev1", "\x03\x01l\x0e\x03~\x06\f7\x01"}, + {"internal/trace/raw", "\x02n\x11{\x03\x06E\x01\x13"}, + {"internal/trace/testtrace", "\x02\x01{\x03r\x04\x03\x05\x01\x05.\v\x02\b\x02\x01\x05"}, {"internal/trace/tracev2", ""}, - {"internal/trace/traceviewer", "\x02d\v\x06\x1a<\x1f\a\a\x04\b\v\x15\x01\x05\a\n\x01\x02\x0f"}, + {"internal/trace/traceviewer", "\x02g\v\r\x19>\x1f\a\a\x04\b\v\x17\x01\x05\a\n\x01\x02\x0f"}, {"internal/trace/traceviewer/format", ""}, - {"internal/trace/version", "wz\t"}, - {"internal/txtar", "\x03t\xaf\x01\x18"}, - {"internal/types/errors", "\xbd\x02"}, - {"internal/unsafeheader", "\xd5\x02"}, - {"internal/xcoff", "`\r\a\x03g\x1c,\x17\x01"}, - {"internal/zstd", "m\a\x03\x83\x01\x0f"}, - {"io", "t\xcc\x01"}, - {"io/fs", "t+*11\x10\x14\x04"}, - {"io/ioutil", "\xfa\x01\x01+\x15\x03"}, - {"iter", "\xcf\x01d\""}, - {"log", "w\x83\x01\x05'\r\r\x01\x0e"}, + {"internal/trace/version", "\x81\x01{\t"}, + {"internal/txtar", "\x03~\xb2\x01\x18"}, + {"internal/types/errors", "\xca\x02"}, + {"internal/unsafeheader", "\xe2\x02"}, + {"internal/xcoff", "c\r\x0e\x03h\x1c.\x17\x01"}, + {"internal/zstd", "p\x0e\x03\x84\x01\x0f"}, + {"io", "~\xcf\x01"}, + {"io/fs", "~*,13\x10\x14\x04"}, + {"io/ioutil", "\x85\x02\x01-\x15\x03"}, + {"iter", "\xda\x01f\""}, + {"log", "\x81\x01\x84\x01\x05)\r\r\x01\x0e"}, {"log/internal", ""}, - {"log/slog", "\x03\n[\t\x03\x03\x83\x01\x04\x01\x02\x02\x03(\x05\b\x02\x01\x02\x01\x0e\x02\x02\x02"}, + {"log/slog", "\x03\n^\t\n\x03H<\x04\x01\x02\x02\x03*\x05\b\x02\x01\x02\x01\x0e\x02\x02\x02"}, {"log/slog/internal", ""}, - {"log/slog/internal/benchmarks", "\rg\x03\x83\x01\x06\x03:\x12"}, - {"log/slog/internal/buffer", "\xc0\x02"}, - {"log/syslog", "t\x03\x87\x01\x12\x16\x18\x02\x0f"}, - {"maps", "\xfd\x01X"}, - {"math", "\xb5\x01TL"}, - {"math/big", "\x03q\x03)\x15E\f\x03\x020\x02\x01\x02\x15"}, - {"math/big/internal/asmgen", "\x03\x01s\x92\x012\x03"}, - {"math/bits", "\xd5\x02"}, - {"math/cmplx", "\x86\x02\x03"}, - {"math/rand", "\xbd\x01I:\x01\x14"}, - {"math/rand/v2", "t,\x03c\x03L"}, - {"mime", "\x02\x01i\b\x03\x83\x01\v!\x15\x03\x02\x11\x02"}, - {"mime/multipart", "\x02\x01N#\x03F=\v\x01\a\x02\x15\x02\x06\x0f\x02\x01\x17"}, - {"mime/quotedprintable", "\x02\x01t\x83\x01"}, - {"net", "\x04\tg+\x1e\n\x05\x13\x01\x01\x04\x15\x01%\x06\r\b\x05\x01\x01\r\x06\a"}, - {"net/http", "\x02\x01\x03\x01\x04\x02D\b\x13\x01\a\x03F=\x01\x03\a\x01\x03\x02\x02\x01\x02\x06\x02\x01\x01\n\x01\x01\x05\x01\x02\x05\b\x01\x01\x01\x02\x01\x0e\x02\x02\x02\b\x01\x01\x01"}, - {"net/http/cgi", "\x02W\x1b\x03\x83\x01\x04\a\v\x01\x13\x01\x01\x01\x04\x01\x05\x02\b\x02\x01\x11\x0e"}, - {"net/http/cookiejar", "\x04p\x03\x99\x01\x01\b\a\x05\x16\x03\x02\x0f\x04"}, - {"net/http/fcgi", "\x02\x01\n`\a\x03\x83\x01\x16\x01\x01\x14\x18\x02\x0f"}, - {"net/http/httptest", "\x02\x01\nL\x02\x1b\x01\x83\x01\x04\x12\x01\n\t\x02\x17\x01\x02\x0f\x0e"}, - {"net/http/httptrace", "\rLnI\x14\n!"}, - {"net/http/httputil", "\x02\x01\ng\x03\x83\x01\x04\x0f\x03\x01\x05\x02\x01\v\x01\x19\x02\x01\x0e\x0e"}, - {"net/http/internal", "\x02\x01q\x03\x83\x01"}, - {"net/http/internal/ascii", "\xbe\x02\x13"}, - {"net/http/internal/httpcommon", "\rg\x03\x9f\x01\x0e\x01\x17\x01\x01\x02\x1d\x02"}, - {"net/http/internal/testcert", "\xbe\x02"}, - {"net/http/pprof", "\x02\x01\nj\x19-\x02\x0e-\x04\x13\x14\x01\r\x04\x03\x01\x02\x01\x11"}, + {"log/slog/internal/benchmarks", "\rq\x03\x84\x01\x06\x03<\x12"}, + {"log/slog/internal/buffer", "\xcd\x02"}, + {"log/syslog", "~\x03\x88\x01\x12\x18\x18\x02\x0f"}, + {"maps", "\x88\x02Z"}, + {"math", "\xbe\x01VN"}, + {"math/big", "\x03{\x03(\x15G\f\x03\x022\x02\x01\x02\x15"}, + {"math/big/internal/asmgen", "\x03\x01}\x93\x014\x03"}, + {"math/bits", "\xe2\x02"}, + {"math/cmplx", "\x91\x02\x03"}, + {"math/rand", "\xc7\x01J<\x01\x14"}, + {"math/rand/v2", "~+\x03e\x03N"}, + {"mime", "\x02\x01l\x0f\x03\x84\x01\v#\x15\x03\x02\x11\x02"}, + {"mime/multipart", "\x02\x01P+\x03F>\v\x01\a\x02\x17\x02\x06\x0f\x02\x01\x17"}, + {"mime/quotedprintable", "\x02\x01~\x84\x01"}, + {"net", "\x04\tq*\x1f\v\x05\x13\x01\x01\x04\x15\x01'\x06\r\b\x05\x01\x01\r\x06\t"}, + {"net/http", "\x02\x01\x03\x01\x04\x02N\x14\x0f\x03F>\x01\x03\a\x01\x06\x01\x01\x02\x06\x02\x01\x01\f\x01\x01\x05\x01\x02\x05\b\x01\x01\x01\x02\x01\x0e\x02\x02\x02\n\x01\x03"}, + {"net/http/cgi", "\x02Y#\x03\x84\x01\x04\a\v\x01\x15\x01\x01\x01\x04\x01\x05\x02\b\x02\x01\x11\x10"}, + {"net/http/cookiejar", "\x04z\x03\x9a\x01\x01\b\t\x05\x16\x03\x02\x0f\x04"}, + {"net/http/fcgi", "\x02\x01\nc\x0e\x03\x84\x01\x16\x01\x01\x16\x18\x02\x0f"}, + {"net/http/httptest", "\x02\x01\nN\x02#\x01P4\x04\x12\x01\f\t\x02\r\n\x01\x02\x03\f\x06\n"}, + {"net/http/httptrace", "\rNwI\x16+"}, + {"net/http/httputil", "\x02\x01\nq\x03F>\x04\x0f\x03\x01\x05\x02\x01\r\x01\x19\x02\x01\x0e\x10"}, + {"net/http/internal", "\x02\x01m\x0e\x03\x84\x01"}, + {"net/http/internal/ascii", "\xcb\x02\x13"}, + {"net/http/internal/http2", "\x02\x01\x03\x01\x06F\b\x15\x0e\x03\x84\x01\x01\x03\b\x03\x02\x03\x02\x06\x02\x03\x01\n\x01\x01\b\x05\b\x02\x01\x02\x01\x0e\x10\x02\x02"}, + {"net/http/internal/httpcommon", "\rq\x03\xa0\x01\x10\x01\x17\x01\x01\x02\x1f\x02"}, + {"net/http/internal/httpsfv", "\xc8\x02\x02\x01\x11\x04"}, + {"net/http/internal/testcert", "\xcb\x02"}, + {"net/http/pprof", "\x02\x01\nt\x18.\x11-\x04\x13\x16\x01\r\x04\x03\x01\x02\x01\x11"}, {"net/internal/cgotest", ""}, - {"net/internal/socktest", "w\xc9\x01\x02"}, - {"net/mail", "\x02r\x03\x83\x01\x04\x0f\x03\x14\x1a\x02\x0f\x04"}, - {"net/netip", "\x04p+\x01f\x034\x17"}, - {"net/rpc", "\x02m\x05\x03\x10\ni\x04\x12\x01\x1d\r\x03\x02"}, - {"net/rpc/jsonrpc", "q\x03\x03\x83\x01\x16\x11\x1f"}, - {"net/smtp", "\x194\f\x13\b\x03\x83\x01\x16\x14\x1a"}, - {"net/textproto", "\x02\x01q\x03\x83\x01\f\n-\x01\x02\x15"}, - {"net/url", "t\x03Fc\v\x10\x02\x01\x17"}, - {"os", "t+\x01\x19\x03\x10\x14\x01\x03\x01\x05\x10\x018\b\x05\x01\x01\r\x06"}, - {"os/exec", "\x03\ngI'\x01\x15\x01+\x06\a\n\x01\x03\x01\r"}, - {"os/exec/internal/fdtest", "\xc2\x02"}, - {"os/signal", "\r\x99\x02\x15\x05\x02"}, - {"os/user", "\x02\x01q\x03\x83\x01,\r\n\x01\x02"}, - {"path", "t+\xb4\x01"}, - {"path/filepath", "t+\x1aB+\r\b\x03\x04\x11"}, - {"plugin", "t"}, - {"reflect", "t'\x04\x1d\x13\b\x04\x05\x17\x06\t-\n\x03\x11\x02\x02"}, + {"net/internal/socktest", "\x81\x01\xcc\x01\x02"}, + {"net/mail", "\x02|\x03\x84\x01\x04\x0f\x03\x16\x1a\x02\x0f\x04"}, + {"net/netip", "\x04z*\x01h\x036\x17"}, + {"net/rpc", "\x02p\f\x03\x0f\nk\x04\x12\x01\x1f\r\x03\x02"}, + {"net/rpc/jsonrpc", "t\n\x03\x84\x01\x16\x13\x1f"}, + {"net/smtp", "\x195\r\x14\x0f\x03\x84\x01\x16\x16\x1a"}, + {"net/textproto", "\x02\x01{\x03\x84\x01\f\n/\x01\x02\x15"}, + {"net/url", "~\x03Ff\v\x10\x02\x01\x17"}, + {"os", "~*\x01\x19\x04\x11\x14\x01\x03\x01\x05\x10\x01:\b\x05\x01\x01\r\x06"}, + {"os/exec", "\x03\nqI(\x01\x15\x01-\x06\a\n\x01\x03\x01\r"}, + {"os/exec/internal/fdtest", "\xcf\x02"}, + {"os/signal", "\r\xa6\x02\x15\x05\x02"}, + {"os/user", "\x02\x01{\x03\x84\x01.\r\n\x01\x02"}, + {"path", "~*\xb8\x01"}, + {"path/filepath", "~*\x1aD-\r\b\x03\x04\x11"}, + {"plugin", "~"}, + {"reflect", "~&\x04\x1e\x03\x11\b\x04\x05\x17\x06\t/\n\x03\x11\x02\x02"}, {"reflect/internal/example1", ""}, {"reflect/internal/example2", ""}, - {"regexp", "\x03\xf7\x018\t\x02\x01\x02\x11\x02"}, - {"regexp/syntax", "\xbb\x02\x01\x01\x01\x02\x11\x02"}, - {"runtime", "\x9b\x01\x04\x01\x03\f\x06\a\x02\x01\x01\x0e\x03\x01\x01\x01\x02\x01\x01\x01\x02\x01\x04\x01\x10\x18L"}, - {"runtime/coverage", "\xa7\x01S"}, - {"runtime/debug", "wUZ\r\b\x02\x01\x11\x06"}, - {"runtime/metrics", "\xbe\x01H-\""}, - {"runtime/pprof", "\x02\x01\x01\x03\x06`\a\x03$$\x0f\v!\f \r\b\x01\x01\x01\x02\x02\n\x03\x06"}, - {"runtime/race", "\xb9\x02"}, + {"regexp", "\x03\x82\x02\x037\t\x02\x01\x02\x11\x02"}, + {"regexp/syntax", "\xc8\x02\x01\x01\x01\x02\x11\x02"}, + {"runtime", "\xa4\x01\x04\x01\x03\f\x06\b\x02\x01\x01\x0f\x03\x01\x01\x01\x02\x01\x01\x01\x02\x01\x04\x01\x10\x18N"}, + {"runtime/coverage", "\xb0\x01U"}, + {"runtime/debug", "\x81\x01V\\\r\b\x02\x01\x11\x06"}, + {"runtime/metrics", "\xc8\x01I/\""}, + {"runtime/pprof", "\x02\x01\x01\x03\x06c\x0e\x03#5\v!\f\"\r\b\x01\x01\x01\x02\x02\n\x03\x06"}, + {"runtime/race", "\xc6\x02"}, {"runtime/race/internal/amd64v1", ""}, - {"runtime/trace", "\rg\x03z\t9\b\x05\x01\x0e\x06"}, - {"slices", "\x04\xf9\x01\fL"}, - {"sort", "\xd0\x0192"}, - {"strconv", "t+A\x01r"}, - {"strings", "t'\x04B\x19\x03\f7\x11\x02\x02"}, + {"runtime/trace", "\rq\x03{\t;\b\x05\x01\x0e\x06"}, + {"slices", "\x04\x84\x02\fN"}, + {"sort", "\xdb\x0194"}, + {"strconv", "~*C\x01t"}, + {"strings", "~&\x04D\x19\x03\f9\x11\x02\x02"}, {"structs", ""}, - {"sync", "\xcf\x01\x13\x01P\x0e\x14"}, - {"sync/atomic", "\xd5\x02"}, - {"syscall", "t(\x03\x01\x1c\n\x03\x06\r\x04S\b\x05\x01\x14"}, - {"testing", "\x03\ng\x02\x01X\x17\x14\f\x05\x1b\x06\x02\x05\x02\x05\x01\x02\x01\x02\x01\x0e\x02\x04"}, - {"testing/cryptotest", "QOZ\x124\x03\x12"}, - {"testing/fstest", "t\x03\x83\x01\x01\n&\x10\x03\t\b"}, - {"testing/internal/testdeps", "\x02\v\xae\x01/\x10,\x03\x05\x03\x06\a\x02\x0f"}, - {"testing/iotest", "\x03q\x03\x83\x01\x04"}, - {"testing/quick", "v\x01\x8f\x01\x05#\x10\x11"}, - {"testing/slogtest", "\rg\x03\x89\x01.\x05\x10\f"}, - {"testing/synctest", "\xe3\x01`\x12"}, - {"text/scanner", "\x03t\x83\x01,+\x02"}, - {"text/tabwriter", "w\x83\x01Y"}, - {"text/template", "t\x03C@\x01\n \x01\x05\x01\x02\x05\v\x02\x0e\x03\x02"}, - {"text/template/parse", "\x03t\xbc\x01\n\x01\x13\x02"}, - {"time", "t+\x1e$(*\r\x02\x13"}, - {"time/tzdata", "t\xce\x01\x13"}, + {"sync", "\xda\x01\x02\x11\x01R\x0e\x14"}, + {"sync/atomic", "\xe2\x02"}, + {"syscall", "~'\x03\x01\x1d\n\x04\x06\r\x04U\b\x05\x01\x14"}, + {"testing", "\x03\nq\x02\x01Y\x17\x14\f\x05\x1d\x06\x02\x05\x02\x05\x01\x02\x01\x02\x01\x0e\x02\x04"}, + {"testing/cryptotest", "SV\\\x126\x03\x12"}, + {"testing/fstest", "~\x03\x84\x01\x01\n(\x10\x03\t\b"}, + {"testing/internal/testdeps", "\x02\v\xb7\x011\x10.\x03\x05\x03\x06\a\x02\x0f"}, + {"testing/iotest", "\x03{\x03\x84\x01\x04"}, + {"testing/quick", "\x80\x01\x01\x90\x01\x05%\x10\x11"}, + {"testing/slogtest", "\rq\x03\x8a\x010\x05\x10\f"}, + {"testing/synctest", "\xee\x01b\f\x06"}, + {"text/scanner", "\x03~\x84\x01.+\x02"}, + {"text/tabwriter", "\x81\x01\x84\x01["}, + {"text/template", "~\x03BB\x01\n\"\x01\x05\x01\x02\x05\v\x02\x0e\x03\x02"}, + {"text/template/parse", "\x03~\xbf\x01\n\x01\x13\x02"}, + {"time", "~*D(,\r\x02\x13"}, + {"time/tzdata", "~\xd1\x01\x13"}, {"unicode", ""}, {"unicode/utf16", ""}, {"unicode/utf8", ""}, - {"unique", "\x9b\x01!%\x01Q\r\x01\x14\x12"}, + {"unique", "\xa4\x01\"&\x01S\r\x01\x14\x19"}, {"unsafe", ""}, - {"vendor/golang.org/x/crypto/chacha20", "\x10]\a\x95\x01*'"}, - {"vendor/golang.org/x/crypto/chacha20poly1305", "\x10\aV\a\xe2\x01\x04\x01\a"}, - {"vendor/golang.org/x/crypto/cryptobyte", "j\n\x03\x90\x01'!\n"}, + {"uuid", "\x03\x01O\x1d\x03\v\xcf\x01\x0f"}, + {"vendor/golang.org/x/crypto/chacha20", "\x10`\x0e\x96\x01,)"}, + {"vendor/golang.org/x/crypto/chacha20poly1305", "\x10\aY\x0e\xe6\x01\x05\x01\f"}, + {"vendor/golang.org/x/crypto/cryptobyte", "m\x11\x03\x91\x01)!\v"}, {"vendor/golang.org/x/crypto/cryptobyte/asn1", ""}, - {"vendor/golang.org/x/crypto/internal/alias", "\xd5\x02"}, - {"vendor/golang.org/x/crypto/internal/poly1305", "X\x15\x9c\x01"}, - {"vendor/golang.org/x/net/dns/dnsmessage", "t\xc7\x01"}, - {"vendor/golang.org/x/net/http/httpguts", "\x90\x02\x14\x1a\x15\r"}, - {"vendor/golang.org/x/net/http/httpproxy", "t\x03\x99\x01\x10\x05\x01\x18\x15\r"}, - {"vendor/golang.org/x/net/http2/hpack", "\x03q\x03\x83\x01F"}, - {"vendor/golang.org/x/net/idna", "w\x8f\x018\x15\x10\x02\x01"}, - {"vendor/golang.org/x/net/nettest", "\x03j\a\x03\x83\x01\x11\x05\x16\x01\f\n\x01\x02\x02\x01\f"}, - {"vendor/golang.org/x/sys/cpu", "\xa6\x02\r\n\x01\x17"}, - {"vendor/golang.org/x/text/secure/bidirule", "t\xdf\x01\x11\x01"}, - {"vendor/golang.org/x/text/transform", "\x03q\x86\x01Y"}, - {"vendor/golang.org/x/text/unicode/bidi", "\x03\bl\x87\x01>\x17"}, - {"vendor/golang.org/x/text/unicode/norm", "m\n\x83\x01F\x13\x11"}, - {"weak", "\x9b\x01\x98\x01\""}, + {"vendor/golang.org/x/crypto/hkdf", "\x18\x01e\x15r"}, + {"vendor/golang.org/x/crypto/internal/alias", "\xe2\x02"}, + {"vendor/golang.org/x/crypto/internal/poly1305", "Z\x16\xa4\x01"}, + {"vendor/golang.org/x/net/dns/dnsmessage", "~\xca\x01"}, + {"vendor/golang.org/x/net/http/httpguts", "\x9b\x02\x16\x1a\x15\x10"}, + {"vendor/golang.org/x/net/http/httpproxy", "~\x03\x9a\x01\x12\x05\x01\x18\x15\x10"}, + {"vendor/golang.org/x/net/http2/hpack", "\x03{\x03\x84\x01H"}, + {"vendor/golang.org/x/net/http3", "\x9c\x02@\x06\x0f\x04"}, + {"vendor/golang.org/x/net/idna", "\x81\x01\x90\x01:\x13\x02\x17\x02\x01"}, + {"vendor/golang.org/x/net/internal/http3", "\rN#\x03\x84\x01\v\x04\a\x01\x05\x10\x01\x16\x02\x01\x02\x0f\x10\x02\x04\x03"}, + {"vendor/golang.org/x/net/internal/httpcommon", "\rq\x03\xa0\x01\x10\x01\x17\x01\x01\x02\x1f\x02"}, + {"vendor/golang.org/x/net/internal/quic/quicwire", "p"}, + {"vendor/golang.org/x/net/nettest", "\x03m\x0e\x03\x84\x01\x11\x05\x18\x01\f\n\x01\x02\x02\x01\f"}, + {"vendor/golang.org/x/net/quic", "\x03\n\x01\x01\x01\t:\x04\x04\x15\x03\v\x03\x12r\x06\x06\x06\x04\x12\x06\x15\x02\x01\x02\x01\x01\r\x06\x02\x01\x01\x02\v"}, + {"vendor/golang.org/x/sys/cpu", "\xb3\x02\r\n\x01\x17"}, + {"vendor/golang.org/x/text/secure/bidirule", "~\xe2\x01\x18\x01"}, + {"vendor/golang.org/x/text/transform", "\x03{\x87\x01["}, + {"vendor/golang.org/x/text/unicode/bidi", "\x03\bv\x88\x01@\x17"}, + {"vendor/golang.org/x/text/unicode/norm", "p\x11\x84\x01H\x13\x18"}, + {"weak", "\xa4\x01\x9c\x01\""}, } // bootstrap is the list of bootstrap packages extracted from cmd/dist. @@ -408,6 +428,7 @@ var bootstrap = map[string]bool{ "cmd/compile/internal/logopt": true, "cmd/compile/internal/loong64": true, "cmd/compile/internal/loopvar": true, + "cmd/compile/internal/midway": true, "cmd/compile/internal/mips": true, "cmd/compile/internal/mips64": true, "cmd/compile/internal/noder": true, @@ -512,6 +533,7 @@ var bootstrap = map[string]bool{ "internal/race": true, "internal/runtime/gc": true, "internal/saferio": true, + "internal/strconv": true, "internal/syscall/unix": true, "internal/types/errors": true, "internal/unsafeheader": true, diff --git a/vendor/golang.org/x/tools/internal/stdlib/manifest.go b/vendor/golang.org/x/tools/internal/stdlib/manifest.go index 33e4f505f3..2180c296ec 100644 --- a/vendor/golang.org/x/tools/internal/stdlib/manifest.go +++ b/vendor/golang.org/x/tools/internal/stdlib/manifest.go @@ -270,6 +270,7 @@ var PackageSymbols = map[string][]Symbol{ {"ContainsRune", Func, 7, "func(b []byte, r rune) bool"}, {"Count", Func, 0, "func(s []byte, sep []byte) int"}, {"Cut", Func, 18, "func(s []byte, sep []byte) (before []byte, after []byte, found bool)"}, + {"CutLast", Func, 27, "func(s []byte, sep []byte) (before []byte, after []byte, found bool)"}, {"CutPrefix", Func, 20, "func(s []byte, prefix []byte) (after []byte, found bool)"}, {"CutSuffix", Func, 20, "func(s []byte, suffix []byte) (before []byte, found bool)"}, {"Equal", Func, 0, "func(a []byte, b []byte) bool"}, @@ -538,6 +539,7 @@ var PackageSymbols = map[string][]Symbol{ {"MD4", Const, 0, ""}, {"MD5", Const, 0, ""}, {"MD5SHA1", Const, 0, ""}, + {"MLDSAMu", Const, 27, ""}, {"MessageSigner", Type, 25, ""}, {"PrivateKey", Type, 0, ""}, {"PublicKey", Type, 2, ""}, @@ -812,6 +814,40 @@ var PackageSymbols = map[string][]Symbol{ {"Size", Const, 0, ""}, {"Sum", Func, 2, "func(data []byte) [16]byte"}, }, + "crypto/mldsa": { + {"(*Options).HashFunc", Method, 27, ""}, + {"(*PrivateKey).Bytes", Method, 27, ""}, + {"(*PrivateKey).Equal", Method, 27, ""}, + {"(*PrivateKey).Public", Method, 27, ""}, + {"(*PrivateKey).PublicKey", Method, 27, ""}, + {"(*PrivateKey).Sign", Method, 27, ""}, + {"(*PrivateKey).SignDeterministic", Method, 27, ""}, + {"(*PublicKey).Bytes", Method, 27, ""}, + {"(*PublicKey).Equal", Method, 27, ""}, + {"(*PublicKey).Parameters", Method, 27, ""}, + {"(Parameters).PublicKeySize", Method, 27, ""}, + {"(Parameters).SignatureSize", Method, 27, ""}, + {"(Parameters).String", Method, 27, ""}, + {"GenerateKey", Func, 27, "func(params Parameters) (*PrivateKey, error)"}, + {"MLDSA44", Func, 27, "func() Parameters"}, + {"MLDSA44PublicKeySize", Const, 27, ""}, + {"MLDSA44SignatureSize", Const, 27, ""}, + {"MLDSA65", Func, 27, "func() Parameters"}, + {"MLDSA65PublicKeySize", Const, 27, ""}, + {"MLDSA65SignatureSize", Const, 27, ""}, + {"MLDSA87", Func, 27, "func() Parameters"}, + {"MLDSA87PublicKeySize", Const, 27, ""}, + {"MLDSA87SignatureSize", Const, 27, ""}, + {"NewPrivateKey", Func, 27, "func(params Parameters, seed []byte) (*PrivateKey, error)"}, + {"NewPublicKey", Func, 27, "func(params Parameters, encoding []byte) (*PublicKey, error)"}, + {"Options", Type, 27, ""}, + {"Options.Context", Field, 27, ""}, + {"Parameters", Type, 27, ""}, + {"PrivateKey", Type, 27, ""}, + {"PrivateKeySize", Const, 27, ""}, + {"PublicKey", Type, 27, ""}, + {"Verify", Func, 27, "func(pk *PublicKey, message []byte, signature []byte, opts *Options) error"}, + }, "crypto/mlkem": { {"(*DecapsulationKey1024).Bytes", Method, 24, ""}, {"(*DecapsulationKey1024).Decapsulate", Method, 24, ""}, @@ -1120,6 +1156,7 @@ var PackageSymbols = map[string][]Symbol{ {"ConnectionState.ECHAccepted", Field, 23, ""}, {"ConnectionState.HandshakeComplete", Field, 0, ""}, {"ConnectionState.HelloRetryRequest", Field, 26, ""}, + {"ConnectionState.LocalCertificate", Field, 27, ""}, {"ConnectionState.NegotiatedProtocol", Field, 0, ""}, {"ConnectionState.NegotiatedProtocolIsMutual", Field, 0, ""}, {"ConnectionState.OCSPResponse", Field, 5, ""}, @@ -1152,6 +1189,10 @@ var PackageSymbols = map[string][]Symbol{ {"InsecureCipherSuites", Func, 14, "func() []*CipherSuite"}, {"Listen", Func, 0, "func(network string, laddr string, config *Config) (net.Listener, error)"}, {"LoadX509KeyPair", Func, 0, "func(certFile string, keyFile string) (Certificate, error)"}, + {"MLDSA44", Const, 27, ""}, + {"MLDSA65", Const, 27, ""}, + {"MLDSA87", Const, 27, ""}, + {"MLKEM1024", Const, 27, ""}, {"NewLRUClientSessionCache", Func, 3, "func(capacity int) ClientSessionCache"}, {"NewListener", Func, 0, "func(inner net.Listener, config *Config) net.Listener"}, {"NewResumptionState", Func, 21, "func(ticket []byte, state *SessionState) (*ClientSessionState, error)"}, @@ -1166,6 +1207,7 @@ var PackageSymbols = map[string][]Symbol{ {"ParseSessionState", Func, 21, "func(data []byte) (*SessionState, error)"}, {"QUICClient", Func, 21, "func(config *QUICConfig) *QUICConn"}, {"QUICConfig", Type, 21, ""}, + {"QUICConfig.ClientHelloInfoConn", Field, 27, ""}, {"QUICConfig.EnableSessionEvents", Field, 23, ""}, {"QUICConfig.TLSConfig", Field, 21, ""}, {"QUICConn", Type, 21, ""}, @@ -1334,6 +1376,7 @@ var PackageSymbols = map[string][]Symbol{ {"Certificate.PublicKeyAlgorithm", Field, 0, ""}, {"Certificate.Raw", Field, 0, ""}, {"Certificate.RawIssuer", Field, 0, ""}, + {"Certificate.RawSignatureAlgorithm", Field, 27, ""}, {"Certificate.RawSubject", Field, 0, ""}, {"Certificate.RawSubjectPublicKeyInfo", Field, 0, ""}, {"Certificate.RawTBSCertificate", Field, 0, ""}, @@ -1362,6 +1405,7 @@ var PackageSymbols = map[string][]Symbol{ {"CertificateRequest.PublicKey", Field, 3, ""}, {"CertificateRequest.PublicKeyAlgorithm", Field, 3, ""}, {"CertificateRequest.Raw", Field, 3, ""}, + {"CertificateRequest.RawSignatureAlgorithm", Field, 27, ""}, {"CertificateRequest.RawSubject", Field, 3, ""}, {"CertificateRequest.RawSubjectPublicKeyInfo", Field, 3, ""}, {"CertificateRequest.RawTBSCertificateRequest", Field, 3, ""}, @@ -1422,6 +1466,10 @@ var PackageSymbols = map[string][]Symbol{ {"KeyUsageKeyEncipherment", Const, 0, ""}, {"MD2WithRSA", Const, 0, ""}, {"MD5WithRSA", Const, 0, ""}, + {"MLDSA", Const, 27, ""}, + {"MLDSA44", Const, 27, ""}, + {"MLDSA65", Const, 27, ""}, + {"MLDSA87", Const, 27, ""}, {"MarshalECPrivateKey", Func, 2, "func(key *ecdsa.PrivateKey) ([]byte, error)"}, {"MarshalPKCS1PrivateKey", Func, 0, "func(key *rsa.PrivateKey) []byte"}, {"MarshalPKCS1PublicKey", Func, 10, "func(key *rsa.PublicKey) []byte"}, @@ -1468,6 +1516,7 @@ var PackageSymbols = map[string][]Symbol{ {"RevocationList.Number", Field, 15, ""}, {"RevocationList.Raw", Field, 19, ""}, {"RevocationList.RawIssuer", Field, 19, ""}, + {"RevocationList.RawSignatureAlgorithm", Field, 27, ""}, {"RevocationList.RawTBSRevocationList", Field, 19, ""}, {"RevocationList.RevokedCertificateEntries", Field, 21, ""}, {"RevocationList.RevokedCertificates", Field, 15, ""}, @@ -1648,6 +1697,7 @@ var PackageSymbols = map[string][]Symbol{ {"(Scanner).Scan", Method, 0, ""}, {"ColumnType", Type, 8, ""}, {"Conn", Type, 9, ""}, + {"ConvertAssign", Func, 27, "func(scanCtx driver.ScanContext, dest any, src driver.Value) error"}, {"DB", Type, 0, ""}, {"DBStats", Type, 5, ""}, {"DBStats.Idle", Field, 11, ""}, @@ -1744,6 +1794,11 @@ var PackageSymbols = map[string][]Symbol{ {"(Rows).Next", Method, 0, ""}, {"(RowsAffected).LastInsertId", Method, 0, ""}, {"(RowsAffected).RowsAffected", Method, 0, ""}, + {"(RowsColumnScanner).Close", Method, 27, ""}, + {"(RowsColumnScanner).Columns", Method, 27, ""}, + {"(RowsColumnScanner).Next", Method, 27, ""}, + {"(RowsColumnScanner).NextRow", Method, 27, ""}, + {"(RowsColumnScanner).ScanColumn", Method, 27, ""}, {"(RowsColumnTypeDatabaseTypeName).Close", Method, 8, ""}, {"(RowsColumnTypeDatabaseTypeName).ColumnTypeDatabaseTypeName", Method, 8, ""}, {"(RowsColumnTypeDatabaseTypeName).Columns", Method, 8, ""}, @@ -1815,12 +1870,14 @@ var PackageSymbols = map[string][]Symbol{ {"ResultNoRows", Var, 0, ""}, {"Rows", Type, 0, ""}, {"RowsAffected", Type, 0, ""}, + {"RowsColumnScanner", Type, 27, ""}, {"RowsColumnTypeDatabaseTypeName", Type, 8, ""}, {"RowsColumnTypeLength", Type, 8, ""}, {"RowsColumnTypeNullable", Type, 8, ""}, {"RowsColumnTypePrecisionScale", Type, 8, ""}, {"RowsColumnTypeScanType", Type, 8, ""}, {"RowsNextResultSet", Type, 8, ""}, + {"ScanContext", Type, 27, ""}, {"SessionResetter", Type, 10, ""}, {"Stmt", Type, 0, ""}, {"StmtExecContext", Type, 8, ""}, @@ -5038,24 +5095,32 @@ var PackageSymbols = map[string][]Symbol{ {"(*InvalidUnmarshalError).Error", Method, 0, ""}, {"(*MarshalerError).Error", Method, 0, ""}, {"(*MarshalerError).Unwrap", Method, 13, ""}, + {"(*Number).UnmarshalJSONFrom", Method, 27, ""}, {"(*RawMessage).MarshalJSON", Method, 0, ""}, {"(*RawMessage).UnmarshalJSON", Method, 0, ""}, {"(*SyntaxError).Error", Method, 0, ""}, {"(*UnmarshalFieldError).Error", Method, 0, ""}, {"(*UnmarshalTypeError).Error", Method, 0, ""}, + {"(*UnmarshalTypeError).Unwrap", Method, 27, ""}, {"(*UnsupportedTypeError).Error", Method, 0, ""}, {"(*UnsupportedValueError).Error", Method, 0, ""}, {"(Delim).String", Method, 5, ""}, {"(Marshaler).MarshalJSON", Method, 0, ""}, {"(Number).Float64", Method, 1, ""}, {"(Number).Int64", Method, 1, ""}, + {"(Number).MarshalJSONTo", Method, 27, ""}, {"(Number).String", Method, 1, ""}, {"(RawMessage).MarshalJSON", Method, 8, ""}, {"(Unmarshaler).UnmarshalJSON", Method, 0, ""}, + {"CallMethodsWithLegacySemantics", Func, 27, "func(v bool) Options"}, {"Compact", Func, 0, "func(dst *bytes.Buffer, src []byte) error"}, {"Decoder", Type, 0, ""}, + {"DefaultOptionsV1", Func, 27, "func() Options"}, {"Delim", Type, 5, ""}, {"Encoder", Type, 0, ""}, + {"FormatByteArrayAsArray", Func, 27, "func(v bool) Options"}, + {"FormatBytesWithLegacySemantics", Func, 27, "func(v bool) Options"}, + {"FormatDurationAsNano", Func, 27, "func(v bool) Options"}, {"HTMLEscape", Func, 0, "func(dst *bytes.Buffer, src []byte)"}, {"Indent", Func, 0, "func(dst *bytes.Buffer, src []byte, prefix string, indent string) error"}, {"InvalidUTF8Error", Type, 0, ""}, @@ -5068,19 +5133,29 @@ var PackageSymbols = map[string][]Symbol{ {"MarshalerError", Type, 0, ""}, {"MarshalerError.Err", Field, 0, ""}, {"MarshalerError.Type", Field, 0, ""}, + {"MatchCaseSensitiveDelimiter", Func, 27, "func(v bool) Options"}, + {"MergeWithLegacySemantics", Func, 27, "func(v bool) Options"}, {"NewDecoder", Func, 0, "func(r io.Reader) *Decoder"}, {"NewEncoder", Func, 0, "func(w io.Writer) *Encoder"}, {"Number", Type, 1, ""}, + {"OmitEmptyWithLegacySemantics", Func, 27, "func(v bool) Options"}, + {"Options", Type, 27, ""}, + {"ParseBytesWithLooseRFC4648", Func, 27, "func(v bool) Options"}, + {"ParseTimeWithLooseRFC3339", Func, 27, "func(v bool) Options"}, {"RawMessage", Type, 0, ""}, + {"ReportErrorsWithLegacySemantics", Func, 27, "func(v bool) Options"}, + {"StringifyWithLegacySemantics", Func, 27, "func(v bool) Options"}, {"SyntaxError", Type, 0, ""}, {"SyntaxError.Offset", Field, 0, ""}, {"Token", Type, 5, ""}, {"Unmarshal", Func, 0, "func(data []byte, v any) error"}, + {"UnmarshalArrayFromAnyLength", Func, 27, "func(v bool) Options"}, {"UnmarshalFieldError", Type, 0, ""}, {"UnmarshalFieldError.Field", Field, 0, ""}, {"UnmarshalFieldError.Key", Field, 0, ""}, {"UnmarshalFieldError.Type", Field, 0, ""}, {"UnmarshalTypeError", Type, 0, ""}, + {"UnmarshalTypeError.Err", Field, 27, ""}, {"UnmarshalTypeError.Field", Field, 8, ""}, {"UnmarshalTypeError.Offset", Field, 5, ""}, {"UnmarshalTypeError.Struct", Field, 8, ""}, @@ -5094,6 +5169,158 @@ var PackageSymbols = map[string][]Symbol{ {"UnsupportedValueError.Value", Field, 0, ""}, {"Valid", Func, 9, "func(data []byte) bool"}, }, + "encoding/json/jsontext": { + {"(*Decoder).InputOffset", Method, 27, ""}, + {"(*Decoder).Options", Method, 27, ""}, + {"(*Decoder).PeekKind", Method, 27, ""}, + {"(*Decoder).ReadToken", Method, 27, ""}, + {"(*Decoder).ReadValue", Method, 27, ""}, + {"(*Decoder).Reset", Method, 27, ""}, + {"(*Decoder).SkipValue", Method, 27, ""}, + {"(*Decoder).StackDepth", Method, 27, ""}, + {"(*Decoder).StackIndex", Method, 27, ""}, + {"(*Decoder).StackPointer", Method, 27, ""}, + {"(*Decoder).UnreadBuffer", Method, 27, ""}, + {"(*Encoder).AvailableBuffer", Method, 27, ""}, + {"(*Encoder).Options", Method, 27, ""}, + {"(*Encoder).OutputOffset", Method, 27, ""}, + {"(*Encoder).Reset", Method, 27, ""}, + {"(*Encoder).StackDepth", Method, 27, ""}, + {"(*Encoder).StackIndex", Method, 27, ""}, + {"(*Encoder).StackPointer", Method, 27, ""}, + {"(*Encoder).WriteToken", Method, 27, ""}, + {"(*Encoder).WriteValue", Method, 27, ""}, + {"(*SyntacticError).Error", Method, 27, ""}, + {"(*SyntacticError).Unwrap", Method, 27, ""}, + {"(*Value).Canonicalize", Method, 27, ""}, + {"(*Value).Compact", Method, 27, ""}, + {"(*Value).Format", Method, 27, ""}, + {"(*Value).Indent", Method, 27, ""}, + {"(*Value).UnmarshalJSON", Method, 27, ""}, + {"(Kind).String", Method, 27, ""}, + {"(Pointer).AppendToken", Method, 27, ""}, + {"(Pointer).Contains", Method, 27, ""}, + {"(Pointer).IsValid", Method, 27, ""}, + {"(Pointer).LastToken", Method, 27, ""}, + {"(Pointer).Parent", Method, 27, ""}, + {"(Pointer).Tokens", Method, 27, ""}, + {"(Token).Bool", Method, 27, ""}, + {"(Token).Clone", Method, 27, ""}, + {"(Token).Float", Method, 27, ""}, + {"(Token).Float32", Method, 27, ""}, + {"(Token).Int", Method, 27, ""}, + {"(Token).Kind", Method, 27, ""}, + {"(Token).String", Method, 27, ""}, + {"(Token).Uint", Method, 27, ""}, + {"(Value).Clone", Method, 27, ""}, + {"(Value).IsValid", Method, 27, ""}, + {"(Value).Kind", Method, 27, ""}, + {"(Value).MarshalJSON", Method, 27, ""}, + {"(Value).String", Method, 27, ""}, + {"AllowDuplicateNames", Func, 27, "func(v bool) Options"}, + {"AllowInvalidUTF8", Func, 27, "func(v bool) Options"}, + {"AppendFloat", Func, 27, "func(dst []byte, src float64, bits int) []byte"}, + {"AppendFormat", Func, 27, "func[Bytes ~[]byte | ~string](dst []byte, src Bytes, opts ...Options) ([]byte, error)"}, + {"AppendQuote", Func, 27, "func[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error)"}, + {"AppendUnquote", Func, 27, "func[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error)"}, + {"BeginArray", Var, 27, ""}, + {"BeginObject", Var, 27, ""}, + {"Bool", Func, 27, "func(b bool) Token"}, + {"CanonicalizeRawFloats", Func, 27, "func(v bool) Options"}, + {"CanonicalizeRawInts", Func, 27, "func(v bool) Options"}, + {"Decoder", Type, 27, ""}, + {"Encoder", Type, 27, ""}, + {"EndArray", Var, 27, ""}, + {"EndObject", Var, 27, ""}, + {"ErrDuplicateName", Var, 27, ""}, + {"ErrNonStringName", Var, 27, ""}, + {"EscapeForHTML", Func, 27, "func(v bool) Options"}, + {"EscapeForJS", Func, 27, "func(v bool) Options"}, + {"False", Var, 27, ""}, + {"Float", Func, 27, "func(n float64) Token"}, + {"Float32", Func, 27, "func(n float32) Token"}, + {"Int", Func, 27, "func(n int64) Token"}, + {"Internal", Var, 27, ""}, + {"Kind", Type, 27, ""}, + {"KindBeginArray", Const, 27, ""}, + {"KindBeginObject", Const, 27, ""}, + {"KindEndArray", Const, 27, ""}, + {"KindEndObject", Const, 27, ""}, + {"KindFalse", Const, 27, ""}, + {"KindInvalid", Const, 27, ""}, + {"KindNull", Const, 27, ""}, + {"KindNumber", Const, 27, ""}, + {"KindString", Const, 27, ""}, + {"KindTrue", Const, 27, ""}, + {"Multiline", Func, 27, "func(v bool) Options"}, + {"NewDecoder", Func, 27, "func(r io.Reader, opts ...Options) *Decoder"}, + {"NewEncoder", Func, 27, "func(w io.Writer, opts ...Options) *Encoder"}, + {"Null", Var, 27, ""}, + {"Options", Type, 27, ""}, + {"Pointer", Type, 27, ""}, + {"PreserveRawStrings", Func, 27, "func(v bool) Options"}, + {"ReorderRawObjects", Func, 27, "func(v bool) Options"}, + {"SpaceAfterColon", Func, 27, "func(v bool) Options"}, + {"SpaceAfterComma", Func, 27, "func(v bool) Options"}, + {"String", Func, 27, "func(s string) Token"}, + {"SyntacticError", Type, 27, ""}, + {"SyntacticError.ByteOffset", Field, 27, ""}, + {"SyntacticError.Err", Field, 27, ""}, + {"SyntacticError.JSONPointer", Field, 27, ""}, + {"Token", Type, 27, ""}, + {"True", Var, 27, ""}, + {"Uint", Func, 27, "func(n uint64) Token"}, + {"Value", Type, 27, ""}, + {"WithIndent", Func, 27, "func(indent string) Options"}, + {"WithIndentPrefix", Func, 27, "func(prefix string) Options"}, + }, + "encoding/json/v2": { + {"(*SemanticError).Error", Method, 27, ""}, + {"(*SemanticError).Unwrap", Method, 27, ""}, + {"(Marshaler).MarshalJSON", Method, 27, ""}, + {"(MarshalerTo).MarshalJSONTo", Method, 27, ""}, + {"(Unmarshaler).UnmarshalJSON", Method, 27, ""}, + {"(UnmarshalerFrom).UnmarshalJSONFrom", Method, 27, ""}, + {"DefaultOptionsV2", Func, 27, "func() Options"}, + {"Deterministic", Func, 27, "func(v bool) Options"}, + {"ErrUnknownName", Var, 27, ""}, + {"FormatNilMapAsNull", Func, 27, "func(v bool) Options"}, + {"FormatNilSliceAsNull", Func, 27, "func(v bool) Options"}, + {"GetOption", Func, 27, "func[T any](opts Options, setter func(T) Options) (T, bool)"}, + {"JoinMarshalers", Func, 27, "func(ms ...*Marshalers) *Marshalers"}, + {"JoinOptions", Func, 27, "func(srcs ...Options) Options"}, + {"JoinUnmarshalers", Func, 27, "func(us ...*Unmarshalers) *Unmarshalers"}, + {"Marshal", Func, 27, "func(in any, opts ...Options) (out []byte, err error)"}, + {"MarshalEncode", Func, 27, "func(out *jsontext.Encoder, in any, opts ...Options) (err error)"}, + {"MarshalFunc", Func, 27, "func[T any](fn func(T) ([]byte, error)) *Marshalers"}, + {"MarshalToFunc", Func, 27, "func[T any](fn func(*jsontext.Encoder, T) error) *Marshalers"}, + {"MarshalWrite", Func, 27, "func(out io.Writer, in any, opts ...Options) (err error)"}, + {"Marshaler", Type, 27, ""}, + {"MarshalerTo", Type, 27, ""}, + {"Marshalers", Type, 27, ""}, + {"MatchCaseInsensitiveNames", Func, 27, "func(v bool) Options"}, + {"OmitZeroStructFields", Func, 27, "func(v bool) Options"}, + {"Options", Type, 27, ""}, + {"RejectUnknownMembers", Func, 27, "func(v bool) Options"}, + {"SemanticError", Type, 27, ""}, + {"SemanticError.ByteOffset", Field, 27, ""}, + {"SemanticError.Err", Field, 27, ""}, + {"SemanticError.GoType", Field, 27, ""}, + {"SemanticError.JSONKind", Field, 27, ""}, + {"SemanticError.JSONPointer", Field, 27, ""}, + {"SemanticError.JSONValue", Field, 27, ""}, + {"StringifyNumbers", Func, 27, "func(v bool) Options"}, + {"Unmarshal", Func, 27, "func(in []byte, out any, opts ...Options) (err error)"}, + {"UnmarshalDecode", Func, 27, "func(in *jsontext.Decoder, out any, opts ...Options) (err error)"}, + {"UnmarshalFromFunc", Func, 27, "func[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers"}, + {"UnmarshalFunc", Func, 27, "func[T any](fn func([]byte, T) error) *Unmarshalers"}, + {"UnmarshalRead", Func, 27, "func(in io.Reader, out any, opts ...Options) (err error)"}, + {"Unmarshaler", Type, 27, ""}, + {"UnmarshalerFrom", Type, 27, ""}, + {"Unmarshalers", Type, 27, ""}, + {"WithMarshalers", Func, 27, "func(v *Marshalers) Options"}, + {"WithUnmarshalers", Func, 27, "func(v *Unmarshalers) Options"}, + }, "encoding/pem": { {"Block", Type, 0, ""}, {"Block.Bytes", Field, 0, ""}, @@ -6002,6 +6229,7 @@ var PackageSymbols = map[string][]Symbol{ {"Shift", Func, 5, "func(x Value, op token.Token, s uint) Value"}, {"Sign", Func, 5, "func(x Value) int"}, {"String", Const, 5, ""}, + {"StringLen", Func, 27, "func(x Value) int64"}, {"StringVal", Func, 5, "func(x Value) string"}, {"ToComplex", Func, 6, "func(x Value) Value"}, {"ToFloat", Func, 6, "func(x Value) Value"}, @@ -6183,6 +6411,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*ErrorList).Add", Method, 0, ""}, {"(*ErrorList).RemoveMultiples", Method, 0, ""}, {"(*ErrorList).Reset", Method, 0, ""}, + {"(*Scanner).End", Method, 27, ""}, {"(*Scanner).Init", Method, 0, ""}, {"(*Scanner).Scan", Method, 0, ""}, {"(Error).Error", Method, 0, ""}, @@ -6222,6 +6451,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*File).SetLines", Method, 0, ""}, {"(*File).SetLinesForContent", Method, 0, ""}, {"(*File).Size", Method, 0, ""}, + {"(*File).String", Method, 27, ""}, {"(*FileSet).AddExistingFiles", Method, 25, ""}, {"(*FileSet).AddFile", Method, 0, ""}, {"(*FileSet).Base", Method, 0, ""}, @@ -6529,6 +6759,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Tuple).Variables", Method, 24, ""}, {"(*TypeList).At", Method, 18, ""}, {"(*TypeList).Len", Method, 18, ""}, + {"(*TypeList).String", Method, 27, ""}, {"(*TypeList).Types", Method, 24, ""}, {"(*TypeName).Exported", Method, 5, ""}, {"(*TypeName).Id", Method, 5, ""}, @@ -6547,6 +6778,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*TypeParam).Underlying", Method, 18, ""}, {"(*TypeParamList).At", Method, 18, ""}, {"(*TypeParamList).Len", Method, 18, ""}, + {"(*TypeParamList).String", Method, 27, ""}, {"(*TypeParamList).TypeParams", Method, 24, ""}, {"(*Union).Len", Method, 18, ""}, {"(*Union).String", Method, 18, ""}, @@ -6571,9 +6803,14 @@ var PackageSymbols = map[string][]Symbol{ {"(Checker).PkgNameOf", Method, 22, ""}, {"(Checker).TypeOf", Method, 5, ""}, {"(Error).Error", Method, 5, ""}, + {"(Hasher).Equal", Method, 27, ""}, + {"(Hasher).Hash", Method, 27, ""}, + {"(HasherIgnoreTags).Equal", Method, 27, ""}, + {"(HasherIgnoreTags).Hash", Method, 27, ""}, {"(Importer).Import", Method, 5, ""}, {"(ImporterFrom).Import", Method, 6, ""}, {"(ImporterFrom).ImportFrom", Method, 6, ""}, + {"(Instance).String", Method, 27, ""}, {"(Object).Exported", Method, 5, ""}, {"(Object).Id", Method, 5, ""}, {"(Object).Name", Method, 5, ""}, @@ -6643,6 +6880,8 @@ var PackageSymbols = map[string][]Symbol{ {"Float32", Const, 5, ""}, {"Float64", Const, 5, ""}, {"Func", Type, 5, ""}, + {"Hasher", Type, 27, ""}, + {"HasherIgnoreTags", Type, 27, ""}, {"Id", Func, 5, "func(pkg *Package, name string) string"}, {"Identical", Func, 5, "func(x Type, y Type) bool"}, {"IdenticalIgnoreTags", Func, 8, "func(x Type, y Type) bool"}, @@ -6877,9 +7116,13 @@ var PackageSymbols = map[string][]Symbol{ {"(*Hash).Write", Method, 14, ""}, {"(*Hash).WriteByte", Method, 14, ""}, {"(*Hash).WriteString", Method, 14, ""}, + {"(ComparableHasher).Equal", Method, 27, ""}, + {"(ComparableHasher).Hash", Method, 27, ""}, {"Bytes", Func, 19, "func(seed Seed, b []byte) uint64"}, {"Comparable", Func, 24, "func[T comparable](seed Seed, v T) uint64"}, + {"ComparableHasher", Type, 27, ""}, {"Hash", Type, 14, ""}, + {"Hasher", Type, 27, ""}, {"MakeSeed", Func, 14, "func() Seed"}, {"Seed", Type, 14, ""}, {"String", Func, 19, "func(seed Seed, s string) uint64"}, @@ -8035,6 +8278,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Int).CmpAbs", Method, 10, ""}, {"(*Int).Div", Method, 0, ""}, {"(*Int).DivMod", Method, 0, ""}, + {"(*Int).Divide", Method, 27, ""}, {"(*Int).Exp", Method, 0, ""}, {"(*Int).FillBytes", Method, 15, ""}, {"(*Int).Float64", Method, 21, ""}, @@ -8119,9 +8363,11 @@ var PackageSymbols = map[string][]Symbol{ {"Accuracy", Type, 5, ""}, {"AwayFromZero", Const, 5, ""}, {"Below", Const, 5, ""}, + {"Ceil", Const, 27, ""}, {"ErrNaN", Type, 5, ""}, {"Exact", Const, 5, ""}, {"Float", Type, 5, ""}, + {"Floor", Const, 27, ""}, {"Int", Type, 0, ""}, {"Jacobi", Func, 5, "func(x *Int, y *Int) int"}, {"MaxBase", Const, 0, ""}, @@ -8133,12 +8379,14 @@ var PackageSymbols = map[string][]Symbol{ {"NewRat", Func, 0, "func(a int64, b int64) *Rat"}, {"ParseFloat", Func, 5, "func(s string, base int, prec uint, mode RoundingMode) (f *Float, b int, err error)"}, {"Rat", Type, 0, ""}, + {"Round", Const, 27, ""}, {"RoundingMode", Type, 5, ""}, {"ToNearestAway", Const, 5, ""}, {"ToNearestEven", Const, 5, ""}, {"ToNegativeInf", Const, 5, ""}, {"ToPositiveInf", Const, 5, ""}, {"ToZero", Const, 5, ""}, + {"Trunc", Const, 27, ""}, {"Word", Type, 0, ""}, }, "math/bits": { @@ -8290,6 +8538,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Rand).Int64", Method, 22, ""}, {"(*Rand).Int64N", Method, 22, ""}, {"(*Rand).IntN", Method, 22, ""}, + {"(*Rand).N", Method, 27, ""}, {"(*Rand).NormFloat64", Method, 22, ""}, {"(*Rand).Perm", Method, 22, ""}, {"(*Rand).Shuffle", Method, 22, ""}, @@ -8985,7 +9234,7 @@ var PackageSymbols = map[string][]Symbol{ {"NoBody", Var, 8, ""}, {"NotFound", Func, 0, "func(w ResponseWriter, r *Request)"}, {"NotFoundHandler", Func, 0, "func() Handler"}, - {"ParseCookie", Func, 23, "func(line string) ([]*Cookie, error)"}, + {"ParseCookie", Func, 23, "func(line string) (#rv1 []*Cookie, #rv2 error)"}, {"ParseHTTPVersion", Func, 0, "func(vers string) (major int, minor int, ok bool)"}, {"ParseSetCookie", Func, 23, "func(line string) (*Cookie, error)"}, {"ParseTime", Func, 1, "func(text string) (t time.Time, err error)"}, @@ -9061,6 +9310,7 @@ var PackageSymbols = map[string][]Symbol{ {"Server.BaseContext", Field, 13, ""}, {"Server.ConnContext", Field, 13, ""}, {"Server.ConnState", Field, 3, ""}, + {"Server.DisableClientPriority", Field, 27, ""}, {"Server.DisableGeneralOptionsHandler", Field, 20, ""}, {"Server.ErrorLog", Field, 3, ""}, {"Server.HTTP2", Field, 24, ""}, @@ -9226,6 +9476,7 @@ var PackageSymbols = map[string][]Symbol{ {"NewRequestWithContext", Func, 23, "func(ctx context.Context, method string, target string, body io.Reader) *http.Request"}, {"NewServer", Func, 0, "func(handler http.Handler) *Server"}, {"NewTLSServer", Func, 0, "func(handler http.Handler) *Server"}, + {"NewTestServer", Func, 27, "func(t testing.TB, handler http.Handler) *Server"}, {"NewUnstartedServer", Func, 0, "func(handler http.Handler) *Server"}, {"ResponseRecorder", Type, 0, ""}, {"ResponseRecorder.Body", Field, 0, ""}, @@ -9596,6 +9847,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Error).Timeout", Method, 6, ""}, {"(*Error).Unwrap", Method, 13, ""}, {"(*URL).AppendBinary", Method, 24, ""}, + {"(*URL).Clone", Method, 27, ""}, {"(*URL).EscapedFragment", Method, 15, ""}, {"(*URL).EscapedPath", Method, 5, ""}, {"(*URL).Hostname", Method, 8, ""}, @@ -9616,6 +9868,7 @@ var PackageSymbols = map[string][]Symbol{ {"(EscapeError).Error", Method, 0, ""}, {"(InvalidHostError).Error", Method, 6, ""}, {"(Values).Add", Method, 0, ""}, + {"(Values).Clone", Method, 27, ""}, {"(Values).Del", Method, 0, ""}, {"(Values).Encode", Method, 0, ""}, {"(Values).Get", Method, 0, ""}, @@ -10793,6 +11046,7 @@ var PackageSymbols = map[string][]Symbol{ {"ContainsRune", Func, 0, "func(s string, r rune) bool"}, {"Count", Func, 0, "func(s string, substr string) int"}, {"Cut", Func, 18, "func(s string, sep string) (before string, after string, found bool)"}, + {"CutLast", Func, 27, "func(s string, sep string) (before string, after string, found bool)"}, {"CutPrefix", Func, 20, "func(s string, prefix string) (after string, found bool)"}, {"CutSuffix", Func, 20, "func(s string, suffix string) (before string, found bool)"}, {"EqualFold", Func, 0, "func(s string, t string) bool"}, @@ -17476,6 +17730,7 @@ var PackageSymbols = map[string][]Symbol{ {"TestHandler", Func, 21, "func(h slog.Handler, results func() []map[string]any) error"}, }, "testing/synctest": { + {"Sleep", Func, 27, "func(d time.Duration)"}, {"Test", Func, 25, "func(t *testing.T, f func(*testing.T))"}, {"Wait", Func, 25, "func()"}, }, @@ -17980,6 +18235,7 @@ var PackageSymbols = map[string][]Symbol{ {"Bassa_Vah", Var, 4, ""}, {"Batak", Var, 0, ""}, {"Bengali", Var, 0, ""}, + {"Beria_Erfe", Var, 27, ""}, {"Bhaiksuki", Var, 7, ""}, {"Bidi_Control", Var, 0, ""}, {"Bopomofo", Var, 0, ""}, @@ -18029,6 +18285,7 @@ var PackageSymbols = map[string][]Symbol{ {"Extender", Var, 0, ""}, {"FoldCategory", Var, 0, ""}, {"FoldScript", Var, 0, ""}, + {"Garay", Var, 27, ""}, {"Georgian", Var, 0, ""}, {"Glagolitic", Var, 0, ""}, {"Gothic", Var, 0, ""}, @@ -18038,6 +18295,7 @@ var PackageSymbols = map[string][]Symbol{ {"Gujarati", Var, 0, ""}, {"Gunjala_Gondi", Var, 13, ""}, {"Gurmukhi", Var, 0, ""}, + {"Gurung_Khema", Var, 27, ""}, {"Han", Var, 0, ""}, {"Hangul", Var, 0, ""}, {"Hanifi_Rohingya", Var, 13, ""}, @@ -18049,6 +18307,9 @@ var PackageSymbols = map[string][]Symbol{ {"Hyphen", Var, 0, ""}, {"IDS_Binary_Operator", Var, 0, ""}, {"IDS_Trinary_Operator", Var, 0, ""}, + {"IDS_Unary_Operator", Var, 27, ""}, + {"ID_Compat_Math_Continue", Var, 27, ""}, + {"ID_Compat_Math_Start", Var, 27, ""}, {"Ideographic", Var, 0, ""}, {"Imperial_Aramaic", Var, 0, ""}, {"In", Func, 2, "func(r rune, ranges ...*RangeTable) bool"}, @@ -18082,6 +18343,7 @@ var PackageSymbols = map[string][]Symbol{ {"Khmer", Var, 0, ""}, {"Khojki", Var, 4, ""}, {"Khudawadi", Var, 4, ""}, + {"Kirat_Rai", Var, 27, ""}, {"L", Var, 0, ""}, {"LC", Var, 25, ""}, {"Lao", Var, 0, ""}, @@ -18125,6 +18387,7 @@ var PackageSymbols = map[string][]Symbol{ {"Miao", Var, 1, ""}, {"Mn", Var, 0, ""}, {"Modi", Var, 4, ""}, + {"Modifier_Combining_Mark", Var, 27, ""}, {"Mongolian", Var, 0, ""}, {"Mro", Var, 4, ""}, {"Multani", Var, 5, ""}, @@ -18145,6 +18408,7 @@ var PackageSymbols = map[string][]Symbol{ {"Nyiakeng_Puachue_Hmong", Var, 14, ""}, {"Ogham", Var, 0, ""}, {"Ol_Chiki", Var, 0, ""}, + {"Ol_Onal", Var, 27, ""}, {"Old_Hungarian", Var, 5, ""}, {"Old_Italic", Var, 0, ""}, {"Old_North_Arabian", Var, 4, ""}, @@ -18214,6 +18478,7 @@ var PackageSymbols = map[string][]Symbol{ {"Sharada", Var, 1, ""}, {"Shavian", Var, 0, ""}, {"Siddham", Var, 4, ""}, + {"Sidetic", Var, 27, ""}, {"SignWriting", Var, 5, ""}, {"SimpleFold", Func, 0, "func(r rune) rune"}, {"Sinhala", Var, 0, ""}, @@ -18227,6 +18492,7 @@ var PackageSymbols = map[string][]Symbol{ {"Space", Var, 0, ""}, {"SpecialCase", Type, 0, ""}, {"Sundanese", Var, 0, ""}, + {"Sunuwar", Var, 27, ""}, {"Syloti_Nagri", Var, 0, ""}, {"Symbol", Var, 0, ""}, {"Syriac", Var, 0, ""}, @@ -18235,6 +18501,7 @@ var PackageSymbols = map[string][]Symbol{ {"Tai_Le", Var, 0, ""}, {"Tai_Tham", Var, 0, ""}, {"Tai_Viet", Var, 0, ""}, + {"Tai_Yo", Var, 27, ""}, {"Takri", Var, 1, ""}, {"Tamil", Var, 0, ""}, {"Tangsa", Var, 21, ""}, @@ -18252,7 +18519,10 @@ var PackageSymbols = map[string][]Symbol{ {"ToLower", Func, 0, "func(r rune) rune"}, {"ToTitle", Func, 0, "func(r rune) rune"}, {"ToUpper", Func, 0, "func(r rune) rune"}, + {"Todhri", Var, 27, ""}, + {"Tolong_Siki", Var, 27, ""}, {"Toto", Var, 21, ""}, + {"Tulu_Tigalari", Var, 27, ""}, {"TurkishCase", Var, 0, ""}, {"Ugaritic", Var, 0, ""}, {"Unified_Ideograph", Var, 0, ""}, @@ -18320,6 +18590,21 @@ var PackageSymbols = map[string][]Symbol{ {"String", Func, 0, ""}, {"StringData", Func, 0, ""}, }, + "uuid": { + {"(*UUID).UnmarshalText", Method, 27, ""}, + {"(UUID).AppendText", Method, 27, ""}, + {"(UUID).Compare", Method, 27, ""}, + {"(UUID).MarshalText", Method, 27, ""}, + {"(UUID).String", Method, 27, ""}, + {"Max", Func, 27, "func() UUID"}, + {"MustParse", Func, 27, "func(s string) UUID"}, + {"New", Func, 27, "func() UUID"}, + {"NewV4", Func, 27, "func() UUID"}, + {"NewV7", Func, 27, "func() UUID"}, + {"Nil", Func, 27, "func() UUID"}, + {"Parse", Func, 27, "func(s string) (UUID, error)"}, + {"UUID", Type, 27, ""}, + }, "weak": { {"(Pointer).Value", Method, 24, ""}, {"Make", Func, 24, "func[T any](ptr *T) Pointer[T]"}, diff --git a/vendor/golang.org/x/tools/internal/typesinternal/element.go b/vendor/golang.org/x/tools/internal/typesinternal/element.go index 5fe4d8abcb..89eeea1653 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/element.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/element.go @@ -37,6 +37,10 @@ func ForEachElement(rtypes *typeutil.Map, msets *typeutil.MethodSetCache, T type tmset := msets.MethodSet(T) for method := range tmset.Methods() { sig := method.Type().(*types.Signature) + if sig.TypeParams() != nil { + continue // skip type-parameterized methods + } + // It is tempting to call visit(sig, false) // but, as noted in golang.org/cl/65450043, // the Signature.Recv field is ignored by @@ -123,10 +127,10 @@ func ForEachElement(rtypes *typeutil.Map, msets *typeutil.MethodSetCache, T type case *types.TypeParam, *types.Union: // forEachReachable must not be called on parameterized types. - panic(T) + panic(fmt.Sprintf("ForEachElement called on type containing %T", T)) default: - panic(T) + panic(fmt.Sprintf("ForEachElement called on unexpected type %T", T)) } } visit(T, false) diff --git a/vendor/golang.org/x/tools/internal/typesinternal/types.go b/vendor/golang.org/x/tools/internal/typesinternal/types.go index 6582cc81f5..d2c0b4c5ff 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/types.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/types.go @@ -22,6 +22,7 @@ import ( "go/ast" "go/token" "go/types" + "iter" "reflect" "golang.org/x/tools/go/ast/inspector" @@ -242,3 +243,30 @@ func ObjectKind(obj types.Object) string { } return "unknown symbol" } + +// ImplicitFieldSelections returns the sequence of implicit embedded fields +// traversed by the given selection. It skips the final leaf field or method. +// The boolean component indicates whether the traversal traversed a pointer. +func ImplicitFieldSelections(seln types.Selection) iter.Seq2[*types.Var, bool] { + return func(yield func(*types.Var, bool) bool) { + var ( + t = seln.Recv() + indices = seln.Index() + ) + for _, idx := range indices[:len(indices)-1] { + ptr, isPtr := t.Underlying().(*types.Pointer) + if isPtr { + t = ptr.Elem() + } + structType, ok := t.Underlying().(*types.Struct) + if !ok { + break + } + field := structType.Field(idx) + if !yield(field, isPtr) { + break + } + t = field.Type() + } + } +} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go b/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go index d612a71029..706ad33ef8 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go @@ -259,13 +259,13 @@ func TypeExpr(t types.Type, qual types.Qualifier) ast.Expr { case *types.Signature: var params []*ast.Field for v := range t.Params().Variables() { + var names []*ast.Ident + if v.Name() != "" { + names = []*ast.Ident{ast.NewIdent(v.Name())} + } params = append(params, &ast.Field{ - Type: TypeExpr(v.Type(), qual), - Names: []*ast.Ident{ - { - Name: v.Name(), - }, - }, + Type: TypeExpr(v.Type(), qual), + Names: names, }) } if t.Variadic() { @@ -328,10 +328,10 @@ func TypeExpr(t types.Type, qual types.Qualifier) ast.Expr { return expr case *types.Struct: - return ast.NewIdent(t.String()) + return ast.NewIdent(types.TypeString(t, qual)) case *types.Interface: - return ast.NewIdent(t.String()) + return ast.NewIdent(types.TypeString(t, qual)) case *types.Union: if t.Len() == 0 { diff --git a/vendor/google.golang.org/grpc/cmd/protoc-gen-go-grpc/grpc.go b/vendor/google.golang.org/grpc/cmd/protoc-gen-go-grpc/grpc.go index cff016d66a..31344f175f 100644 --- a/vendor/google.golang.org/grpc/cmd/protoc-gen-go-grpc/grpc.go +++ b/vendor/google.golang.org/grpc/cmd/protoc-gen-go-grpc/grpc.go @@ -178,13 +178,8 @@ func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen. g.P("// This is a compile-time assertion to ensure that this generated file") g.P("// is compatible with the grpc package it is being compiled against.") - if *useGenericStreams { - g.P("// Requires gRPC-Go v1.64.0 or later.") - g.P("const _ = ", grpcPackage.Ident("SupportPackageIsVersion9")) - } else { - g.P("// Requires gRPC-Go v1.62.0 or later.") - g.P("const _ = ", grpcPackage.Ident("SupportPackageIsVersion8")) // When changing, update version number above. - } + g.P("// Requires gRPC-Go v1.64.0 or later.") + g.P("const _ = ", grpcPackage.Ident("SupportPackageIsVersion9")) g.P() for _, service := range file.Services { genService(gen, file, g, service) @@ -333,11 +328,7 @@ func clientSignature(g *protogen.GeneratedFile, method *protogen.Method) string if !method.Desc.IsStreamingClient() && !method.Desc.IsStreamingServer() { s += "*" + g.QualifiedGoIdent(method.Output.GoIdent) } else { - if *useGenericStreams { - s += clientStreamInterface(g, method) - } else { - s += method.Parent.GoName + "_" + method.GoName + "Client" - } + s += clientStreamInterface(g, method) } s += ", error)" return s @@ -373,12 +364,8 @@ func genClientMethod(_ *protogen.Plugin, _ *protogen.File, g *protogen.Generated return } - streamImpl := unexport(service.GoName) + method.GoName + "Client" - if *useGenericStreams { - typeParam := g.QualifiedGoIdent(method.Input.GoIdent) + ", " + g.QualifiedGoIdent(method.Output.GoIdent) - streamImpl = g.QualifiedGoIdent(grpcPackage.Ident("GenericClientStream")) + "[" + typeParam + "]" - } - + typeParam := g.QualifiedGoIdent(method.Input.GoIdent) + ", " + g.QualifiedGoIdent(method.Output.GoIdent) + streamImpl := g.QualifiedGoIdent(grpcPackage.Ident("GenericClientStream")) + "[" + typeParam + "]" serviceDescVar := service.GoName + "_ServiceDesc" g.P("stream, err := c.cc.NewStream(ctx, &", serviceDescVar, ".Streams[", index, `], `, fmSymbol, `, cOpts...)`) g.P("if err != nil { return nil, err }") @@ -392,61 +379,9 @@ func genClientMethod(_ *protogen.Plugin, _ *protogen.File, g *protogen.Generated g.P() // Auxiliary types aliases, for backwards compatibility. - if *useGenericStreams { - g.P("// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.") - g.P("type ", service.GoName, "_", method.GoName, "Client = ", clientStreamInterface(g, method)) - g.P() - return - } - - // Stream auxiliary types and methods, if we're not taking advantage of the - // pre-implemented generic types and their methods. - genSend := method.Desc.IsStreamingClient() - genRecv := method.Desc.IsStreamingServer() - genCloseAndRecv := !method.Desc.IsStreamingServer() - - g.P("type ", service.GoName, "_", method.GoName, "Client interface {") - if genSend { - g.P("Send(*", method.Input.GoIdent, ") error") - } - if genRecv { - g.P("Recv() (*", method.Output.GoIdent, ", error)") - } - if genCloseAndRecv { - g.P("CloseAndRecv() (*", method.Output.GoIdent, ", error)") - } - g.P(grpcPackage.Ident("ClientStream")) - g.P("}") - g.P() - - g.P("type ", streamImpl, " struct {") - g.P(grpcPackage.Ident("ClientStream")) - g.P("}") + g.P("// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.") + g.P("type ", service.GoName, "_", method.GoName, "Client = ", clientStreamInterface(g, method)) g.P() - - if genSend { - g.P("func (x *", streamImpl, ") Send(m *", method.Input.GoIdent, ") error {") - g.P("return x.ClientStream.SendMsg(m)") - g.P("}") - g.P() - } - if genRecv { - g.P("func (x *", streamImpl, ") Recv() (*", method.Output.GoIdent, ", error) {") - g.P("m := new(", method.Output.GoIdent, ")") - g.P("if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err }") - g.P("return m, nil") - g.P("}") - g.P() - } - if genCloseAndRecv { - g.P("func (x *", streamImpl, ") CloseAndRecv() (*", method.Output.GoIdent, ", error) {") - g.P("if err := x.ClientStream.CloseSend(); err != nil { return nil, err }") - g.P("m := new(", method.Output.GoIdent, ")") - g.P("if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err }") - g.P("return m, nil") - g.P("}") - g.P() - } } func serverSignature(g *protogen.GeneratedFile, method *protogen.Method) string { @@ -460,11 +395,7 @@ func serverSignature(g *protogen.GeneratedFile, method *protogen.Method) string reqArgs = append(reqArgs, "*"+g.QualifiedGoIdent(method.Input.GoIdent)) } if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() { - if *useGenericStreams { - reqArgs = append(reqArgs, serverStreamInterface(g, method)) - } else { - reqArgs = append(reqArgs, method.Parent.GoName+"_"+method.GoName+"Server") - } + reqArgs = append(reqArgs, serverStreamInterface(g, method)) } return method.GoName + "(" + strings.Join(reqArgs, ", ") + ") " + ret } @@ -545,11 +476,8 @@ func genServerMethod(_ *protogen.Plugin, _ *protogen.File, g *protogen.Generated return hname } - streamImpl := unexport(service.GoName) + method.GoName + "Server" - if *useGenericStreams { - typeParam := g.QualifiedGoIdent(method.Input.GoIdent) + ", " + g.QualifiedGoIdent(method.Output.GoIdent) - streamImpl = g.QualifiedGoIdent(grpcPackage.Ident("GenericServerStream")) + "[" + typeParam + "]" - } + typeParam := g.QualifiedGoIdent(method.Input.GoIdent) + ", " + g.QualifiedGoIdent(method.Output.GoIdent) + streamImpl := g.QualifiedGoIdent(grpcPackage.Ident("GenericServerStream")) + "[" + typeParam + "]" g.P("func ", hnameFuncNameFormatter(hname), "(srv interface{}, stream ", grpcPackage.Ident("ServerStream"), ") error {") if !method.Desc.IsStreamingClient() { @@ -563,59 +491,9 @@ func genServerMethod(_ *protogen.Plugin, _ *protogen.File, g *protogen.Generated g.P() // Auxiliary types aliases, for backwards compatibility. - if *useGenericStreams { - g.P("// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.") - g.P("type ", service.GoName, "_", method.GoName, "Server = ", serverStreamInterface(g, method)) - g.P() - return hname - } - - // Stream auxiliary types and methods, if we're not taking advantage of the - // pre-implemented generic types and their methods. - genSend := method.Desc.IsStreamingServer() - genSendAndClose := !method.Desc.IsStreamingServer() - genRecv := method.Desc.IsStreamingClient() - - g.P("type ", service.GoName, "_", method.GoName, "Server interface {") - if genSend { - g.P("Send(*", method.Output.GoIdent, ") error") - } - if genSendAndClose { - g.P("SendAndClose(*", method.Output.GoIdent, ") error") - } - if genRecv { - g.P("Recv() (*", method.Input.GoIdent, ", error)") - } - g.P(grpcPackage.Ident("ServerStream")) - g.P("}") + g.P("// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.") + g.P("type ", service.GoName, "_", method.GoName, "Server = ", serverStreamInterface(g, method)) g.P() - - g.P("type ", streamImpl, " struct {") - g.P(grpcPackage.Ident("ServerStream")) - g.P("}") - g.P() - - if genSend { - g.P("func (x *", streamImpl, ") Send(m *", method.Output.GoIdent, ") error {") - g.P("return x.ServerStream.SendMsg(m)") - g.P("}") - g.P() - } - if genSendAndClose { - g.P("func (x *", streamImpl, ") SendAndClose(m *", method.Output.GoIdent, ") error {") - g.P("return x.ServerStream.SendMsg(m)") - g.P("}") - g.P() - } - if genRecv { - g.P("func (x *", streamImpl, ") Recv() (*", method.Input.GoIdent, ", error) {") - g.P("m := new(", method.Input.GoIdent, ")") - g.P("if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err }") - g.P("return m, nil") - g.P("}") - g.P() - } - return hname } diff --git a/vendor/google.golang.org/grpc/cmd/protoc-gen-go-grpc/main.go b/vendor/google.golang.org/grpc/cmd/protoc-gen-go-grpc/main.go index 83bff58e0c..94e7ffcf58 100644 --- a/vendor/google.golang.org/grpc/cmd/protoc-gen-go-grpc/main.go +++ b/vendor/google.golang.org/grpc/cmd/protoc-gen-go-grpc/main.go @@ -42,10 +42,9 @@ import ( "google.golang.org/protobuf/types/pluginpb" ) -const version = "1.6.1" +const version = "1.6.2" var requireUnimplemented *bool -var useGenericStreams *bool func main() { showVersion := flag.Bool("version", false, "print the version and exit") @@ -57,7 +56,6 @@ func main() { var flags flag.FlagSet requireUnimplemented = flags.Bool("require_unimplemented_servers", true, "set to false to match legacy behavior") - useGenericStreams = flags.Bool("use_generic_streams_experimental", true, "set to true to use generic types for streaming client and server objects; this flag is EXPERIMENTAL and may be changed or removed in a future release") protogen.Options{ ParamFunc: flags.Set, diff --git a/vendor/google.golang.org/grpc/cmd/protoc-gen-go-grpc/protoc-gen-go-grpc_test.sh b/vendor/google.golang.org/grpc/cmd/protoc-gen-go-grpc/protoc-gen-go-grpc_test.sh index c70fd0fa63..fb67b1f41b 100644 --- a/vendor/google.golang.org/grpc/cmd/protoc-gen-go-grpc/protoc-gen-go-grpc_test.sh +++ b/vendor/google.golang.org/grpc/cmd/protoc-gen-go-grpc/protoc-gen-go-grpc_test.sh @@ -30,7 +30,7 @@ popd protoc \ --go-grpc_out="${TEMPDIR}" \ - --go-grpc_opt=paths=source_relative,use_generic_streams_experimental=true \ + --go-grpc_opt=paths=source_relative \ "examples/route_guide/routeguide/route_guide.proto" GOLDENFILE="examples/route_guide/routeguide/route_guide_grpc.pb.go" diff --git a/vendor/modules.txt b/vendor/modules.txt index 7060bba176..6e07b2579f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,14 +1,14 @@ -# charm.land/bubbles/v2 v2.1.0 +# charm.land/bubbles/v2 v2.1.1 ## explicit; go 1.25.0 charm.land/bubbles/v2/cursor charm.land/bubbles/v2/internal/runeutil charm.land/bubbles/v2/key charm.land/bubbles/v2/textinput charm.land/bubbles/v2/viewport -# charm.land/bubbletea/v2 v2.0.2 -## explicit; go 1.24.2 +# charm.land/bubbletea/v2 v2.0.7 +## explicit; go 1.25.0 charm.land/bubbletea/v2 -# charm.land/lipgloss/v2 v2.0.2 +# charm.land/lipgloss/v2 v2.0.4 ## explicit; go 1.25.0 charm.land/lipgloss/v2 # charm.land/log/v2 v2.0.0 @@ -46,10 +46,10 @@ github.com/charmbracelet/keygen # github.com/charmbracelet/ssh v0.0.0-20250826160808-ebfa259c7309 ## explicit; go 1.23.0 github.com/charmbracelet/ssh -# github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 -## explicit; go 1.24.2 +# github.com/charmbracelet/ultraviolet v0.0.0-20260525132238-948f4557a654 +## explicit; go 1.25.0 github.com/charmbracelet/ultraviolet -# github.com/charmbracelet/x/ansi v0.11.6 +# github.com/charmbracelet/x/ansi v0.11.7 ## explicit; go 1.24.2 github.com/charmbracelet/x/ansi github.com/charmbracelet/x/ansi/kitty @@ -69,16 +69,18 @@ github.com/charmbracelet/x/termios # github.com/charmbracelet/x/windows v0.2.2 ## explicit; go 1.23.0 github.com/charmbracelet/x/windows -# github.com/cilium/ebpf v0.19.0 -## explicit; go 1.23.0 +# github.com/cilium/ebpf v0.22.0 +## explicit; go 1.25.0 github.com/cilium/ebpf github.com/cilium/ebpf/asm github.com/cilium/ebpf/btf +github.com/cilium/ebpf/features github.com/cilium/ebpf/internal github.com/cilium/ebpf/internal/efw github.com/cilium/ebpf/internal/kallsyms github.com/cilium/ebpf/internal/kconfig github.com/cilium/ebpf/internal/linux +github.com/cilium/ebpf/internal/mountinfo github.com/cilium/ebpf/internal/platform github.com/cilium/ebpf/internal/sys github.com/cilium/ebpf/internal/sysenc @@ -96,7 +98,7 @@ github.com/clipperhouse/uax29/v2/graphemes # github.com/creack/pty v1.1.24 ## explicit; go 1.18 github.com/creack/pty -# github.com/davecgh/go-spew v1.1.1 +# github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew # github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 @@ -109,7 +111,7 @@ github.com/fsnotify/fsnotify/internal # github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff ## explicit github.com/ftrvxmtrx/fd -# github.com/gaissmai/bart v0.26.1 +# github.com/gaissmai/bart v0.29.0 ## explicit; go 1.24.0 github.com/gaissmai/bart github.com/gaissmai/bart/internal/allot @@ -203,7 +205,7 @@ github.com/klauspost/compress/internal/le github.com/klauspost/compress/internal/snapref github.com/klauspost/compress/zstd github.com/klauspost/compress/zstd/internal/xxhash -# github.com/lucasb-eyer/go-colorful v1.3.0 +# github.com/lucasb-eyer/go-colorful v1.4.0 ## explicit; go 1.12 github.com/lucasb-eyer/go-colorful # github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe @@ -212,22 +214,22 @@ github.com/lunixbochs/struc # github.com/mattn/go-isatty v0.0.22 ## explicit; go 1.21 github.com/mattn/go-isatty -# github.com/mattn/go-runewidth v0.0.21 +# github.com/mattn/go-runewidth v0.0.24 ## explicit; go 1.20 github.com/mattn/go-runewidth -# github.com/mdlayher/genetlink v1.3.2 -## explicit; go 1.18 +# github.com/mdlayher/genetlink v1.4.0 +## explicit; go 1.25.0 github.com/mdlayher/genetlink -# github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 -## explicit; go 1.21 +# github.com/mdlayher/netlink v1.9.0 +## explicit; go 1.24.0 github.com/mdlayher/netlink github.com/mdlayher/netlink/nlenc github.com/mdlayher/netlink/nltest # github.com/mdlayher/packet v1.1.2 ## explicit; go 1.20 github.com/mdlayher/packet -# github.com/mdlayher/socket v0.5.1 -## explicit; go 1.20 +# github.com/mdlayher/socket v0.6.0 +## explicit; go 1.25.0 github.com/mdlayher/socket # github.com/miekg/dns v1.1.72 ## explicit; go 1.24.0 @@ -259,11 +261,11 @@ github.com/pierrec/lz4/v4/internal/lz4block github.com/pierrec/lz4/v4/internal/lz4errors github.com/pierrec/lz4/v4/internal/lz4stream github.com/pierrec/lz4/v4/internal/xxh32 -# github.com/pmezard/go-difflib v1.0.0 +# github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 ## explicit github.com/pmezard/go-difflib/difflib -# github.com/prometheus/client_golang v1.23.2 -## explicit; go 1.23.0 +# github.com/prometheus/client_golang v1.24.0 +## explicit; go 1.25.0 github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header github.com/prometheus/client_golang/prometheus @@ -274,20 +276,20 @@ github.com/prometheus/client_golang/prometheus/promhttp/internal # github.com/prometheus/client_model v0.6.2 ## explicit; go 1.22.0 github.com/prometheus/client_model/go -# github.com/prometheus/common v0.66.1 -## explicit; go 1.23.0 +# github.com/prometheus/common v0.70.0 +## explicit; go 1.25.0 github.com/prometheus/common/expfmt github.com/prometheus/common/model -# github.com/prometheus/procfs v0.16.1 -## explicit; go 1.23.0 +# github.com/prometheus/procfs v0.21.1 +## explicit; go 1.25.0 github.com/prometheus/procfs github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util # github.com/rivo/uniseg v0.4.7 ## explicit; go 1.18 github.com/rivo/uniseg -# github.com/sirupsen/logrus v1.9.3 -## explicit; go 1.13 +# github.com/sirupsen/logrus v1.9.4 +## explicit; go 1.17 github.com/sirupsen/logrus # github.com/sivchari/gomu v0.2.1 ## explicit; go 1.24 @@ -362,10 +364,7 @@ go.fd.io/govpp/binapi/wireguard go.fd.io/govpp/codec go.fd.io/govpp/core go.fd.io/govpp/core/genericpool -# go.yaml.in/yaml/v2 v2.4.2 -## explicit; go 1.15 -go.yaml.in/yaml/v2 -# golang.org/x/crypto v0.53.0 +# golang.org/x/crypto v0.54.0 ## explicit; go 1.25.0 golang.org/x/crypto/argon2 golang.org/x/crypto/bcrypt @@ -391,13 +390,13 @@ golang.org/x/exp/slices golang.org/x/exp/slog golang.org/x/exp/slog/internal golang.org/x/exp/slog/internal/buffer -# golang.org/x/mod v0.37.0 +# golang.org/x/mod v0.38.0 ## explicit; go 1.25.0 golang.org/x/mod/internal/lazyregexp golang.org/x/mod/modfile golang.org/x/mod/module golang.org/x/mod/semver -# golang.org/x/net v0.56.0 +# golang.org/x/net v0.57.0 ## explicit; go 1.25.0 golang.org/x/net/bpf golang.org/x/net/http/httpguts @@ -416,31 +415,31 @@ golang.org/x/net/trace ## explicit; go 1.25.0 golang.org/x/oauth2 golang.org/x/oauth2/internal -# golang.org/x/sync v0.21.0 +# golang.org/x/sync v0.22.0 ## explicit; go 1.25.0 golang.org/x/sync/errgroup -# golang.org/x/sys v0.46.0 +# golang.org/x/sys v0.47.0 ## explicit; go 1.25.0 golang.org/x/sys/cpu golang.org/x/sys/plan9 golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 +# golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57 ## explicit; go 1.25.0 golang.org/x/telemetry/counter golang.org/x/telemetry/internal/counter golang.org/x/telemetry/internal/mmap golang.org/x/telemetry/internal/telemetry -# golang.org/x/term v0.44.0 +# golang.org/x/term v0.45.0 ## explicit; go 1.25.0 golang.org/x/term -# golang.org/x/text v0.38.0 +# golang.org/x/text v0.40.0 ## explicit; go 1.25.0 golang.org/x/text/secure/bidirule golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm -# golang.org/x/tools v0.45.0 +# golang.org/x/tools v0.47.0 ## explicit; go 1.25.0 golang.org/x/tools/cmd/goimports golang.org/x/tools/go/ast/astutil @@ -555,8 +554,8 @@ google.golang.org/grpc/serviceconfig google.golang.org/grpc/stats google.golang.org/grpc/status google.golang.org/grpc/tap -# google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 -## explicit; go 1.24.0 +# google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 +## explicit; go 1.25.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc # google.golang.org/protobuf v1.36.11 ## explicit; go 1.23