mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c815a4432 | ||
|
|
0b235b4d42 | ||
|
|
1402a75b18 | ||
|
|
ff5e4a4a24 | ||
|
|
e59c90c89a | ||
|
|
49771f0159 | ||
|
|
a2808b19f6 | ||
|
|
eddfb7073c | ||
|
|
930799495c | ||
|
|
5016fb8d7c | ||
|
|
65382f7b77 | ||
|
|
c1d00f0e48 | ||
|
|
e89280d64a | ||
|
|
aa4c9d7b8a | ||
|
|
2be820daf1 | ||
|
|
0a44fde9fa | ||
|
|
151dfe2643 | ||
|
|
fa7033f0c4 | ||
|
|
4eab1b582a | ||
|
|
f78db1a825 | ||
|
|
eb61740f5d | ||
|
|
39d0f3b838 | ||
|
|
8f084c99a4 | ||
|
|
87720d8a41 | ||
|
|
f8dd5230c1 | ||
|
|
174455cbcc |
No files matched your search
+5
-9
@@ -9,7 +9,7 @@
|
||||
|
||||
# STAGE: base
|
||||
## Compiles Sliver for use
|
||||
FROM --platform=linux/amd64 golang:1.22.2 as base
|
||||
FROM golang:1.22.2 as base
|
||||
|
||||
### Base packages
|
||||
RUN apt-get update --fix-missing && apt-get -y install \
|
||||
@@ -29,7 +29,7 @@ RUN cp -vv sliver-server /opt/sliver-server
|
||||
# STAGE: test
|
||||
## Run unit tests against the compiled instance
|
||||
## Use `--target test` in the docker build command to run this stage
|
||||
FROM --platform=linux/amd64 base as test
|
||||
FROM base as test
|
||||
|
||||
RUN apt-get update --fix-missing \
|
||||
&& apt-get -y upgrade \
|
||||
@@ -43,7 +43,7 @@ RUN /go/src/github.com/bishopfox/sliver/go-tests.sh
|
||||
|
||||
# STAGE: production
|
||||
## Final dockerized form of Sliver
|
||||
FROM --platform=linux/amd64 debian:bookworm-slim as production
|
||||
FROM debian:bookworm-slim as production
|
||||
|
||||
### Install production packages
|
||||
RUN apt-get update --fix-missing \
|
||||
@@ -91,15 +91,11 @@ ENTRYPOINT [ "/opt/sliver-server" ]
|
||||
|
||||
|
||||
# STAGE: production-slim (about 1Gb smaller)
|
||||
### Slim production image, i.e. without MSF and assoicated libraries
|
||||
### Still include GCC and MinGW for cross-platform generation
|
||||
FROM --platform=linux/amd64 debian:bookworm-slim as production-slim
|
||||
FROM debian:bookworm-slim as production-slim
|
||||
|
||||
### Install production packages
|
||||
RUN apt-get update --fix-missing \
|
||||
&& apt-get -y upgrade \
|
||||
&& apt-get -y install \
|
||||
build-essential mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
&& apt-get -y upgrade
|
||||
|
||||
### Cleanup unneeded packages
|
||||
RUN apt-get autoremove -y \
|
||||
|
||||
+6
-4
@@ -24,8 +24,8 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/version"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -34,8 +34,6 @@ const (
|
||||
logFileName = "sliver-client.log"
|
||||
)
|
||||
|
||||
var sliverServerVersion = fmt.Sprintf("v%s", version.FullVersion())
|
||||
|
||||
// Initialize logging.
|
||||
func initLogging(appDir string) *os.File {
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
@@ -48,6 +46,10 @@ func initLogging(appDir string) *os.File {
|
||||
}
|
||||
|
||||
func init() {
|
||||
appDir := assets.GetRootAppDir()
|
||||
logFile := initLogging(appDir)
|
||||
defer logFile.Close()
|
||||
|
||||
rootCmd.TraverseChildren = true
|
||||
|
||||
// Create the console client, without any RPC or commands bound to it yet.
|
||||
@@ -90,7 +92,7 @@ var rootCmd = &cobra.Command{
|
||||
// Execute - Execute root command.
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Printf("root command: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
+18
-4
@@ -1,5 +1,23 @@
|
||||
package cli
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
@@ -20,7 +38,6 @@ func consoleCmd(con *console.SliverClient) *cobra.Command {
|
||||
}
|
||||
|
||||
consoleCmd.RunE, consoleCmd.PersistentPostRunE = consoleRunnerCmd(con, true)
|
||||
|
||||
return consoleCmd
|
||||
}
|
||||
|
||||
@@ -28,9 +45,6 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
|
||||
var ln *grpc.ClientConn
|
||||
|
||||
pre = func(_ *cobra.Command, _ []string) error {
|
||||
appDir := assets.GetRootAppDir()
|
||||
logFile := initLogging(appDir)
|
||||
defer logFile.Close()
|
||||
|
||||
configs := assets.GetConfigs()
|
||||
if len(configs) == 0 {
|
||||
|
||||
+19
-1
@@ -1,5 +1,23 @@
|
||||
package cli
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
@@ -24,7 +42,7 @@ func implantCmd(con *console.SliverClient) *cobra.Command {
|
||||
implantFlags.StringP("use", "s", "", "interact with a session")
|
||||
cmd.Flags().AddFlagSet(implantFlags)
|
||||
|
||||
// Prerunners (console setup, connection, etc)
|
||||
// Pre-runners (console setup, connection, etc)
|
||||
cmd.PersistentPreRunE, cmd.PersistentPostRunE = makeRunners(cmd, con)
|
||||
|
||||
// Completions
|
||||
|
||||
@@ -105,7 +105,7 @@ func (ec *AliasManifest) getDefaultProcess(targetOS string) (proc string, err er
|
||||
return
|
||||
}
|
||||
|
||||
func (a *AliasManifest) getFileForTarget(cmdName string, targetOS string, targetArch string) (string, error) {
|
||||
func (a *AliasManifest) getFileForTarget(_ string, targetOS string, targetArch string) (string, error) {
|
||||
filePath := ""
|
||||
for _, extFile := range a.Files {
|
||||
if targetOS == extFile.OS && targetArch == extFile.Arch {
|
||||
|
||||
@@ -141,13 +141,13 @@ func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (strin
|
||||
func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
dirPath := args[0]
|
||||
// dirPath := ctx.Args.String("dir-path")
|
||||
manyfest, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName))
|
||||
manifest, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// do not add if the command already exists
|
||||
sliverMenu := con.App.Menu("implant")
|
||||
for _, extCmd := range manyfest.ExtCommand {
|
||||
for _, extCmd := range manifest.ExtCommand {
|
||||
if CmdExists(extCmd.CommandName, sliverMenu.Command) {
|
||||
con.PrintErrorf("%s command already exists\n", extCmd.CommandName)
|
||||
confirm := false
|
||||
@@ -184,7 +184,7 @@ func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) {
|
||||
|
||||
func convertOldManifest(old *ExtensionManifest_) *ExtensionManifest {
|
||||
ret := &ExtensionManifest{
|
||||
Name: old.CommandName, //treating old commandname as the manifest name to avoid weird chars mostly
|
||||
Name: old.CommandName, //treating old command name as the manifest name to avoid weird chars mostly
|
||||
Version: old.Version,
|
||||
ExtensionAuthor: old.ExtensionAuthor,
|
||||
OriginalAuthor: old.OriginalAuthor,
|
||||
@@ -215,7 +215,9 @@ func ParseExtensionManifest(data []byte) (*ExtensionManifest, error) {
|
||||
err := json.Unmarshal(data, &extManifest)
|
||||
if err != nil || len(extManifest.ExtCommand) == 0 { //extensions must have at least one command to be sensible
|
||||
//maybe it's an old manifest
|
||||
log.Println(err)
|
||||
if err != nil {
|
||||
log.Printf("extension load error: %s", err)
|
||||
}
|
||||
oldmanifest := &ExtensionManifest_{}
|
||||
err := json.Unmarshal(data, &oldmanifest)
|
||||
if err != nil {
|
||||
@@ -412,7 +414,7 @@ func loadExtension(goos string, goarch string, checkCache bool, ext *ExtCommand,
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerExtension(goos string, ext *ExtCommand, binData []byte, cmd *cobra.Command, con *console.SliverClient) error {
|
||||
func registerExtension(goos string, _ *ExtCommand, binData []byte, cmd *cobra.Command, con *console.SliverClient) error {
|
||||
//set extension name to a hash of the data to avoid loading more than one instance
|
||||
bd := sha256.Sum256(binData)
|
||||
name := hex.EncodeToString(bd[:])
|
||||
@@ -567,7 +569,7 @@ func PrintExtOutput(extName string, commandName string, callExtension *sliverpb.
|
||||
}
|
||||
}
|
||||
|
||||
func getExtArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtCommand) ([]byte, error) {
|
||||
func getExtArgs(_ *cobra.Command, args []string, _ string, ext *ExtCommand) ([]byte, error) {
|
||||
var err error
|
||||
argsBuffer := core.BOFArgsBuffer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
|
||||
@@ -184,6 +184,28 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
profilesCmd.AddCommand(profilesNewCmd)
|
||||
|
||||
profilesStageCmd := &cobra.Command{
|
||||
Use: consts.StageStr,
|
||||
Short: "Generate implant from a profile and encode or encrypt it",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Long: help.GetHelpFor([]string{consts.ProfilesStr, consts.StageStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ProfilesStageCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("profiles", false, profilesStageCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("save", "s", "", "directory/file to the binary to")
|
||||
f.StringP("name", "n", "", "Implant name")
|
||||
f.StringP("aesEncryptKey", "k", "", "AES Encryption Key")
|
||||
f.StringP("aesEncryptIv", "i", "", "AES Encryption IV")
|
||||
f.StringP("rc4EncryptKey", "r", "", "RC4 encryption key")
|
||||
f.BoolP("prepend", "p", false, "Prepend stage size")
|
||||
f.StringP("compress", "c", "", "Compress stage (zlib, gzip, deflate9 or deflate)")
|
||||
})
|
||||
|
||||
carapace.Gen(profilesStageCmd).PositionalCompletion(ProfileNameCompleter(con))
|
||||
profilesCmd.AddCommand(profilesStageCmd)
|
||||
|
||||
// Session flags and completions.
|
||||
coreImplantFlags("session", profilesNewCmd)
|
||||
compileImplantFlags("session", profilesNewCmd)
|
||||
|
||||
@@ -108,6 +108,7 @@ var (
|
||||
// Profiles
|
||||
consts.ProfilesStr + sep + consts.NewStr: newProfileHelp,
|
||||
consts.ProfilesStr + sep + consts.GenerateStr: generateProfileHelp,
|
||||
consts.ProfilesStr + sep + consts.StageStr: generateProfileStageHelp,
|
||||
|
||||
// Reactions
|
||||
consts.ReactionStr: reactionHelp,
|
||||
@@ -249,6 +250,9 @@ settings. Generated implants will still have per-binary certificates/obfuscation
|
||||
generateProfileHelp = `[[.Bold]]Command:[[.Normal]] generate [name] <options>
|
||||
[[.Bold]]About:[[.Normal]] Generate an implant from a saved profile (see 'profiles new --help').`
|
||||
|
||||
generateProfileStageHelp = `[[.Bold]]Command:[[.Normal]] stage [name] <options>
|
||||
[[.Bold]]About:[[.Normal]] Generate and encrypt or encode an implant from a saved profile (see 'profiles stage --help').`
|
||||
|
||||
msfHelp = `[[.Bold]]Command:[[.Normal]] msf [--lhost] <options>
|
||||
[[.Bold]]About:[[.Normal]] Execute a metasploit payload in the current process.`
|
||||
|
||||
|
||||
@@ -356,7 +356,7 @@ func (con *SliverClient) triggerReactions(event *clientpb.Event) {
|
||||
for _, reaction := range reactions {
|
||||
for _, line := range reaction.Commands {
|
||||
con.PrintInfof(Bold+"Execute reaction: '%s'"+Normal, line)
|
||||
err := con.App.ActiveMenu().RunCommand(line)
|
||||
err := con.App.ActiveMenu().RunCommandLine(line)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Reaction command error: %s\n", err)
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
module github.com/bishopfox/sliver
|
||||
|
||||
go 1.21.1
|
||||
go 1.22.0
|
||||
|
||||
toolchain go1.21.4
|
||||
toolchain go1.22.2
|
||||
|
||||
replace github.com/rsteube/carapace v0.36.3 => github.com/reeflective/carapace v0.25.2-0.20230602202234-e8d757e458ca
|
||||
replace github.com/rsteube/carapace v0.36.3 => github.com/reeflective/carapace v0.46.3-0.20231214181515-27e49f3c3b69
|
||||
|
||||
require (
|
||||
filippo.io/age v1.1.1
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/Binject/binjection v0.0.0-20200705191933-da1a50d7013d
|
||||
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf
|
||||
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313
|
||||
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb
|
||||
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a
|
||||
github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c
|
||||
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0
|
||||
github.com/Ne0nd0g/go-clr v1.0.3
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/cheggaaa/pb/v3 v3.1.2
|
||||
github.com/chromedp/cdproto v0.0.0-20231011050154-1d073bb38998
|
||||
github.com/chromedp/chromedp v0.9.3
|
||||
github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071
|
||||
github.com/chromedp/chromedp v0.9.5
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/glebarez/sqlite v1.10.0
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/golang/protobuf v1.5.3
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.8
|
||||
@@ -30,46 +30,46 @@ require (
|
||||
github.com/klauspost/compress v1.17.8
|
||||
github.com/lesnuages/go-winio v0.4.19
|
||||
github.com/lesnuages/snitch v0.6.0
|
||||
github.com/mattn/go-sqlite3 v1.14.18
|
||||
github.com/miekg/dns v1.1.57
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/moloch--/asciicast v0.1.0
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e
|
||||
github.com/ncruces/go-sqlite3 v0.7.2
|
||||
github.com/reeflective/console v0.1.6
|
||||
github.com/reeflective/readline v1.0.13
|
||||
github.com/rsteube/carapace v0.36.3
|
||||
github.com/reeflective/console v0.1.15
|
||||
github.com/reeflective/readline v1.0.14
|
||||
github.com/rsteube/carapace v0.46.3-0.20231214181515-27e49f3c3b69
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tetratelabs/wazero v1.3.1
|
||||
github.com/things-go/go-socks5 v0.0.3
|
||||
github.com/ulikunitz/xz v0.5.11
|
||||
github.com/tetratelabs/wazero v1.7.1
|
||||
github.com/things-go/go-socks5 v0.0.5
|
||||
github.com/ulikunitz/xz v0.5.12
|
||||
github.com/xlab/treeprint v1.2.0
|
||||
github.com/yiya1989/sshkrb5 v0.0.1
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||
golang.org/x/net v0.23.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/term v0.18.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/term v0.19.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68
|
||||
google.golang.org/grpc v1.59.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
google.golang.org/grpc v1.63.2
|
||||
google.golang.org/protobuf v1.34.0
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8
|
||||
gorm.io/driver/mysql v1.5.1
|
||||
gorm.io/driver/postgres v1.5.2
|
||||
gorm.io/driver/sqlite v1.5.4
|
||||
gorm.io/gorm v1.25.5
|
||||
gvisor.dev/gvisor v0.0.0-20231116203655-c480f66679e6
|
||||
modernc.org/sqlite v1.27.0
|
||||
tailscale.com v1.54.0
|
||||
gorm.io/driver/mysql v1.5.6
|
||||
gorm.io/driver/postgres v1.5.7
|
||||
gorm.io/driver/sqlite v1.5.5
|
||||
gorm.io/gorm v1.25.10
|
||||
gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3
|
||||
modernc.org/sqlite v1.29.8
|
||||
tailscale.com v1.64.2
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 // indirect
|
||||
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
@@ -79,48 +79,52 @@ require (
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
|
||||
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 // indirect
|
||||
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.42 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.38.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 // indirect
|
||||
github.com/aws/smithy-go v1.14.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
|
||||
github.com/aws/smithy-go v1.19.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
||||
github.com/chromedp/sysutil v1.0.0 // indirect
|
||||
github.com/coreos/go-iptables v0.7.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dblohm7/wingoes v0.0.0-20230929194252-e994401fc077 // indirect
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
|
||||
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d // indirect
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 // indirect
|
||||
github.com/gaissmai/bart v0.4.1 // indirect
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.3.0 // indirect
|
||||
github.com/gobwas/ws v1.3.2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c // indirect
|
||||
github.com/gorilla/csrf v1.7.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/gorilla/csrf v1.7.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||
github.com/hdevalence/ed25519consensus v0.1.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
||||
github.com/illarion/gonotify v1.0.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a // indirect
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.5.4 // indirect
|
||||
@@ -131,7 +135,7 @@ require (
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect
|
||||
github.com/jsimonetti/rtnetlink v1.3.5 // indirect
|
||||
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
@@ -145,51 +149,50 @@ require (
|
||||
github.com/mdlayher/socket v0.5.0 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/ncruces/julianday v0.1.5 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rsteube/carapace-shlex v0.1.1 // indirect
|
||||
github.com/safchain/ethtool v0.3.0 // indirect
|
||||
github.com/tailscale/certstore v0.1.1-0.20231020161753-77811a65f4ff // indirect
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e // indirect
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20240108194725-7ce1f622c780 // indirect
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
|
||||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 // indirect
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20231114171715-25f8d12b3c2d // indirect
|
||||
github.com/tailscale/wireguard-go v0.0.0-20231101022006-db7604d1aa90 // indirect
|
||||
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 // indirect
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect
|
||||
github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272 // indirect
|
||||
github.com/tcnksm/go-httpstat v0.2.0 // indirect
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
|
||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
|
||||
golang.org/x/mod v0.13.0 // indirect
|
||||
golang.org/x/sync v0.4.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.20.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 // indirect
|
||||
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.29.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.49.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
nhooyr.io/websocket v1.8.7 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
mvdan.cc/sh/v3 v3.7.0 // indirect
|
||||
nhooyr.io/websocket v1.8.10 // indirect
|
||||
)
|
||||
@@ -1,30 +1,30 @@
|
||||
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
|
||||
cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
|
||||
cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg=
|
||||
filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE=
|
||||
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
|
||||
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
|
||||
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
|
||||
github.com/AlecAivazis/survey/v2 v2.0.5/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||
github.com/Binject/binjection v0.0.0-20200705191933-da1a50d7013d h1:uq6FM7b88vkNEqwjyP2mK6oL8cymY5b75Luu6ADTStY=
|
||||
github.com/Binject/binjection v0.0.0-20200705191933-da1a50d7013d/go.mod h1:rJLtHgGUqTxJbfg+b+LjWBBPuYUzFFza1LXFhrzfxHA=
|
||||
github.com/Binject/debug v0.0.0-20200621050044-63af8e405111/go.mod h1:6WgJ7mEG7ngJkNwwDkEVreDk2dPGg3oqdI6OEWaeDNg=
|
||||
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb h1:c1Bb1n1wr2xgTHkNVxkUHBshff33m+IXgY6lE6oYttg=
|
||||
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb/go.mod h1:fSQ9asCJK/lIGXUnoRwJNMXcnzel53AsZMCtlVBIn3k=
|
||||
github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
|
||||
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf h1:Cx4YJvjPZD91xiffqJOq8l3j1YKcvx3+8duqq7DX9gY=
|
||||
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
|
||||
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e h1:ytVmxGQuS7ELO/WpvH6iuY1hVcJ6iOTw3VLOOIFlo8o=
|
||||
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e/go.mod h1:dc3mUnr4KTKcFKVq7BVbHGF0xAHrIyooQ+VTO7/bIZw=
|
||||
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a h1:4c0nc0krv8eh7gD809n+swLaCuFyHpxdrxwx0ZmHvBw=
|
||||
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
|
||||
github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c h1:cFMQKxryHZ3Sddca8nqJ1RDjaUTW/+79lRKs+7508nk=
|
||||
github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c/go.mod h1:dc3mUnr4KTKcFKVq7BVbHGF0xAHrIyooQ+VTO7/bIZw=
|
||||
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 h1:T8z8Wz/fqaPPANF8Unv4LjWLoa0TAvsjYAsRpvSkOqs=
|
||||
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563/go.mod h1:HMbzsKPz1sF7H4Hmeovh+d2PH2iKPYpAB4XMOVz8wmM=
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313 h1:hX9boCRvCxIpsiV4q9ts+WOYNnjHiji2FOR5BsDzxiY=
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313/go.mod h1:J3XDRlam5pPYca3i6EqgQ35GCCEoyxafpCbLkta0ozc=
|
||||
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0 h1:1sdnMvqxxlMXsu8ku/Xrh6gi3V40ykksst5fd0lfWnc=
|
||||
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0/go.mod h1:J3XDRlam5pPYca3i6EqgQ35GCCEoyxafpCbLkta0ozc=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
@@ -41,7 +41,6 @@ github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab h1:96tkQLYmgypA3W
|
||||
github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE=
|
||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||
github.com/akamensky/argparse v1.2.1/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
|
||||
github.com/akamensky/argparse v1.3.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
|
||||
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
||||
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
||||
@@ -55,85 +54,91 @@ github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 h1:JjwxKkxzcBk4k8147g0e
|
||||
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0/go.mod h1:IbERvuyb387Hppp8hX0SQTFt/mkej8+OhuS8L0nC2CI=
|
||||
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 h1:cIAK2NNf2yafdgpFRNJrgZMwvy61BEVpGoHc2n4/yWs=
|
||||
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4/go.mod h1:SalMPBCab3yuID8nIhLfzwoBV+lBRyaC7NhuN8qL8xE=
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc=
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.42 h1:28jHROB27xZwU0CB88giDSjz7M1Sba3olb5JBGwina8=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.42/go.mod h1:4AZM3nMMxwlG+eZlxvBKqwVbkDLlnN2a4UGTL6HjaZI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.40 h1:s8yOkDh+5b1jUDhMBtngF6zKWLDs84chUk2Vk0c38Og=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.40/go.mod h1:VtEHVAAqDWASwdOqj/1huyT6uHbs5s8FUHfDQdky/Rs=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 h1:g+qlObJH4Kn4n21g69DjspU0hKTjWtq7naZ9OLCv0ew=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.38.0 h1:JON9MBvwUlM8HXylfB2caZuH3VXz9RxO4SMp2+TNc3Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.38.0/go.mod h1:JjBzoceyKkpQY3v1GPIdg6kHqUFHRJ7SDlwtwoH0Qh8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 h1:YkNzx1RLS0F5qdf9v1Q8Cuv9NXCL2TkosOxhzlUPV64=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 h1:8lKOidPkmSmfUtiTgtdXWgaKItCZ/g75/jEk6Ql6GsA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 h1:s4bioTgjSFRwOoyEFzAVCmFmoowBgjTR8gkrF/sQ4wk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU=
|
||||
github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ=
|
||||
github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
|
||||
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
|
||||
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
||||
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cheggaaa/pb/v3 v3.1.2 h1:FIxT3ZjOj9XJl0U4o2XbEhjFfZl7jCVCDOGq1ZAB7wQ=
|
||||
github.com/cheggaaa/pb/v3 v3.1.2/go.mod h1:SNjnd0yKcW+kw0brSusraeDd5Bf1zBfxAzTL2ss3yQ4=
|
||||
github.com/chromedp/cdproto v0.0.0-20231011050154-1d073bb38998 h1:2zipcnjfFdqAjOQa8otCCh0Lk1M7RBzciy3s80YAKHk=
|
||||
github.com/chromedp/cdproto v0.0.0-20231011050154-1d073bb38998/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/chromedp v0.9.3 h1:Wq58e0dZOdHsxaj9Owmfcf+ibtpYN1N0FWVbaxa/esg=
|
||||
github.com/chromedp/chromedp v0.9.3/go.mod h1:NipeUkUcuzIdFbBP8eNNvl9upcceOfWzoJn6cRe4ksA=
|
||||
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071 h1:RdCf9hH3xq5vJifrjGB7zQlFkdRB3pAppcX2helDq2U=
|
||||
github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg=
|
||||
github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y=
|
||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
|
||||
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
|
||||
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
|
||||
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
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/dblohm7/wingoes v0.0.0-20230929194252-e994401fc077 h1:WphxHslVftszsr0oZOHPaOjpmN/BsgNYF+gW/hxZXXc=
|
||||
github.com/dblohm7/wingoes v0.0.0-20230929194252-e994401fc077/go.mod h1:6NCrWM5jRefaG7iN0iMShPalLsljHWBh9v1zxM2f8Xs=
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
|
||||
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d h1:hmOGJg3cq5XK2aMs7R4kXXVSHqHMaC5hI5fwkX7V2zE=
|
||||
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d/go.mod h1:q72QzdO6OUjwTqnLCFJczIQ7GsBa4ffzkQiQcq6rVTY=
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI=
|
||||
github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/gaissmai/bart v0.4.1 h1:G1t58voWkNmT47lBDawH5QhtTDsdqRIO+ftq5x4P9Ls=
|
||||
github.com/gaissmai/bart v0.4.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
@@ -144,25 +149,15 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/gobwas/ws v1.3.0 h1:sbeU3Y4Qzlb+MOzIe6mQGf7QR4Hkv6ZD0qhGkBFL2O0=
|
||||
github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
|
||||
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
||||
@@ -177,42 +172,36 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c h1:06RMfw+TMMHtRuUOroMeatRCCgSMWXCJQeABvHU69YQ=
|
||||
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c/go.mod h1:BVIYo3cdnT4qSylnYqcd5YtmXhr51cJPGtnLBe/uLBU=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE=
|
||||
github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
|
||||
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
|
||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU=
|
||||
github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
@@ -220,8 +209,8 @@ github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwso
|
||||
github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
@@ -234,6 +223,8 @@ github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem
|
||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.8 h1:8BCzJdSvUbaDuRba4YVh+SKMGcAAKdkcF3SVFbrHAtQ=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.8/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
|
||||
github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g=
|
||||
github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
@@ -244,22 +235,17 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8=
|
||||
github.com/jsimonetti/rtnetlink v1.3.5 h1:hVlNQNRlLDGZz31gBPicsG7Q53rnlsz1l1Ix/9XlpVA=
|
||||
github.com/jsimonetti/rtnetlink v1.3.5/go.mod h1:0LFedyiTkebnd43tE4YAkWGIq9jQphow4CcwxaT2Y00=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
|
||||
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f h1:5hWo+DzJQSOBl6X+TDac0SPWffRonuRJ2///OYtYRT8=
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f/go.mod h1:f8GY5V3lRzakvEyr49P7hHRYoHtPr8zvj/7JodCoRzw=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -277,8 +263,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/lesnuages/go-winio v0.4.19 h1:lFDu1mnhg5em+8zTHO4ChSD11J56xkyE8m3N8IrVmbA=
|
||||
github.com/lesnuages/go-winio v0.4.19/go.mod h1:rm7jf4kBcldxMeljR7c7XY1qVCBc+8z3PtSgkrT9Clk=
|
||||
github.com/lesnuages/snitch v0.6.0 h1:vOao32MdYDHYDcTAq2CKszDh6Dn8Bv1Gmgc8uVh193U=
|
||||
@@ -288,72 +272,62 @@ github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
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.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c=
|
||||
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
|
||||
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
|
||||
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
|
||||
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
||||
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/moloch--/asciicast v0.1.0 h1:eBOJwuFKSk447s/kPs9MWsc4kAl5HmuKIDLDYD6/RrM=
|
||||
github.com/moloch--/asciicast v0.1.0/go.mod h1:OckO16UDLgxVLclrCnbocL1ix15Br/8Xv/caBoYq98o=
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945 h1:m3yCfV8Vqp4MF1B+gPPjbjINdufl0UXqyYplE0aGhx8=
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945/go.mod h1:1grVt4HaTofvhFUZYtofeRbGXfczNwCie9MYoM4lP/o=
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e h1:IkFCPlAa0iTiLxck+NqAwBx8JDlnHYm4orOQBbs4BDQ=
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e/go.mod h1:eYeI6cQ5YHhHt9i0BBW0zc1DaQnb4ZMXsSPuEV/V5Og=
|
||||
github.com/ncruces/go-sqlite3 v0.7.2 h1:K7jU4rnUxFdUsbEL+B0Xc+VexLTEwGSO6Qh91Qh4hYc=
|
||||
github.com/ncruces/go-sqlite3 v0.7.2/go.mod h1:t3dP4AP9rJddU+ffFv0h6fWyeOCEhjxrYc1nsYG7aQI=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ncruces/julianday v0.1.5 h1:hDJ9ejiMp3DHsoZ5KW4c1lwfMjbARS7u/gbYcd0FBZk=
|
||||
github.com/ncruces/julianday v0.1.5/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/reeflective/carapace v0.25.2-0.20230602202234-e8d757e458ca h1:tD797h1qmNtS/2z6Y7EtIg7OXEDaoSuULsUoksEepmQ=
|
||||
github.com/reeflective/carapace v0.25.2-0.20230602202234-e8d757e458ca/go.mod h1:jkLt41Ne2TD2xPuMdX/2O05Smhy8vMgG7O2TYvC0yOc=
|
||||
github.com/reeflective/console v0.1.6 h1:BhhvQU/m8QOpaIRzrXfwcbtkGQJX9jVR5qIqAy/Mcuw=
|
||||
github.com/reeflective/console v0.1.6/go.mod h1:7owTBE9k2lg2QpVw7g4DrK1HxEgr/5DQCmA3O2Znoek=
|
||||
github.com/reeflective/readline v1.0.13 h1:TeJmYw9B7VRPZWfNExr9QHxL1m0iSicyqBSQIRn39Ss=
|
||||
github.com/reeflective/readline v1.0.13/go.mod h1:3iOe/qyb2jEy0KqLrNlb/CojBVqxga9ACqz/VU22H6A=
|
||||
github.com/reeflective/console v0.1.15 h1:r4M1a19s882znSO5Zkj7memsLSDTLT/0fZwdNzhIldE=
|
||||
github.com/reeflective/console v0.1.15/go.mod h1:U2i+gzsZ5mT9LZHLzoeuOJ7BtcyXy7l+psRZSu4zmQU=
|
||||
github.com/reeflective/readline v1.0.14 h1:sgKLpi3figH8IcWx3Hu3rCeGJQ6W+6KtZ3PTdcAD3jA=
|
||||
github.com/reeflective/readline v1.0.14/go.mod h1:3iOe/qyb2jEy0KqLrNlb/CojBVqxga9ACqz/VU22H6A=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
@@ -361,6 +335,10 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rsteube/carapace v0.46.3-0.20231214181515-27e49f3c3b69 h1:ctOUuKn5PO6VtwtaS7unNrm6u20YXESPtnKEie/u304=
|
||||
github.com/rsteube/carapace v0.46.3-0.20231214181515-27e49f3c3b69/go.mod h1:4ZC5bulItu9t9sZ5yPcHgPREd8rPf274Q732n+wfl/o=
|
||||
github.com/rsteube/carapace-shlex v0.1.1 h1:fRQEBBKyYKm4TXUabm4tzH904iFWSmXJl3UZhMfQNYU=
|
||||
github.com/rsteube/carapace-shlex v0.1.1/go.mod h1:zPw1dOFwvLPKStUy9g2BYKanI6bsQMATzDMYQQybo3o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
||||
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||
@@ -368,7 +346,6 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
@@ -383,40 +360,44 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231020161753-77811a65f4ff h1:vnxdYZUJbsSRcIcduDW3DcQqfqaiv4FUgy25q8X+vfI=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231020161753-77811a65f4ff/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e h1:JyeJF/HuSwvxWtsR1c0oKX1lzaSH5Wh4aX+MgiStaGQ=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e/go.mod h1:DjoeCULdP6vTJ/xY+nzzR9LaUHprkbZEpNidX0aqEEk=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20240108194725-7ce1f622c780 h1:U0J2CUrrTcc2wmr9tSLYEo+USfwNikRRsmxVLD4eZ7E=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20240108194725-7ce1f622c780/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
|
||||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk=
|
||||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20231114171715-25f8d12b3c2d h1:6bpLeKxSPVTwxzoy+0SrDLEaa60G6jnGqgLAcdDhkDg=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20231114171715-25f8d12b3c2d/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20231101022006-db7604d1aa90 h1:lMGYrokOq9NKDw1UMBH7AsS4boZ41jcduvYaRIdedhE=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20231101022006-db7604d1aa90/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
||||
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w=
|
||||
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
||||
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
|
||||
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272 h1:zwsem4CaamMdC3tFoTpzrsUSMDPV0K6rhnQdF7kXekQ=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
||||
github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9 h1:81P7rjnikHKTJ75EkjppvbwUfKHDHYk6LJpO5PZy8pA=
|
||||
github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
|
||||
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
|
||||
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
|
||||
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8=
|
||||
github.com/tetratelabs/wazero v1.3.1 h1:rnb9FgOEQRLLR8tgoD1mfjNjMhFeWRUk+a4b4j/GpUM=
|
||||
github.com/tetratelabs/wazero v1.3.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
|
||||
github.com/tetratelabs/wazero v1.7.1 h1:QtSfd6KLc41DIMpDYlJdoMc6k7QTN246DM2+n2Y/Dx8=
|
||||
github.com/tetratelabs/wazero v1.7.1/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2 h1:CoMVaYyKFsVj6TjU6APqAhAvC07hTI6IQen8PHzHYY0=
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2/go.mod h1:bv6Xa7kWy82uT0LnXPE2SzGqTj33TAEeR560MdJkiXs=
|
||||
github.com/things-go/go-socks5 v0.0.3 h1:QtlIhkwDuLNCwW3wnt2uTjn1mQzpyjnwct2xdPuqroI=
|
||||
github.com/things-go/go-socks5 v0.0.3/go.mod h1:f8Zx+n8kfzyT90hXM767cP6sysAud93+t9rV90IgMcg=
|
||||
github.com/u-root/u-root v0.11.0 h1:6gCZLOeRyevw7gbTwMj3fKxnr9+yHFlgF3N7udUVNO8=
|
||||
github.com/u-root/u-root v0.11.0/go.mod h1:DBkDtiZyONk9hzVEdB/PWI9B4TxDkElWlVTHseglrZY=
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
|
||||
github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ=
|
||||
github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs=
|
||||
github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI=
|
||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw=
|
||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
@@ -437,23 +418,23 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
|
||||
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8=
|
||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9 h1:j3D9DvWRpUfIyFfDPws7LoIZ2MAI1OJHdQXtTnYtN+k=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a h1:8qmSSA8Gz/1kTrCe0nqR0R3Gb/NDhykzWw2q2mWZydM=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@@ -461,8 +442,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -472,16 +453,12 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
|
||||
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
|
||||
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
|
||||
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -489,8 +466,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -500,22 +477,15 @@ golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -526,23 +496,21 @@ golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -553,21 +521,18 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d/go.mod h1:5yyfuiqVIJ7t+3MqrpTQ+QqRkMWiESiyDvPNvKYCecg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178/go.mod h1:TjUWrnD5ATh7bFvmm/ALEJZQ4ivKbETb6pmyj1vUoNI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb h1:c5tyN8sSp8jSDxdCCDXVOpJwYXXhmTkNMt+g0zTSOic=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68 h1:9c4/JVIQUc2qCJEEIiGIs3HmmnFjhPj4qHW4+Uj+u3U=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68/go.mod h1:8P32Ilp1kCpwB4ItaHyvSk4xAtnpQ+8gQVfg5WaO1TU=
|
||||
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-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
@@ -577,19 +542,17 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
|
||||
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8 h1:5UtTowJZTz1j7NxVzDGKTz6Lm9IWm8DDF6b7a2wq9VY=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8/go.mod h1:CaHjv79TCgAvXMSFJSVgonHXYWxnhzI3eoHtnX5UgUo=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -614,58 +577,54 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
|
||||
gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
|
||||
gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
|
||||
gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
|
||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gvisor.dev/gvisor v0.0.0-20231116203655-c480f66679e6 h1:QYF5D47yyyibiD9otaga9OZu9JK/epGfDfJckWn3TAI=
|
||||
gvisor.dev/gvisor v0.0.0-20231116203655-c480f66679e6/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||
gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
|
||||
gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
|
||||
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
|
||||
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3 h1:/8/t5pz/mgdRXhYOIeqqYhFAQLE4DDGegc0Y4ZjyFJM=
|
||||
gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8=
|
||||
honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a h1:qdkS8Q5/i10xU2ArJMKYhVa1DORzBfYS/qA2UK2jheg=
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU=
|
||||
inet.af/wf v0.0.0-20221017222439-36129f591884 h1:zg9snq3Cpy50lWuVqDYM7AIRVTtU50y5WXETMFohW/Q=
|
||||
inet.af/wf v0.0.0-20221017222439-36129f591884/go.mod h1:bSAQ38BYbY68uwpasXOTZo22dKGy9SNvI6PZFeKomZE=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs=
|
||||
modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
|
||||
modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
|
||||
modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA=
|
||||
modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg=
|
||||
modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8=
|
||||
modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
|
||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
|
||||
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
|
||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.1 h1:tbT1jjaeFOF230tzOIRJ6U5S1jNqpsSyNjzDd58H3J8=
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.1/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
tailscale.com v1.54.0 h1:Dri5BTKkHYpl+/t8ofY+tyvoTDbH/FpP7iB4B0cAQOY=
|
||||
tailscale.com v1.54.0/go.mod h1:MnLFoCRwzFWr3qtkSW2nZdQpK7wQRZEk1KtcEGAuZYw=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.29.8 h1:nGKglNx9K5v0As+zF0/Gcl1kMkmaU1XynYyq92PbsC8=
|
||||
modernc.org/sqlite v1.29.8/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
|
||||
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
|
||||
nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
|
||||
nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
tailscale.com v1.64.2 h1:0VNwUsjK6CwgkqyaOANndBER2SMYl8JZ5uNRTvIqCnY=
|
||||
tailscale.com v1.64.2/go.mod h1:6kGByHNxnFfK1i4gVpdtvpdS1HicHohWXnsfwmXy64I=
|
||||
+10
-10
@@ -6,22 +6,22 @@ toolchain go1.21.4
|
||||
|
||||
require (
|
||||
filippo.io/age v1.1.1
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313
|
||||
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0
|
||||
github.com/Ne0nd0g/go-clr v1.0.3
|
||||
github.com/gofrs/uuid v4.0.0+incompatible
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
|
||||
github.com/lesnuages/go-winio v0.4.19
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tetratelabs/wazero v1.2.0
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tetratelabs/wazero v1.7.1
|
||||
github.com/things-go/go-socks5 v0.0.4
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e
|
||||
golang.org/x/crypto v0.13.0
|
||||
golang.org/x/net v0.15.0
|
||||
golang.org/x/sys v0.14.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb
|
||||
google.golang.org/protobuf v1.31.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
google.golang.org/protobuf v1.32.0
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259
|
||||
)
|
||||
|
||||
@@ -38,9 +38,9 @@ require (
|
||||
github.com/jcmturner/gofork v1.0.0 // indirect
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/mod v0.13.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
|
||||
|
||||
+20
-24
@@ -2,8 +2,8 @@ filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg=
|
||||
filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE=
|
||||
github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2 h1:8kQNJC9AxAaNs0JkXnWUfbNeDnIO1QLYYWjYqC6JEE4=
|
||||
github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313 h1:hX9boCRvCxIpsiV4q9ts+WOYNnjHiji2FOR5BsDzxiY=
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313/go.mod h1:J3XDRlam5pPYca3i6EqgQ35GCCEoyxafpCbLkta0ozc=
|
||||
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0 h1:1sdnMvqxxlMXsu8ku/Xrh6gi3V40ykksst5fd0lfWnc=
|
||||
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0/go.mod h1:J3XDRlam5pPYca3i6EqgQ35GCCEoyxafpCbLkta0ozc=
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk=
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||
@@ -21,10 +21,8 @@ github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u7
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||
@@ -39,8 +37,8 @@ github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945 h1:m3yCfV8Vqp4MF1B+gPPjbjINdufl0UXqyYplE0aGhx8=
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945/go.mod h1:1grVt4HaTofvhFUZYtofeRbGXfczNwCie9MYoM4lP/o=
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e h1:IkFCPlAa0iTiLxck+NqAwBx8JDlnHYm4orOQBbs4BDQ=
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e/go.mod h1:eYeI6cQ5YHhHt9i0BBW0zc1DaQnb4ZMXsSPuEV/V5Og=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -48,12 +46,13 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tetratelabs/wazero v1.2.0 h1:I/8LMf4YkCZ3r2XaL9whhA0VMyAvF6QE+O7rco0DCeQ=
|
||||
github.com/tetratelabs/wazero v1.2.0/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43 h1:+sN63SJGqchSsVyNoeW4GyA0Y+g0LvKOHcaeqs1Rf6M=
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43/go.mod h1:dhnDTBbIg31cbJdROP4/Zz6Iw7JPEpiMvOl2LdHSSjE=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tetratelabs/wazero v1.7.1 h1:QtSfd6KLc41DIMpDYlJdoMc6k7QTN246DM2+n2Y/Dx8=
|
||||
github.com/tetratelabs/wazero v1.7.1/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
|
||||
github.com/things-go/go-socks5 v0.0.4 h1:jMQjIc+qhD4z9cITOMnBiwo9dDmpGuXmBlkRFrl/qD0=
|
||||
github.com/things-go/go-socks5 v0.0.4/go.mod h1:sh4K6WHrmHZpjxLTCHyYtXYH8OUuD+yZun41NomR1IQ=
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e h1:6SWomM8ezwUwnN3lqKxE4r7bYVwpY9514DQkwBV9IPI=
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e/go.mod h1:r1bHcNa22x8rrg/Kj/cDF5bX6UtZrHiQgRFvj3Mnpl8=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
@@ -63,12 +62,11 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
@@ -88,8 +86,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
@@ -99,8 +97,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
@@ -108,15 +106,13 @@ golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb h1:c5tyN8sSp8jSDxdCCDXVOpJwYXXhmTkNMt+g0zTSOic=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
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=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
|
||||
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package codesign provides basic functionalities for
|
||||
// ad-hoc code signing of Mach-O files.
|
||||
//
|
||||
// This is not a general tool for code-signing. It is made
|
||||
// specifically for the Go toolchain. It uses the same
|
||||
// ad-hoc signing algorithm as the Darwin linker.
|
||||
package universal
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"debug/macho"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Code signature layout.
|
||||
//
|
||||
// The code signature is a block of bytes that contains
|
||||
// a SuperBlob, which contains one or more Blobs. For ad-hoc
|
||||
// signing, a single CodeDirectory Blob suffices.
|
||||
//
|
||||
// A SuperBlob starts with its header (the binary representation
|
||||
// of the SuperBlob struct), followed by a list of (in our case,
|
||||
// one) Blobs (offset and size). A CodeDirectory Blob starts
|
||||
// with its head (the binary representation of CodeDirectory struct),
|
||||
// followed by the identifier (as a C string) and the hashes, at
|
||||
// the corresponding offsets.
|
||||
//
|
||||
// The signature data must be included in the __LINKEDIT segment.
|
||||
// In the Mach-O file header, an LC_CODE_SIGNATURE load command
|
||||
// points to the data.
|
||||
|
||||
const (
|
||||
pageSizeBits = 12
|
||||
pageSize = 1 << pageSizeBits
|
||||
machoHeaderSize64 = 8 * 4 // size of 64-bit Mach-O header
|
||||
)
|
||||
|
||||
const LC_CODE_SIGNATURE = 0x1d
|
||||
|
||||
// Constants and struct layouts are from
|
||||
// https://opensource.apple.com/source/xnu/xnu-4903.270.47/osfmk/kern/cs_blobs.h
|
||||
|
||||
const (
|
||||
CSMAGIC_REQUIREMENT = 0xfade0c00 // single Requirement blob
|
||||
CSMAGIC_REQUIREMENTS = 0xfade0c01 // Requirements vector (internal requirements)
|
||||
CSMAGIC_CODEDIRECTORY = 0xfade0c02 // CodeDirectory blob
|
||||
CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0 // embedded form of signature data
|
||||
CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1 // multi-arch collection of embedded signatures
|
||||
|
||||
CSSLOT_CODEDIRECTORY = 0 // slot index for CodeDirectory
|
||||
)
|
||||
|
||||
const (
|
||||
CS_HASHTYPE_SHA1 = 1
|
||||
CS_HASHTYPE_SHA256 = 2
|
||||
CS_HASHTYPE_SHA256_TRUNCATED = 3
|
||||
CS_HASHTYPE_SHA384 = 4
|
||||
)
|
||||
|
||||
const (
|
||||
CS_EXECSEG_MAIN_BINARY = 0x1 // executable segment denotes main binary
|
||||
CS_EXECSEG_ALLOW_UNSIGNED = 0x10 // allow unsigned pages (for debugging)
|
||||
CS_EXECSEG_DEBUGGER = 0x20 // main binary is debugger
|
||||
CS_EXECSEG_JIT = 0x40 // JIT enabled
|
||||
CS_EXECSEG_SKIP_LV = 0x80 // skip library validation
|
||||
CS_EXECSEG_CAN_LOAD_CDHASH = 0x100 // can bless cdhash for execution
|
||||
CS_EXECSEG_CAN_EXEC_CDHASH = 0x200 // can execute blessed cdhash
|
||||
)
|
||||
|
||||
type Blob struct {
|
||||
typ uint32 // type of entry
|
||||
offset uint32 // offset of entry
|
||||
// data follows
|
||||
}
|
||||
|
||||
func (b *Blob) put(out []byte) []byte {
|
||||
out = put32be(out, b.typ)
|
||||
out = put32be(out, b.offset)
|
||||
return out
|
||||
}
|
||||
|
||||
const blobSize = 2 * 4
|
||||
|
||||
type SuperBlob struct {
|
||||
magic uint32 // magic number
|
||||
length uint32 // total length of SuperBlob
|
||||
count uint32 // number of index entries following
|
||||
// blobs []Blob
|
||||
}
|
||||
|
||||
func (s *SuperBlob) put(out []byte) []byte {
|
||||
out = put32be(out, s.magic)
|
||||
out = put32be(out, s.length)
|
||||
out = put32be(out, s.count)
|
||||
return out
|
||||
}
|
||||
|
||||
const superBlobSize = 3 * 4
|
||||
|
||||
type CodeDirectory struct {
|
||||
magic uint32 // magic number (CSMAGIC_CODEDIRECTORY)
|
||||
length uint32 // total length of CodeDirectory blob
|
||||
version uint32 // compatibility version
|
||||
flags uint32 // setup and mode flags
|
||||
hashOffset uint32 // offset of hash slot element at index zero
|
||||
identOffset uint32 // offset of identifier string
|
||||
nSpecialSlots uint32 // number of special hash slots
|
||||
nCodeSlots uint32 // number of ordinary (code) hash slots
|
||||
codeLimit uint32 // limit to main image signature range
|
||||
hashSize uint8 // size of each hash in bytes
|
||||
hashType uint8 // type of hash (cdHashType* constants)
|
||||
_pad1 uint8 // unused (must be zero)
|
||||
pageSize uint8 // log2(page size in bytes); 0 => infinite
|
||||
_pad2 uint32 // unused (must be zero)
|
||||
scatterOffset uint32
|
||||
teamOffset uint32
|
||||
_pad3 uint32
|
||||
codeLimit64 uint64
|
||||
execSegBase uint64
|
||||
execSegLimit uint64
|
||||
execSegFlags uint64
|
||||
// data follows
|
||||
}
|
||||
|
||||
func (c *CodeDirectory) put(out []byte) []byte {
|
||||
out = put32be(out, c.magic)
|
||||
out = put32be(out, c.length)
|
||||
out = put32be(out, c.version)
|
||||
out = put32be(out, c.flags)
|
||||
out = put32be(out, c.hashOffset)
|
||||
out = put32be(out, c.identOffset)
|
||||
out = put32be(out, c.nSpecialSlots)
|
||||
out = put32be(out, c.nCodeSlots)
|
||||
out = put32be(out, c.codeLimit)
|
||||
out = put8(out, c.hashSize)
|
||||
out = put8(out, c.hashType)
|
||||
out = put8(out, c._pad1)
|
||||
out = put8(out, c.pageSize)
|
||||
out = put32be(out, c._pad2)
|
||||
out = put32be(out, c.scatterOffset)
|
||||
out = put32be(out, c.teamOffset)
|
||||
out = put32be(out, c._pad3)
|
||||
out = put64be(out, c.codeLimit64)
|
||||
out = put64be(out, c.execSegBase)
|
||||
out = put64be(out, c.execSegLimit)
|
||||
out = put64be(out, c.execSegFlags)
|
||||
return out
|
||||
}
|
||||
|
||||
const codeDirectorySize = 13*4 + 4 + 4*8
|
||||
|
||||
// CodeSigCmd is Mach-O LC_CODE_SIGNATURE load command.
|
||||
type CodeSigCmd struct {
|
||||
Cmd uint32 // LC_CODE_SIGNATURE
|
||||
Cmdsize uint32 // sizeof this command (16)
|
||||
Dataoff uint32 // file offset of data in __LINKEDIT segment
|
||||
Datasize uint32 // file size of data in __LINKEDIT segment
|
||||
}
|
||||
|
||||
func FindCodeSigCmd(f *macho.File) (CodeSigCmd, bool) {
|
||||
get32 := f.ByteOrder.Uint32
|
||||
for _, l := range f.Loads {
|
||||
data := l.Raw()
|
||||
cmd := get32(data)
|
||||
if cmd == LC_CODE_SIGNATURE {
|
||||
return CodeSigCmd{
|
||||
cmd,
|
||||
get32(data[4:]),
|
||||
get32(data[8:]),
|
||||
get32(data[12:]),
|
||||
}, true
|
||||
}
|
||||
}
|
||||
return CodeSigCmd{}, false
|
||||
}
|
||||
|
||||
func put32be(b []byte, x uint32) []byte { binary.BigEndian.PutUint32(b, x); return b[4:] }
|
||||
func put64be(b []byte, x uint64) []byte { binary.BigEndian.PutUint64(b, x); return b[8:] }
|
||||
func put8(b []byte, x uint8) []byte { b[0] = x; return b[1:] }
|
||||
func puts(b, s []byte) []byte { n := copy(b, s); return b[n:] }
|
||||
|
||||
// Size computes the size of the code signature.
|
||||
// id is the identifier used for signing (a field in CodeDirectory blob, which
|
||||
// has no significance in ad-hoc signing).
|
||||
func Size(codeSize int64, id string) int64 {
|
||||
nhashes := (codeSize + pageSize - 1) / pageSize
|
||||
idOff := int64(codeDirectorySize)
|
||||
hashOff := idOff + int64(len(id)+1)
|
||||
cdirSz := hashOff + nhashes*sha256.Size
|
||||
return int64(superBlobSize+blobSize) + cdirSz
|
||||
}
|
||||
|
||||
// Sign generates an ad-hoc code signature and writes it to out.
|
||||
// out must have length at least Size(codeSize, id).
|
||||
// data is the file content without the signature, of size codeSize.
|
||||
// textOff and textSize is the file offset and size of the text segment.
|
||||
// isMain is true if this is a main executable.
|
||||
// id is the identifier used for signing (a field in CodeDirectory blob, which
|
||||
// has no significance in ad-hoc signing).
|
||||
func Sign(out []byte, data io.Reader, id string, codeSize, textOff, textSize int64, isMain bool) {
|
||||
nhashes := (codeSize + pageSize - 1) / pageSize
|
||||
idOff := int64(codeDirectorySize)
|
||||
hashOff := idOff + int64(len(id)+1)
|
||||
sz := Size(codeSize, id)
|
||||
|
||||
// emit blob headers
|
||||
sb := SuperBlob{
|
||||
magic: CSMAGIC_EMBEDDED_SIGNATURE,
|
||||
length: uint32(sz),
|
||||
count: 1,
|
||||
}
|
||||
blob := Blob{
|
||||
typ: CSSLOT_CODEDIRECTORY,
|
||||
offset: superBlobSize + blobSize,
|
||||
}
|
||||
cdir := CodeDirectory{
|
||||
magic: CSMAGIC_CODEDIRECTORY,
|
||||
length: uint32(sz) - (superBlobSize + blobSize),
|
||||
version: 0x20400,
|
||||
flags: 0x20002, // adhoc | linkerSigned
|
||||
hashOffset: uint32(hashOff),
|
||||
identOffset: uint32(idOff),
|
||||
nCodeSlots: uint32(nhashes),
|
||||
codeLimit: uint32(codeSize),
|
||||
hashSize: sha256.Size,
|
||||
hashType: CS_HASHTYPE_SHA256,
|
||||
pageSize: uint8(pageSizeBits),
|
||||
execSegBase: uint64(textOff),
|
||||
execSegLimit: uint64(textSize),
|
||||
}
|
||||
if isMain {
|
||||
cdir.execSegFlags = CS_EXECSEG_MAIN_BINARY
|
||||
}
|
||||
|
||||
outp := out
|
||||
outp = sb.put(outp)
|
||||
outp = blob.put(outp)
|
||||
outp = cdir.put(outp)
|
||||
|
||||
// emit the identifier
|
||||
outp = puts(outp, []byte(id+"\000"))
|
||||
|
||||
// emit hashes
|
||||
var buf [pageSize]byte
|
||||
h := sha256.New()
|
||||
p := 0
|
||||
for p < int(codeSize) {
|
||||
n, err := io.ReadFull(data, buf[:])
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil && err != io.ErrUnexpectedEOF {
|
||||
panic(err)
|
||||
}
|
||||
if p+n > int(codeSize) {
|
||||
n = int(codeSize) - p
|
||||
}
|
||||
p += n
|
||||
h.Reset()
|
||||
h.Write(buf[:n])
|
||||
b := h.Sum(nil)
|
||||
outp = puts(outp, b[:])
|
||||
}
|
||||
}
|
||||
|
||||
// machoCodeSign - sign the macho file
|
||||
// This code has been borrowed from https://github.com/golang/go/blob/master/src/cmd/internal/codesign/codesign.go
|
||||
func machoCodeSign(fname string) error {
|
||||
f, err := os.OpenFile(fname, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
mf, err := macho.NewFile(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mf.Magic != macho.Magic64 {
|
||||
return fmt.Errorf("not 64-bit Mach-O file: %s", fname)
|
||||
}
|
||||
|
||||
// Find existing LC_CODE_SIGNATURE and __LINKEDIT segment
|
||||
var sigOff, sigSz, csCmdOff, linkeditOff int64
|
||||
var linkeditSeg, textSeg *macho.Segment
|
||||
loadOff := int64(machoHeaderSize64)
|
||||
get32 := mf.ByteOrder.Uint32
|
||||
for _, l := range mf.Loads {
|
||||
data := l.Raw()
|
||||
cmd, sz := get32(data), get32(data[4:])
|
||||
if cmd == LC_CODE_SIGNATURE {
|
||||
sigOff = int64(get32(data[8:]))
|
||||
sigSz = int64(get32(data[12:]))
|
||||
csCmdOff = loadOff
|
||||
}
|
||||
if seg, ok := l.(*macho.Segment); ok {
|
||||
switch seg.Name {
|
||||
case "__LINKEDIT":
|
||||
linkeditSeg = seg
|
||||
linkeditOff = loadOff
|
||||
case "__TEXT":
|
||||
textSeg = seg
|
||||
}
|
||||
}
|
||||
loadOff += int64(sz)
|
||||
}
|
||||
|
||||
if sigOff == 0 {
|
||||
// The C linker doesn't generate a signed binary, for some reason.
|
||||
// Skip.
|
||||
return nil
|
||||
}
|
||||
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sigOff+sigSz != fi.Size() {
|
||||
// We don't expect anything after the signature (this will invalidate
|
||||
// the signature anyway.)
|
||||
return fmt.Errorf("unexpected content after code signature")
|
||||
}
|
||||
|
||||
sz := Size(sigOff, "a.out")
|
||||
if sz != sigSz {
|
||||
// Update the load command,
|
||||
var tmp [8]byte
|
||||
mf.ByteOrder.PutUint32(tmp[:4], uint32(sz))
|
||||
_, err = f.WriteAt(tmp[:4], csCmdOff+12)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Uodate the __LINKEDIT segment.
|
||||
segSz := sigOff + sz - int64(linkeditSeg.Offset)
|
||||
mf.ByteOrder.PutUint64(tmp[:8], uint64(segSz))
|
||||
_, err = f.WriteAt(tmp[:8], int64(linkeditOff)+int64(unsafe.Offsetof(macho.Segment64{}.Memsz)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = f.WriteAt(tmp[:8], int64(linkeditOff)+int64(unsafe.Offsetof(macho.Segment64{}.Filesz)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cs := make([]byte, sz)
|
||||
Sign(cs, f, "a.out", sigOff, int64(textSeg.Offset), int64(textSeg.Filesz), false)
|
||||
_, err = f.WriteAt(cs, sigOff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = f.Truncate(sigOff + sz)
|
||||
return err
|
||||
}
|
||||
+206
-3
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Binject/debug/macho"
|
||||
@@ -14,10 +15,46 @@ import (
|
||||
|
||||
const MaxUint = ^uint(0)
|
||||
const MaxInt = int(MaxUint >> 1)
|
||||
const LIB_DYLD_PATH = "/usr/lib/system/libdyld.dylib"
|
||||
|
||||
func syscallSharedRegionCheckNp() (uint64, error) {
|
||||
var address uint64
|
||||
r1, _, _ := unix.RawSyscall(unix.SYS_SHARED_REGION_CHECK_NP, uintptr(unsafe.Pointer(&address)), 0, 0)
|
||||
if r1 != 0 {
|
||||
return 0, errors.New("shared region check failed")
|
||||
}
|
||||
return address, nil
|
||||
}
|
||||
|
||||
func isSierra() bool {
|
||||
var utsName unix.Utsname
|
||||
err := unix.Uname(&utsName)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if utsName.Release[0] == '1' && utsName.Release[1] < '6' {
|
||||
return false
|
||||
}
|
||||
if utsName.Release[0] == '9' && utsName.Release[1] == '.' {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// LoadLibraryImpl - loads a single library to memory, without trying to check or load required imports
|
||||
func LoadLibraryImpl(name string, image *[]byte) (*Library, error) {
|
||||
|
||||
// On MacOS Sierra and up, NSLinkModule is just a wrapper around
|
||||
// dlopen, pwrite and unlink. There's no "in-memory" only method using
|
||||
// dyld4 APIs, so there's no point in using NSCreateObjectFileImageFromMemory and NSLinkModule anymore.
|
||||
// We can just write our own version that:
|
||||
// - writes the binary image to a temporary file
|
||||
// - calls dlopen on the temporary file
|
||||
// - unlinks the temporary file
|
||||
// - uses dlsym to resolve all the symbols
|
||||
// See https://github.com/apple-oss-distributions/dyld/blob/main/dyld/DyldAPIs.cpp#L2785-L2847 for more details.
|
||||
if isSierra() {
|
||||
return LoadLibraryDlopen(name, image)
|
||||
}
|
||||
// force input image to be a bundle
|
||||
// this is not the most efficient method, but it does create the abililty to do more fixups down the road,
|
||||
// like if we added universal file support
|
||||
@@ -120,6 +157,160 @@ func LoadLibraryImpl(name string, image *[]byte) (*Library, error) {
|
||||
return &lib, nil
|
||||
}
|
||||
|
||||
// LoadLibraryDlopen - loads a single library using dlopen and resolves
|
||||
// all exported symbols using dlsym. This function writes to a temporary
|
||||
// file and then loads the file into memory, and unlinks it.
|
||||
// This is similar to what NSLinkModule does nowadays.
|
||||
func LoadLibraryDlopen(name string, image *[]byte) (*Library, error) {
|
||||
var (
|
||||
dlopenAddr uint64
|
||||
dlSymAddr uint64
|
||||
dyldAddress uint64
|
||||
)
|
||||
|
||||
machoFile, err := macho.NewFile(bytes.NewReader(*image))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We use the shared library cache to find the libdyld.dylib wrapper loaded into our process.
|
||||
// Once we have the address of the libdyld.dylib wrapper, we can parse it in memory using debug/macho (thanks Awgh)
|
||||
// and find the exports we're interested in: dlopen and dlsym.
|
||||
// This part of the code is a port from the awesome work of usiegl00: https://github.com/usiegl00/metasploit-framework/blob/osx-stage-monterey/external/source/shellcode/osx/stager/main.c#L159-L183
|
||||
sharedRegionStart, err := syscallSharedRegionCheckNp()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dyldCacheheader := (*DyldCacheHeader)(unsafe.Pointer(uintptr(sharedRegionStart)))
|
||||
|
||||
imagesCount := dyldCacheheader.ImagesCountOld
|
||||
if imagesCount == 0 {
|
||||
imagesCount = dyldCacheheader.ImagesCount
|
||||
}
|
||||
imagesOffset := dyldCacheheader.ImagesOffsetOld
|
||||
if imagesOffset == 0 {
|
||||
imagesOffset = dyldCacheheader.ImagesOffset
|
||||
}
|
||||
sharedFileMapping := (*SharedFileMapping)(unsafe.Pointer(uintptr(sharedRegionStart + uint64(dyldCacheheader.MappingOffset))))
|
||||
|
||||
dyldCacheImageInfo := (*DyldCacheImageInfo)(unsafe.Pointer(uintptr(sharedRegionStart + uint64(imagesOffset))))
|
||||
for i := uint32(0); i < imagesCount; i++ {
|
||||
pathFileOffset := sharedRegionStart + uint64(dyldCacheImageInfo.PathFileOffset)
|
||||
dylibPath := readCString(pathFileOffset)
|
||||
if dylibPath == LIB_DYLD_PATH {
|
||||
dyldAddress = dyldCacheImageInfo.Address
|
||||
break
|
||||
}
|
||||
dyldCacheImageInfo = (*DyldCacheImageInfo)(unsafe.Pointer(uintptr(sharedRegionStart+uint64(imagesOffset)) + uintptr(i)*unsafe.Sizeof(DyldCacheImageInfo{})))
|
||||
}
|
||||
|
||||
offset := sharedRegionStart - sharedFileMapping.Address
|
||||
dyldAddress += offset
|
||||
|
||||
rawr := rawreader.New(uintptr(dyldAddress), MaxInt)
|
||||
dyldFile, err := macho.NewFileFromMemory(rawr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exports := dyldFile.Exports()
|
||||
|
||||
for _, export := range exports {
|
||||
if export.Name == "_dlopen" {
|
||||
dlopenAddr = export.VirtualAddress + offset
|
||||
}
|
||||
if export.Name == "_dlsym" {
|
||||
dlSymAddr = export.VirtualAddress + offset
|
||||
}
|
||||
}
|
||||
dlopenProc := uintptr(dlopenAddr)
|
||||
dlSymProc := uintptr(dlSymAddr)
|
||||
|
||||
options := 0x80000080 // RTLD_UNLOADABLE | RTLD_NODELETE per https://github.dev/apple-oss-distributions/dyld/blob/419f8cbca6fb3420a248f158714a9d322af2aa5a/dyld/DyldAPIs.cpp#L2818-L2819
|
||||
|
||||
// Create a temp file to write the image to so we can dlopen it.
|
||||
// I wish MacOS had a memfd_create equivalent ...
|
||||
tmpFile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filename := tmpFile.Name()
|
||||
imagePath := append([]byte(filename), 0x00) // C strings are the worst
|
||||
|
||||
_, err = tmpFile.Write(*image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We need to sign the written file otherwise dlopen will fail on MacOS 11 and up on Apple Silicon machines.
|
||||
// We're just using ad-hoc code signing, so we're not going to do anything fancy here.
|
||||
err = machoCodeSign(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pseudoHandle, err := cdecl.Call(dlopenProc,
|
||||
uintptr(unsafe.Pointer(&imagePath[0])), // image name
|
||||
uintptr(options)) // image options flags
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pseudoHandle == 0 {
|
||||
unix.Unlink(filename)
|
||||
return nil, errors.New("dlopen returned 0")
|
||||
}
|
||||
|
||||
// Unlink the temp file, we don't need it anymore
|
||||
err = unix.Unlink(tmpFile.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exports = machoFile.Exports()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lib := Library{
|
||||
BaseAddress: pseudoHandle, // library pseudo handle, do not use directly
|
||||
Exports: make(map[string]uint64),
|
||||
}
|
||||
|
||||
// Since the return value from dlopen is a pseudo handle
|
||||
// and not the address of the dylib in memory, we can't
|
||||
// really use it to resolve symbols directly. Instead, we
|
||||
// use dlsym to resolve all symbols ahead of time, that way
|
||||
// the end user can just use Library.Call.
|
||||
// The downside is that Library.BaseAddress is unusable and
|
||||
// any attempt to use it will result in a crash.
|
||||
for _, x := range exports {
|
||||
exportName := append([]byte(x.Name[1:]), 0x00) // strip the _
|
||||
exportPtr, err := cdecl.Call(dlSymProc,
|
||||
pseudoHandle,
|
||||
uintptr(unsafe.Pointer(&exportName[0])))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lib.Exports[x.Name] = uint64(exportPtr)
|
||||
}
|
||||
return &lib, nil
|
||||
}
|
||||
|
||||
func readCString(pathFileOffset uint64) string {
|
||||
path := ""
|
||||
pathSize := 0
|
||||
for {
|
||||
b := (*byte)(unsafe.Pointer(uintptr(pathFileOffset + uint64(pathSize))))
|
||||
if *b == 0 {
|
||||
break
|
||||
}
|
||||
path += string(*b)
|
||||
pathSize++
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func isPtrValid(p uintptr) bool {
|
||||
r1, _, err := unix.RawSyscall(unix.SYS_CHMOD, p, 0777, 0)
|
||||
if r1 != 0 && err != 2 {
|
||||
@@ -174,12 +365,24 @@ func findMachoPtr(addr uintptr) (uintptr, error) {
|
||||
|
||||
// Call - call a function in a loaded library
|
||||
func (l *Library) Call(functionName string, args ...uintptr) (uintptr, error) {
|
||||
|
||||
var (
|
||||
proc uintptr
|
||||
funcPtr uint64
|
||||
ok bool
|
||||
)
|
||||
if len(functionName) > 0 && functionName[0] != '_' {
|
||||
functionName = "_" + functionName
|
||||
} // OSX has underscore-prefixed exports
|
||||
|
||||
proc, ok := l.FindProc(functionName)
|
||||
if !isSierra() {
|
||||
proc, ok = l.FindProc(functionName)
|
||||
} else {
|
||||
// Can't rely on FindProc for Sierra and up since
|
||||
// the BaseAddress is a pseudo handle to the lib,
|
||||
// not the actual address.
|
||||
funcPtr, ok = l.Exports[functionName]
|
||||
proc = uintptr(funcPtr)
|
||||
}
|
||||
if !ok {
|
||||
return 0, errors.New("Call did not find export " + functionName)
|
||||
}
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package universal
|
||||
|
||||
type DyldCacheHeader struct {
|
||||
Magic [16]byte
|
||||
MappingOffset uint32
|
||||
MappingCount uint32
|
||||
ImagesOffsetOld uint32
|
||||
ImagesCountOld uint32
|
||||
DyldBaseAddress uint64
|
||||
CodeSignatureOffset uint64
|
||||
CodeSignatureSize uint64
|
||||
SlideInfoOffsetUnused uint64
|
||||
SlideInfoSizeUnused uint64
|
||||
LocalSymbolsOffset uint64
|
||||
LocalSymbolsSize uint64
|
||||
UUID [16]byte
|
||||
CacheType uint64
|
||||
BranchPoolOffset uint32
|
||||
BranchPoolCount uint32
|
||||
AccelerateInfoAddr uint64
|
||||
AccelerateInfoSize uint64
|
||||
ImagesTextOffset uint64
|
||||
ImagesTextCount uint64
|
||||
PatchInfoAddr uint64
|
||||
PatchInfoSize uint64
|
||||
OtherImageGroupAddrUnused uint64
|
||||
OtherImageGroupSizeUnused uint64
|
||||
ProgClosuresAddr uint64
|
||||
ProgClosuresSize uint64
|
||||
ProgClosuresTrieAddr uint64
|
||||
ProgClosuresTrieSize uint64
|
||||
Platform uint32
|
||||
FormatVersion uint32
|
||||
SharedRegionStart uint64
|
||||
SharedRegionSize uint64
|
||||
MaxSlide uint64
|
||||
DylibsImageArrayAddr uint64
|
||||
DylibsImageArraySize uint64
|
||||
DylibsTrieAddr uint64
|
||||
DylibsTrieSize uint64
|
||||
OtherImageArrayAddr uint64
|
||||
OtherImageArraySize uint64
|
||||
OtherTrieAddr uint64
|
||||
OtherTrieSize uint64
|
||||
MappingWithSlideOffset uint32
|
||||
MappingWithSlideCount uint32
|
||||
DylibsPBLStateArrayAddrUnused uint64
|
||||
DylibsPBLSetAddr uint64
|
||||
ProgramsPBLSetPoolAddr uint64
|
||||
ProgramsPBLSetPoolSize uint64
|
||||
ProgramTrieAddr uint64
|
||||
ProgramTrieSize uint32
|
||||
OsVersion uint32
|
||||
AltPlatform uint32
|
||||
AltOsVersion uint32
|
||||
SwiftOptsOffset uint64
|
||||
SwiftOptsSize uint64
|
||||
SubCacheArrayOffset uint32
|
||||
SubCacheArrayCount uint32
|
||||
SymbolFileUUID [16]byte
|
||||
RosettaReadOnlyAddr uint64
|
||||
RosettaReadOnlySize uint64
|
||||
RosettaReadWriteAddr uint64
|
||||
RosettaReadWriteSize uint64
|
||||
ImagesOffset uint32
|
||||
ImagesCount uint32
|
||||
}
|
||||
|
||||
type DyldCacheMappingInfo struct {
|
||||
Address uint64
|
||||
Size uint64
|
||||
FileOffset uint64
|
||||
MaxProt uint32
|
||||
InitProt uint32
|
||||
}
|
||||
|
||||
type DyldCacheImageInfo struct {
|
||||
Address uint64
|
||||
ModTime uint64
|
||||
Inode uint64
|
||||
PathFileOffset uint32
|
||||
Pad uint32
|
||||
}
|
||||
|
||||
type SharedFileMapping struct {
|
||||
Address uint64
|
||||
Size uint64
|
||||
FileOffset uint64
|
||||
MaxProt uint32
|
||||
InitProt uint32
|
||||
}
|
||||
+88
-24
@@ -1,8 +1,10 @@
|
||||
package assert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CompareType int
|
||||
@@ -30,6 +32,9 @@ var (
|
||||
float64Type = reflect.TypeOf(float64(1))
|
||||
|
||||
stringType = reflect.TypeOf("")
|
||||
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
bytesType = reflect.TypeOf([]byte{})
|
||||
)
|
||||
|
||||
func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
|
||||
@@ -299,6 +304,47 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
// Check for known struct types we can check for compare results.
|
||||
case reflect.Struct:
|
||||
{
|
||||
// All structs enter here. We're not interested in most types.
|
||||
if !canConvert(obj1Value, timeType) {
|
||||
break
|
||||
}
|
||||
|
||||
// time.Time can compared!
|
||||
timeObj1, ok := obj1.(time.Time)
|
||||
if !ok {
|
||||
timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time)
|
||||
}
|
||||
|
||||
timeObj2, ok := obj2.(time.Time)
|
||||
if !ok {
|
||||
timeObj2 = obj2Value.Convert(timeType).Interface().(time.Time)
|
||||
}
|
||||
|
||||
return compare(timeObj1.UnixNano(), timeObj2.UnixNano(), reflect.Int64)
|
||||
}
|
||||
case reflect.Slice:
|
||||
{
|
||||
// We only care about the []byte type.
|
||||
if !canConvert(obj1Value, bytesType) {
|
||||
break
|
||||
}
|
||||
|
||||
// []byte can be compared!
|
||||
bytesObj1, ok := obj1.([]byte)
|
||||
if !ok {
|
||||
bytesObj1 = obj1Value.Convert(bytesType).Interface().([]byte)
|
||||
|
||||
}
|
||||
bytesObj2, ok := obj2.([]byte)
|
||||
if !ok {
|
||||
bytesObj2 = obj2Value.Convert(bytesType).Interface().([]byte)
|
||||
}
|
||||
|
||||
return CompareType(bytes.Compare(bytesObj1, bytesObj2)), true
|
||||
}
|
||||
}
|
||||
|
||||
return compareEqual, false
|
||||
@@ -306,58 +352,76 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
|
||||
|
||||
// Greater asserts that the first element is greater than the second
|
||||
//
|
||||
// assert.Greater(t, 2, 1)
|
||||
// assert.Greater(t, float64(2), float64(1))
|
||||
// assert.Greater(t, "b", "a")
|
||||
// assert.Greater(t, 2, 1)
|
||||
// assert.Greater(t, float64(2), float64(1))
|
||||
// assert.Greater(t, "b", "a")
|
||||
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs)
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// GreaterOrEqual asserts that the first element is greater than or equal to the second
|
||||
//
|
||||
// assert.GreaterOrEqual(t, 2, 1)
|
||||
// assert.GreaterOrEqual(t, 2, 2)
|
||||
// assert.GreaterOrEqual(t, "b", "a")
|
||||
// assert.GreaterOrEqual(t, "b", "b")
|
||||
// assert.GreaterOrEqual(t, 2, 1)
|
||||
// assert.GreaterOrEqual(t, 2, 2)
|
||||
// assert.GreaterOrEqual(t, "b", "a")
|
||||
// assert.GreaterOrEqual(t, "b", "b")
|
||||
func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs)
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// Less asserts that the first element is less than the second
|
||||
//
|
||||
// assert.Less(t, 1, 2)
|
||||
// assert.Less(t, float64(1), float64(2))
|
||||
// assert.Less(t, "a", "b")
|
||||
// assert.Less(t, 1, 2)
|
||||
// assert.Less(t, float64(1), float64(2))
|
||||
// assert.Less(t, "a", "b")
|
||||
func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs)
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// LessOrEqual asserts that the first element is less than or equal to the second
|
||||
//
|
||||
// assert.LessOrEqual(t, 1, 2)
|
||||
// assert.LessOrEqual(t, 2, 2)
|
||||
// assert.LessOrEqual(t, "a", "b")
|
||||
// assert.LessOrEqual(t, "b", "b")
|
||||
// assert.LessOrEqual(t, 1, 2)
|
||||
// assert.LessOrEqual(t, 2, 2)
|
||||
// assert.LessOrEqual(t, "a", "b")
|
||||
// assert.LessOrEqual(t, "b", "b")
|
||||
func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs)
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// Positive asserts that the specified element is positive
|
||||
//
|
||||
// assert.Positive(t, 1)
|
||||
// assert.Positive(t, 1.23)
|
||||
// assert.Positive(t, 1)
|
||||
// assert.Positive(t, 1.23)
|
||||
func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
zero := reflect.Zero(reflect.TypeOf(e))
|
||||
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs)
|
||||
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs...)
|
||||
}
|
||||
|
||||
// Negative asserts that the specified element is negative
|
||||
//
|
||||
// assert.Negative(t, -1)
|
||||
// assert.Negative(t, -1.23)
|
||||
// assert.Negative(t, -1)
|
||||
// assert.Negative(t, -1.23)
|
||||
func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
zero := reflect.Zero(reflect.TypeOf(e))
|
||||
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs)
|
||||
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs...)
|
||||
}
|
||||
|
||||
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
|
||||
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
//go:build go1.17
|
||||
// +build go1.17
|
||||
|
||||
// TODO: once support for Go 1.16 is dropped, this file can be
|
||||
// merged/removed with assertion_compare_go1.17_test.go and
|
||||
// assertion_compare_legacy.go
|
||||
|
||||
package assert
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Wrapper around reflect.Value.CanConvert, for compatibility
|
||||
// reasons.
|
||||
func canConvert(value reflect.Value, to reflect.Type) bool {
|
||||
return value.CanConvert(to)
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
//go:build !go1.17
|
||||
// +build !go1.17
|
||||
|
||||
// TODO: once support for Go 1.16 is dropped, this file can be
|
||||
// merged/removed with assertion_compare_go1.17_test.go and
|
||||
// assertion_compare_can_convert.go
|
||||
|
||||
package assert
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Older versions of Go does not have the reflect.Value.CanConvert
|
||||
// method.
|
||||
func canConvert(value reflect.Value, to reflect.Type) bool {
|
||||
return false
|
||||
}
|
||||
+148
-84
@@ -22,9 +22,9 @@ func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bo
|
||||
// Containsf asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
|
||||
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
|
||||
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
|
||||
// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
|
||||
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
|
||||
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
|
||||
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -56,7 +56,7 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string
|
||||
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// assert.Emptyf(t, obj, "error message %s", "formatted")
|
||||
// assert.Emptyf(t, obj, "error message %s", "formatted")
|
||||
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -66,7 +66,7 @@ func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) boo
|
||||
|
||||
// Equalf asserts that two objects are equal.
|
||||
//
|
||||
// assert.Equalf(t, 123, 123, "error message %s", "formatted")
|
||||
// assert.Equalf(t, 123, 123, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
@@ -81,8 +81,8 @@ func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, ar
|
||||
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that it is equal to the provided error.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
|
||||
// actualObj, err := SomeFunction()
|
||||
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
|
||||
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -90,10 +90,27 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args
|
||||
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// EqualExportedValuesf asserts that the types of two objects are equal and their public
|
||||
// fields are also equal. This is useful for comparing structs that have private fields
|
||||
// that could potentially differ.
|
||||
//
|
||||
// type S struct {
|
||||
// Exported int
|
||||
// notExported int
|
||||
// }
|
||||
// assert.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true
|
||||
// assert.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false
|
||||
func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return EqualExportedValues(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
|
||||
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
|
||||
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -103,10 +120,10 @@ func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg stri
|
||||
|
||||
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.Errorf(t, err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedErrorf, err)
|
||||
// }
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.Errorf(t, err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedErrorf, err)
|
||||
// }
|
||||
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -123,6 +140,18 @@ func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...int
|
||||
return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// ErrorContainsf asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that the error contains the specified substring.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted")
|
||||
func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return ErrorContains(t, theError, contains, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
|
||||
// This is a wrapper for errors.Is.
|
||||
func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
|
||||
@@ -135,7 +164,7 @@ func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface
|
||||
// Eventuallyf asserts that given condition will be met in waitFor time,
|
||||
// periodically checking target function each tick.
|
||||
//
|
||||
// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
|
||||
// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
|
||||
func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -143,9 +172,34 @@ func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick
|
||||
return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// EventuallyWithTf asserts that given condition will be met in waitFor time,
|
||||
// periodically checking target function each tick. In contrast to Eventually,
|
||||
// it supplies a CollectT to the condition function, so that the condition
|
||||
// function can use the CollectT to call other assertions.
|
||||
// The condition is considered "met" if no errors are raised in a tick.
|
||||
// The supplied CollectT collects all errors from one tick (if there are any).
|
||||
// If the condition is not met before waitFor, the collected errors of
|
||||
// the last tick are copied to t.
|
||||
//
|
||||
// externalValue := false
|
||||
// go func() {
|
||||
// time.Sleep(8*time.Second)
|
||||
// externalValue = true
|
||||
// }()
|
||||
// assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") {
|
||||
// // add assertions as needed; any assertion failure will fail the current tick
|
||||
// assert.True(c, externalValue, "expected 'externalValue' to be true")
|
||||
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
|
||||
func EventuallyWithTf(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return EventuallyWithT(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Exactlyf asserts that two objects are equal in value and type.
|
||||
//
|
||||
// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted")
|
||||
// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted")
|
||||
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -171,7 +225,7 @@ func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}
|
||||
|
||||
// Falsef asserts that the specified value is false.
|
||||
//
|
||||
// assert.Falsef(t, myBool, "error message %s", "formatted")
|
||||
// assert.Falsef(t, myBool, "error message %s", "formatted")
|
||||
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -190,9 +244,9 @@ func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool
|
||||
|
||||
// Greaterf asserts that the first element is greater than the second
|
||||
//
|
||||
// assert.Greaterf(t, 2, 1, "error message %s", "formatted")
|
||||
// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted")
|
||||
// assert.Greaterf(t, "b", "a", "error message %s", "formatted")
|
||||
// assert.Greaterf(t, 2, 1, "error message %s", "formatted")
|
||||
// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted")
|
||||
// assert.Greaterf(t, "b", "a", "error message %s", "formatted")
|
||||
func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -202,10 +256,10 @@ func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...in
|
||||
|
||||
// GreaterOrEqualf asserts that the first element is greater than or equal to the second
|
||||
//
|
||||
// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted")
|
||||
// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||
// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted")
|
||||
// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||
// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted")
|
||||
// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||
// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted")
|
||||
// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||
func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -216,7 +270,7 @@ func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, arg
|
||||
// HTTPBodyContainsf asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
@@ -229,7 +283,7 @@ func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url
|
||||
// HTTPBodyNotContainsf asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
@@ -241,7 +295,7 @@ func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, u
|
||||
|
||||
// HTTPErrorf asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
@@ -253,7 +307,7 @@ func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string,
|
||||
|
||||
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
@@ -265,7 +319,7 @@ func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url stri
|
||||
|
||||
// HTTPStatusCodef asserts that a specified handler returns a specified status code.
|
||||
//
|
||||
// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
|
||||
// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) bool {
|
||||
@@ -277,7 +331,7 @@ func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url st
|
||||
|
||||
// HTTPSuccessf asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
||||
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
@@ -289,7 +343,7 @@ func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url strin
|
||||
|
||||
// Implementsf asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
|
||||
// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
|
||||
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -299,7 +353,7 @@ func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, ms
|
||||
|
||||
// InDeltaf asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted")
|
||||
// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted")
|
||||
func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -341,9 +395,9 @@ func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsil
|
||||
|
||||
// IsDecreasingf asserts that the collection is decreasing
|
||||
//
|
||||
// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted")
|
||||
// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted")
|
||||
// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
|
||||
// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted")
|
||||
// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted")
|
||||
// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
|
||||
func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -353,9 +407,9 @@ func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface
|
||||
|
||||
// IsIncreasingf asserts that the collection is increasing
|
||||
//
|
||||
// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted")
|
||||
// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted")
|
||||
// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
|
||||
// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted")
|
||||
// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted")
|
||||
// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
|
||||
func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -365,9 +419,9 @@ func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface
|
||||
|
||||
// IsNonDecreasingf asserts that the collection is not decreasing
|
||||
//
|
||||
// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted")
|
||||
// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted")
|
||||
// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
|
||||
// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted")
|
||||
// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted")
|
||||
// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
|
||||
func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -377,9 +431,9 @@ func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interf
|
||||
|
||||
// IsNonIncreasingf asserts that the collection is not increasing
|
||||
//
|
||||
// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted")
|
||||
// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted")
|
||||
// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
|
||||
// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted")
|
||||
// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted")
|
||||
// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
|
||||
func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -397,7 +451,7 @@ func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg strin
|
||||
|
||||
// JSONEqf asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
|
||||
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
|
||||
func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -408,7 +462,7 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int
|
||||
// Lenf asserts that the specified object has specific length.
|
||||
// Lenf also fails if the object has a type that len() not accept.
|
||||
//
|
||||
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
|
||||
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
|
||||
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -418,9 +472,9 @@ func Lenf(t TestingT, object interface{}, length int, msg string, args ...interf
|
||||
|
||||
// Lessf asserts that the first element is less than the second
|
||||
//
|
||||
// assert.Lessf(t, 1, 2, "error message %s", "formatted")
|
||||
// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted")
|
||||
// assert.Lessf(t, "a", "b", "error message %s", "formatted")
|
||||
// assert.Lessf(t, 1, 2, "error message %s", "formatted")
|
||||
// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted")
|
||||
// assert.Lessf(t, "a", "b", "error message %s", "formatted")
|
||||
func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -430,10 +484,10 @@ func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...inter
|
||||
|
||||
// LessOrEqualf asserts that the first element is less than or equal to the second
|
||||
//
|
||||
// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted")
|
||||
// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||
// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted")
|
||||
// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||
// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted")
|
||||
// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||
// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted")
|
||||
// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||
func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -443,8 +497,8 @@ func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args .
|
||||
|
||||
// Negativef asserts that the specified element is negative
|
||||
//
|
||||
// assert.Negativef(t, -1, "error message %s", "formatted")
|
||||
// assert.Negativef(t, -1.23, "error message %s", "formatted")
|
||||
// assert.Negativef(t, -1, "error message %s", "formatted")
|
||||
// assert.Negativef(t, -1.23, "error message %s", "formatted")
|
||||
func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -455,7 +509,7 @@ func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool
|
||||
// Neverf asserts that the given condition doesn't satisfy in waitFor time,
|
||||
// periodically checking the target function each tick.
|
||||
//
|
||||
// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
|
||||
// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
|
||||
func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -465,7 +519,7 @@ func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.
|
||||
|
||||
// Nilf asserts that the specified object is nil.
|
||||
//
|
||||
// assert.Nilf(t, err, "error message %s", "formatted")
|
||||
// assert.Nilf(t, err, "error message %s", "formatted")
|
||||
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -484,10 +538,10 @@ func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) bool
|
||||
|
||||
// NoErrorf asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.NoErrorf(t, err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.NoErrorf(t, err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -507,9 +561,9 @@ func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) boo
|
||||
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
|
||||
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
|
||||
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
|
||||
// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
|
||||
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
|
||||
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
|
||||
func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -520,9 +574,9 @@ func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, a
|
||||
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -532,7 +586,7 @@ func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{})
|
||||
|
||||
// NotEqualf asserts that the specified values are NOT equal.
|
||||
//
|
||||
// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
|
||||
// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
@@ -545,7 +599,7 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string,
|
||||
|
||||
// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
|
||||
//
|
||||
// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted")
|
||||
// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted")
|
||||
func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -564,7 +618,7 @@ func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interf
|
||||
|
||||
// NotNilf asserts that the specified object is not nil.
|
||||
//
|
||||
// assert.NotNilf(t, err, "error message %s", "formatted")
|
||||
// assert.NotNilf(t, err, "error message %s", "formatted")
|
||||
func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -574,7 +628,7 @@ func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bo
|
||||
|
||||
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
|
||||
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
|
||||
func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -584,8 +638,8 @@ func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bo
|
||||
|
||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
|
||||
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
|
||||
// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
|
||||
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
|
||||
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -595,7 +649,7 @@ func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ..
|
||||
|
||||
// NotSamef asserts that two pointers do not reference the same object.
|
||||
//
|
||||
// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted")
|
||||
// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted")
|
||||
//
|
||||
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||
// determined based on the equality of both type and value.
|
||||
@@ -609,7 +663,7 @@ func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string,
|
||||
// NotSubsetf asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
|
||||
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
|
||||
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -627,7 +681,7 @@ func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||
|
||||
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -639,7 +693,7 @@ func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool
|
||||
// panics, and that the recovered panic value is an error that satisfies the
|
||||
// EqualError comparison.
|
||||
//
|
||||
// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func PanicsWithErrorf(t TestingT, errString string, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -650,7 +704,7 @@ func PanicsWithErrorf(t TestingT, errString string, f PanicTestFunc, msg string,
|
||||
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -660,8 +714,8 @@ func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg str
|
||||
|
||||
// Positivef asserts that the specified element is positive
|
||||
//
|
||||
// assert.Positivef(t, 1, "error message %s", "formatted")
|
||||
// assert.Positivef(t, 1.23, "error message %s", "formatted")
|
||||
// assert.Positivef(t, 1, "error message %s", "formatted")
|
||||
// assert.Positivef(t, 1.23, "error message %s", "formatted")
|
||||
func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -671,8 +725,8 @@ func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool
|
||||
|
||||
// Regexpf asserts that a specified regexp matches a string.
|
||||
//
|
||||
// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
|
||||
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
|
||||
// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
|
||||
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
|
||||
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -682,7 +736,7 @@ func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...in
|
||||
|
||||
// Samef asserts that two pointers reference the same object.
|
||||
//
|
||||
// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted")
|
||||
// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted")
|
||||
//
|
||||
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||
// determined based on the equality of both type and value.
|
||||
@@ -696,7 +750,7 @@ func Samef(t TestingT, expected interface{}, actual interface{}, msg string, arg
|
||||
// Subsetf asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
|
||||
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
|
||||
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -706,7 +760,7 @@ func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args
|
||||
|
||||
// Truef asserts that the specified value is true.
|
||||
//
|
||||
// assert.Truef(t, myBool, "error message %s", "formatted")
|
||||
// assert.Truef(t, myBool, "error message %s", "formatted")
|
||||
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -716,7 +770,7 @@ func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||
|
||||
// WithinDurationf asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
|
||||
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
|
||||
func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -724,6 +778,16 @@ func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta tim
|
||||
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// WithinRangef asserts that a time is within a time range (inclusive).
|
||||
//
|
||||
// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted")
|
||||
func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return WithinRange(t, actual, start, end, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// YAMLEqf asserts that two YAML strings are equivalent.
|
||||
func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
|
||||
+296
-168
File diff suppressed because it is too large.
Load diff
+16
-16
@@ -46,36 +46,36 @@ func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareT
|
||||
|
||||
// IsIncreasing asserts that the collection is increasing
|
||||
//
|
||||
// assert.IsIncreasing(t, []int{1, 2, 3})
|
||||
// assert.IsIncreasing(t, []float{1, 2})
|
||||
// assert.IsIncreasing(t, []string{"a", "b"})
|
||||
// assert.IsIncreasing(t, []int{1, 2, 3})
|
||||
// assert.IsIncreasing(t, []float{1, 2})
|
||||
// assert.IsIncreasing(t, []string{"a", "b"})
|
||||
func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs)
|
||||
return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// IsNonIncreasing asserts that the collection is not increasing
|
||||
//
|
||||
// assert.IsNonIncreasing(t, []int{2, 1, 1})
|
||||
// assert.IsNonIncreasing(t, []float{2, 1})
|
||||
// assert.IsNonIncreasing(t, []string{"b", "a"})
|
||||
// assert.IsNonIncreasing(t, []int{2, 1, 1})
|
||||
// assert.IsNonIncreasing(t, []float{2, 1})
|
||||
// assert.IsNonIncreasing(t, []string{"b", "a"})
|
||||
func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs)
|
||||
return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// IsDecreasing asserts that the collection is decreasing
|
||||
//
|
||||
// assert.IsDecreasing(t, []int{2, 1, 0})
|
||||
// assert.IsDecreasing(t, []float{2, 1})
|
||||
// assert.IsDecreasing(t, []string{"b", "a"})
|
||||
// assert.IsDecreasing(t, []int{2, 1, 0})
|
||||
// assert.IsDecreasing(t, []float{2, 1})
|
||||
// assert.IsDecreasing(t, []string{"b", "a"})
|
||||
func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs)
|
||||
return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// IsNonDecreasing asserts that the collection is not decreasing
|
||||
//
|
||||
// assert.IsNonDecreasing(t, []int{1, 1, 2})
|
||||
// assert.IsNonDecreasing(t, []float{1, 2})
|
||||
// assert.IsNonDecreasing(t, []string{"a", "b"})
|
||||
// assert.IsNonDecreasing(t, []int{1, 1, 2})
|
||||
// assert.IsNonDecreasing(t, []float{1, 2})
|
||||
// assert.IsNonDecreasing(t, []string{"a", "b"})
|
||||
func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs)
|
||||
return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
|
||||
}
|
||||
+403
-123
@@ -75,6 +75,77 @@ func ObjectsAreEqual(expected, actual interface{}) bool {
|
||||
return bytes.Equal(exp, act)
|
||||
}
|
||||
|
||||
// copyExportedFields iterates downward through nested data structures and creates a copy
|
||||
// that only contains the exported struct fields.
|
||||
func copyExportedFields(expected interface{}) interface{} {
|
||||
if isNil(expected) {
|
||||
return expected
|
||||
}
|
||||
|
||||
expectedType := reflect.TypeOf(expected)
|
||||
expectedKind := expectedType.Kind()
|
||||
expectedValue := reflect.ValueOf(expected)
|
||||
|
||||
switch expectedKind {
|
||||
case reflect.Struct:
|
||||
result := reflect.New(expectedType).Elem()
|
||||
for i := 0; i < expectedType.NumField(); i++ {
|
||||
field := expectedType.Field(i)
|
||||
isExported := field.IsExported()
|
||||
if isExported {
|
||||
fieldValue := expectedValue.Field(i)
|
||||
if isNil(fieldValue) || isNil(fieldValue.Interface()) {
|
||||
continue
|
||||
}
|
||||
newValue := copyExportedFields(fieldValue.Interface())
|
||||
result.Field(i).Set(reflect.ValueOf(newValue))
|
||||
}
|
||||
}
|
||||
return result.Interface()
|
||||
|
||||
case reflect.Ptr:
|
||||
result := reflect.New(expectedType.Elem())
|
||||
unexportedRemoved := copyExportedFields(expectedValue.Elem().Interface())
|
||||
result.Elem().Set(reflect.ValueOf(unexportedRemoved))
|
||||
return result.Interface()
|
||||
|
||||
case reflect.Array, reflect.Slice:
|
||||
result := reflect.MakeSlice(expectedType, expectedValue.Len(), expectedValue.Len())
|
||||
for i := 0; i < expectedValue.Len(); i++ {
|
||||
index := expectedValue.Index(i)
|
||||
if isNil(index) {
|
||||
continue
|
||||
}
|
||||
unexportedRemoved := copyExportedFields(index.Interface())
|
||||
result.Index(i).Set(reflect.ValueOf(unexportedRemoved))
|
||||
}
|
||||
return result.Interface()
|
||||
|
||||
case reflect.Map:
|
||||
result := reflect.MakeMap(expectedType)
|
||||
for _, k := range expectedValue.MapKeys() {
|
||||
index := expectedValue.MapIndex(k)
|
||||
unexportedRemoved := copyExportedFields(index.Interface())
|
||||
result.SetMapIndex(k, reflect.ValueOf(unexportedRemoved))
|
||||
}
|
||||
return result.Interface()
|
||||
|
||||
default:
|
||||
return expected
|
||||
}
|
||||
}
|
||||
|
||||
// ObjectsExportedFieldsAreEqual determines if the exported (public) fields of two objects are
|
||||
// considered equal. This comparison of only exported fields is applied recursively to nested data
|
||||
// structures.
|
||||
//
|
||||
// This function does no assertion of any kind.
|
||||
func ObjectsExportedFieldsAreEqual(expected, actual interface{}) bool {
|
||||
expectedCleaned := copyExportedFields(expected)
|
||||
actualCleaned := copyExportedFields(actual)
|
||||
return ObjectsAreEqualValues(expectedCleaned, actualCleaned)
|
||||
}
|
||||
|
||||
// ObjectsAreEqualValues gets whether two objects are equal, or if their
|
||||
// values are equal.
|
||||
func ObjectsAreEqualValues(expected, actual interface{}) bool {
|
||||
@@ -140,10 +211,10 @@ func CallerInfo() []string {
|
||||
}
|
||||
|
||||
parts := strings.Split(file, "/")
|
||||
file = parts[len(parts)-1]
|
||||
if len(parts) > 1 {
|
||||
filename := parts[len(parts)-1]
|
||||
dir := parts[len(parts)-2]
|
||||
if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" {
|
||||
if (dir != "assert" && dir != "mock" && dir != "require") || filename == "mock_test.go" {
|
||||
callers = append(callers, fmt.Sprintf("%s:%d", file, line))
|
||||
}
|
||||
}
|
||||
@@ -271,7 +342,7 @@ type labeledContent struct {
|
||||
|
||||
// labeledOutput returns a string consisting of the provided labeledContent. Each labeled output is appended in the following manner:
|
||||
//
|
||||
// \t{{label}}:{{align_spaces}}\t{{content}}\n
|
||||
// \t{{label}}:{{align_spaces}}\t{{content}}\n
|
||||
//
|
||||
// The initial carriage return is required to undo/erase any padding added by testing.T.Errorf. The "\t{{label}}:" is for the label.
|
||||
// If a label is shorter than the longest label provided, padding spaces are added to make all the labels match in length. Once this
|
||||
@@ -294,7 +365,7 @@ func labeledOutput(content ...labeledContent) string {
|
||||
|
||||
// Implements asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// assert.Implements(t, (*MyInterface)(nil), new(MyObject))
|
||||
// assert.Implements(t, (*MyInterface)(nil), new(MyObject))
|
||||
func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -326,7 +397,7 @@ func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs
|
||||
|
||||
// Equal asserts that two objects are equal.
|
||||
//
|
||||
// assert.Equal(t, 123, 123)
|
||||
// assert.Equal(t, 123, 123)
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
@@ -367,7 +438,7 @@ func validateEqualArgs(expected, actual interface{}) error {
|
||||
|
||||
// Same asserts that two pointers reference the same object.
|
||||
//
|
||||
// assert.Same(t, ptr1, ptr2)
|
||||
// assert.Same(t, ptr1, ptr2)
|
||||
//
|
||||
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||
// determined based on the equality of both type and value.
|
||||
@@ -387,7 +458,7 @@ func Same(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) b
|
||||
|
||||
// NotSame asserts that two pointers do not reference the same object.
|
||||
//
|
||||
// assert.NotSame(t, ptr1, ptr2)
|
||||
// assert.NotSame(t, ptr1, ptr2)
|
||||
//
|
||||
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||
// determined based on the equality of both type and value.
|
||||
@@ -455,7 +526,7 @@ func truncatingFormat(data interface{}) string {
|
||||
// EqualValues asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// assert.EqualValues(t, uint32(123), int32(123))
|
||||
// assert.EqualValues(t, uint32(123), int32(123))
|
||||
func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -473,9 +544,53 @@ func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interfa
|
||||
|
||||
}
|
||||
|
||||
// EqualExportedValues asserts that the types of two objects are equal and their public
|
||||
// fields are also equal. This is useful for comparing structs that have private fields
|
||||
// that could potentially differ.
|
||||
//
|
||||
// type S struct {
|
||||
// Exported int
|
||||
// notExported int
|
||||
// }
|
||||
// assert.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true
|
||||
// assert.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false
|
||||
func EqualExportedValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
aType := reflect.TypeOf(expected)
|
||||
bType := reflect.TypeOf(actual)
|
||||
|
||||
if aType != bType {
|
||||
return Fail(t, fmt.Sprintf("Types expected to match exactly\n\t%v != %v", aType, bType), msgAndArgs...)
|
||||
}
|
||||
|
||||
if aType.Kind() != reflect.Struct {
|
||||
return Fail(t, fmt.Sprintf("Types expected to both be struct \n\t%v != %v", aType.Kind(), reflect.Struct), msgAndArgs...)
|
||||
}
|
||||
|
||||
if bType.Kind() != reflect.Struct {
|
||||
return Fail(t, fmt.Sprintf("Types expected to both be struct \n\t%v != %v", bType.Kind(), reflect.Struct), msgAndArgs...)
|
||||
}
|
||||
|
||||
expected = copyExportedFields(expected)
|
||||
actual = copyExportedFields(actual)
|
||||
|
||||
if !ObjectsAreEqualValues(expected, actual) {
|
||||
diff := diff(expected, actual)
|
||||
expected, actual = formatUnequalValues(expected, actual)
|
||||
return Fail(t, fmt.Sprintf("Not equal (comparing only exported fields): \n"+
|
||||
"expected: %s\n"+
|
||||
"actual : %s%s", expected, actual, diff), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Exactly asserts that two objects are equal in value and type.
|
||||
//
|
||||
// assert.Exactly(t, int32(123), int64(123))
|
||||
// assert.Exactly(t, int32(123), int64(123))
|
||||
func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -494,7 +609,7 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}
|
||||
|
||||
// NotNil asserts that the specified object is not nil.
|
||||
//
|
||||
// assert.NotNil(t, err)
|
||||
// assert.NotNil(t, err)
|
||||
func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
if !isNil(object) {
|
||||
return true
|
||||
@@ -528,7 +643,7 @@ func isNil(object interface{}) bool {
|
||||
[]reflect.Kind{
|
||||
reflect.Chan, reflect.Func,
|
||||
reflect.Interface, reflect.Map,
|
||||
reflect.Ptr, reflect.Slice},
|
||||
reflect.Ptr, reflect.Slice, reflect.UnsafePointer},
|
||||
kind)
|
||||
|
||||
if isNilableKind && value.IsNil() {
|
||||
@@ -540,7 +655,7 @@ func isNil(object interface{}) bool {
|
||||
|
||||
// Nil asserts that the specified object is nil.
|
||||
//
|
||||
// assert.Nil(t, err)
|
||||
// assert.Nil(t, err)
|
||||
func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
if isNil(object) {
|
||||
return true
|
||||
@@ -563,16 +678,17 @@ func isEmpty(object interface{}) bool {
|
||||
|
||||
switch objValue.Kind() {
|
||||
// collection types are empty when they have no element
|
||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
||||
case reflect.Chan, reflect.Map, reflect.Slice:
|
||||
return objValue.Len() == 0
|
||||
// pointers are empty if nil or if the value they point to is empty
|
||||
// pointers are empty if nil or if the value they point to is empty
|
||||
case reflect.Ptr:
|
||||
if objValue.IsNil() {
|
||||
return true
|
||||
}
|
||||
deref := objValue.Elem().Interface()
|
||||
return isEmpty(deref)
|
||||
// for all other types, compare against the zero value
|
||||
// for all other types, compare against the zero value
|
||||
// array types are empty when they match their zero-initialized state
|
||||
default:
|
||||
zero := reflect.Zero(objValue.Type())
|
||||
return reflect.DeepEqual(object, zero.Interface())
|
||||
@@ -582,7 +698,7 @@ func isEmpty(object interface{}) bool {
|
||||
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// assert.Empty(t, obj)
|
||||
// assert.Empty(t, obj)
|
||||
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
pass := isEmpty(object)
|
||||
if !pass {
|
||||
@@ -599,9 +715,9 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// if assert.NotEmpty(t, obj) {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
// if assert.NotEmpty(t, obj) {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
pass := !isEmpty(object)
|
||||
if !pass {
|
||||
@@ -630,7 +746,7 @@ func getLen(x interface{}) (ok bool, length int) {
|
||||
// Len asserts that the specified object has specific length.
|
||||
// Len also fails if the object has a type that len() not accept.
|
||||
//
|
||||
// assert.Len(t, mySlice, 3)
|
||||
// assert.Len(t, mySlice, 3)
|
||||
func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -648,7 +764,7 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{})
|
||||
|
||||
// True asserts that the specified value is true.
|
||||
//
|
||||
// assert.True(t, myBool)
|
||||
// assert.True(t, myBool)
|
||||
func True(t TestingT, value bool, msgAndArgs ...interface{}) bool {
|
||||
if !value {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
@@ -663,7 +779,7 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool {
|
||||
|
||||
// False asserts that the specified value is false.
|
||||
//
|
||||
// assert.False(t, myBool)
|
||||
// assert.False(t, myBool)
|
||||
func False(t TestingT, value bool, msgAndArgs ...interface{}) bool {
|
||||
if value {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
@@ -678,7 +794,7 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) bool {
|
||||
|
||||
// NotEqual asserts that the specified values are NOT equal.
|
||||
//
|
||||
// assert.NotEqual(t, obj1, obj2)
|
||||
// assert.NotEqual(t, obj1, obj2)
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
@@ -701,7 +817,7 @@ func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{
|
||||
|
||||
// NotEqualValues asserts that two objects are not equal even when converted to the same type
|
||||
//
|
||||
// assert.NotEqualValues(t, obj1, obj2)
|
||||
// assert.NotEqualValues(t, obj1, obj2)
|
||||
func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -718,10 +834,14 @@ func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...inte
|
||||
// return (false, false) if impossible.
|
||||
// return (true, false) if element was not found.
|
||||
// return (true, true) if element was found.
|
||||
func includeElement(list interface{}, element interface{}) (ok, found bool) {
|
||||
func containsElement(list interface{}, element interface{}) (ok, found bool) {
|
||||
|
||||
listValue := reflect.ValueOf(list)
|
||||
listKind := reflect.TypeOf(list).Kind()
|
||||
listType := reflect.TypeOf(list)
|
||||
if listType == nil {
|
||||
return false, false
|
||||
}
|
||||
listKind := listType.Kind()
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
ok = false
|
||||
@@ -756,15 +876,15 @@ func includeElement(list interface{}, element interface{}) (ok, found bool) {
|
||||
// Contains asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// assert.Contains(t, "Hello World", "World")
|
||||
// assert.Contains(t, ["Hello", "World"], "World")
|
||||
// assert.Contains(t, {"Hello": "World"}, "Hello")
|
||||
// assert.Contains(t, "Hello World", "World")
|
||||
// assert.Contains(t, ["Hello", "World"], "World")
|
||||
// assert.Contains(t, {"Hello": "World"}, "Hello")
|
||||
func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
ok, found := includeElement(s, contains)
|
||||
ok, found := containsElement(s, contains)
|
||||
if !ok {
|
||||
return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", s), msgAndArgs...)
|
||||
}
|
||||
@@ -779,20 +899,20 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo
|
||||
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// assert.NotContains(t, "Hello World", "Earth")
|
||||
// assert.NotContains(t, ["Hello", "World"], "Earth")
|
||||
// assert.NotContains(t, {"Hello": "World"}, "Earth")
|
||||
// assert.NotContains(t, "Hello World", "Earth")
|
||||
// assert.NotContains(t, ["Hello", "World"], "Earth")
|
||||
// assert.NotContains(t, {"Hello": "World"}, "Earth")
|
||||
func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
ok, found := includeElement(s, contains)
|
||||
ok, found := containsElement(s, contains)
|
||||
if !ok {
|
||||
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...)
|
||||
return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", s), msgAndArgs...)
|
||||
}
|
||||
if found {
|
||||
return Fail(t, fmt.Sprintf("\"%s\" should not contain \"%s\"", s, contains), msgAndArgs...)
|
||||
return Fail(t, fmt.Sprintf("%#v should not contain %#v", s, contains), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -802,7 +922,7 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{})
|
||||
// Subset asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
|
||||
// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
|
||||
func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -811,32 +931,44 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok
|
||||
return true // we consider nil to be equal to the nil set
|
||||
}
|
||||
|
||||
subsetValue := reflect.ValueOf(subset)
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
ok = false
|
||||
}
|
||||
}()
|
||||
|
||||
listKind := reflect.TypeOf(list).Kind()
|
||||
subsetKind := reflect.TypeOf(subset).Kind()
|
||||
|
||||
if listKind != reflect.Array && listKind != reflect.Slice {
|
||||
if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map {
|
||||
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...)
|
||||
}
|
||||
|
||||
if subsetKind != reflect.Array && subsetKind != reflect.Slice {
|
||||
subsetKind := reflect.TypeOf(subset).Kind()
|
||||
if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map {
|
||||
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
|
||||
}
|
||||
|
||||
for i := 0; i < subsetValue.Len(); i++ {
|
||||
element := subsetValue.Index(i).Interface()
|
||||
ok, found := includeElement(list, element)
|
||||
if subsetKind == reflect.Map && listKind == reflect.Map {
|
||||
subsetMap := reflect.ValueOf(subset)
|
||||
actualMap := reflect.ValueOf(list)
|
||||
|
||||
for _, k := range subsetMap.MapKeys() {
|
||||
ev := subsetMap.MapIndex(k)
|
||||
av := actualMap.MapIndex(k)
|
||||
|
||||
if !av.IsValid() {
|
||||
return Fail(t, fmt.Sprintf("%#v does not contain %#v", list, subset), msgAndArgs...)
|
||||
}
|
||||
if !ObjectsAreEqual(ev.Interface(), av.Interface()) {
|
||||
return Fail(t, fmt.Sprintf("%#v does not contain %#v", list, subset), msgAndArgs...)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
subsetList := reflect.ValueOf(subset)
|
||||
for i := 0; i < subsetList.Len(); i++ {
|
||||
element := subsetList.Index(i).Interface()
|
||||
ok, found := containsElement(list, element)
|
||||
if !ok {
|
||||
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...)
|
||||
return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", list), msgAndArgs...)
|
||||
}
|
||||
if !found {
|
||||
return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", list, element), msgAndArgs...)
|
||||
return Fail(t, fmt.Sprintf("%#v does not contain %#v", list, element), msgAndArgs...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -846,36 +978,48 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok
|
||||
// NotSubset asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
|
||||
// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
|
||||
func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if subset == nil {
|
||||
return Fail(t, fmt.Sprintf("nil is the empty set which is a subset of every set"), msgAndArgs...)
|
||||
return Fail(t, "nil is the empty set which is a subset of every set", msgAndArgs...)
|
||||
}
|
||||
|
||||
subsetValue := reflect.ValueOf(subset)
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
ok = false
|
||||
}
|
||||
}()
|
||||
|
||||
listKind := reflect.TypeOf(list).Kind()
|
||||
subsetKind := reflect.TypeOf(subset).Kind()
|
||||
|
||||
if listKind != reflect.Array && listKind != reflect.Slice {
|
||||
if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map {
|
||||
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...)
|
||||
}
|
||||
|
||||
if subsetKind != reflect.Array && subsetKind != reflect.Slice {
|
||||
subsetKind := reflect.TypeOf(subset).Kind()
|
||||
if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map {
|
||||
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
|
||||
}
|
||||
|
||||
for i := 0; i < subsetValue.Len(); i++ {
|
||||
element := subsetValue.Index(i).Interface()
|
||||
ok, found := includeElement(list, element)
|
||||
if subsetKind == reflect.Map && listKind == reflect.Map {
|
||||
subsetMap := reflect.ValueOf(subset)
|
||||
actualMap := reflect.ValueOf(list)
|
||||
|
||||
for _, k := range subsetMap.MapKeys() {
|
||||
ev := subsetMap.MapIndex(k)
|
||||
av := actualMap.MapIndex(k)
|
||||
|
||||
if !av.IsValid() {
|
||||
return true
|
||||
}
|
||||
if !ObjectsAreEqual(ev.Interface(), av.Interface()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...)
|
||||
}
|
||||
|
||||
subsetList := reflect.ValueOf(subset)
|
||||
for i := 0; i < subsetList.Len(); i++ {
|
||||
element := subsetList.Index(i).Interface()
|
||||
ok, found := containsElement(list, element)
|
||||
if !ok {
|
||||
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...)
|
||||
}
|
||||
@@ -1000,32 +1144,26 @@ func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool {
|
||||
type PanicTestFunc func()
|
||||
|
||||
// didPanic returns true if the function passed to it panics. Otherwise, it returns false.
|
||||
func didPanic(f PanicTestFunc) (bool, interface{}, string) {
|
||||
|
||||
didPanic := false
|
||||
var message interface{}
|
||||
var stack string
|
||||
func() {
|
||||
|
||||
defer func() {
|
||||
if message = recover(); message != nil {
|
||||
didPanic = true
|
||||
stack = string(debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
// call the target function
|
||||
f()
|
||||
func didPanic(f PanicTestFunc) (didPanic bool, message interface{}, stack string) {
|
||||
didPanic = true
|
||||
|
||||
defer func() {
|
||||
message = recover()
|
||||
if didPanic {
|
||||
stack = string(debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
return didPanic, message, stack
|
||||
// call the target function
|
||||
f()
|
||||
didPanic = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Panics asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// assert.Panics(t, func(){ GoCrazy() })
|
||||
// assert.Panics(t, func(){ GoCrazy() })
|
||||
func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -1041,7 +1179,7 @@ func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
||||
// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() })
|
||||
// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() })
|
||||
func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -1062,7 +1200,7 @@ func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndAr
|
||||
// panics, and that the recovered panic value is an error that satisfies the
|
||||
// EqualError comparison.
|
||||
//
|
||||
// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() })
|
||||
// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() })
|
||||
func PanicsWithError(t TestingT, errString string, f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -1082,7 +1220,7 @@ func PanicsWithError(t TestingT, errString string, f PanicTestFunc, msgAndArgs .
|
||||
|
||||
// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// assert.NotPanics(t, func(){ RemainCalm() })
|
||||
// assert.NotPanics(t, func(){ RemainCalm() })
|
||||
func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -1097,7 +1235,7 @@ func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
||||
|
||||
// WithinDuration asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second)
|
||||
// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second)
|
||||
func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -1111,6 +1249,27 @@ func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration,
|
||||
return true
|
||||
}
|
||||
|
||||
// WithinRange asserts that a time is within a time range (inclusive).
|
||||
//
|
||||
// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second))
|
||||
func WithinRange(t TestingT, actual, start, end time.Time, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
if end.Before(start) {
|
||||
return Fail(t, "Start should be before end", msgAndArgs...)
|
||||
}
|
||||
|
||||
if actual.Before(start) {
|
||||
return Fail(t, fmt.Sprintf("Time %v expected to be in time range %v to %v, but is before the range", actual, start, end), msgAndArgs...)
|
||||
} else if actual.After(end) {
|
||||
return Fail(t, fmt.Sprintf("Time %v expected to be in time range %v to %v, but is after the range", actual, start, end), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func toFloat(x interface{}) (float64, bool) {
|
||||
var xf float64
|
||||
xok := true
|
||||
@@ -1151,7 +1310,7 @@ func toFloat(x interface{}) (float64, bool) {
|
||||
|
||||
// InDelta asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// assert.InDelta(t, math.Pi, 22/7.0, 0.01)
|
||||
// assert.InDelta(t, math.Pi, 22/7.0, 0.01)
|
||||
func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -1161,11 +1320,15 @@ func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs
|
||||
bf, bok := toFloat(actual)
|
||||
|
||||
if !aok || !bok {
|
||||
return Fail(t, fmt.Sprintf("Parameters must be numerical"), msgAndArgs...)
|
||||
return Fail(t, "Parameters must be numerical", msgAndArgs...)
|
||||
}
|
||||
|
||||
if math.IsNaN(af) && math.IsNaN(bf) {
|
||||
return true
|
||||
}
|
||||
|
||||
if math.IsNaN(af) {
|
||||
return Fail(t, fmt.Sprintf("Expected must not be NaN"), msgAndArgs...)
|
||||
return Fail(t, "Expected must not be NaN", msgAndArgs...)
|
||||
}
|
||||
|
||||
if math.IsNaN(bf) {
|
||||
@@ -1188,7 +1351,7 @@ func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAn
|
||||
if expected == nil || actual == nil ||
|
||||
reflect.TypeOf(actual).Kind() != reflect.Slice ||
|
||||
reflect.TypeOf(expected).Kind() != reflect.Slice {
|
||||
return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...)
|
||||
return Fail(t, "Parameters must be slice", msgAndArgs...)
|
||||
}
|
||||
|
||||
actualSlice := reflect.ValueOf(actual)
|
||||
@@ -1250,8 +1413,12 @@ func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, m
|
||||
|
||||
func calcRelativeError(expected, actual interface{}) (float64, error) {
|
||||
af, aok := toFloat(expected)
|
||||
if !aok {
|
||||
return 0, fmt.Errorf("expected value %q cannot be converted to float", expected)
|
||||
bf, bok := toFloat(actual)
|
||||
if !aok || !bok {
|
||||
return 0, fmt.Errorf("Parameters must be numerical")
|
||||
}
|
||||
if math.IsNaN(af) && math.IsNaN(bf) {
|
||||
return 0, nil
|
||||
}
|
||||
if math.IsNaN(af) {
|
||||
return 0, errors.New("expected value must not be NaN")
|
||||
@@ -1259,10 +1426,6 @@ func calcRelativeError(expected, actual interface{}) (float64, error) {
|
||||
if af == 0 {
|
||||
return 0, fmt.Errorf("expected value must have a value other than zero to calculate the relative error")
|
||||
}
|
||||
bf, bok := toFloat(actual)
|
||||
if !bok {
|
||||
return 0, fmt.Errorf("actual value %q cannot be converted to float", actual)
|
||||
}
|
||||
if math.IsNaN(bf) {
|
||||
return 0, errors.New("actual value must not be NaN")
|
||||
}
|
||||
@@ -1298,7 +1461,7 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m
|
||||
if expected == nil || actual == nil ||
|
||||
reflect.TypeOf(actual).Kind() != reflect.Slice ||
|
||||
reflect.TypeOf(expected).Kind() != reflect.Slice {
|
||||
return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...)
|
||||
return Fail(t, "Parameters must be slice", msgAndArgs...)
|
||||
}
|
||||
|
||||
actualSlice := reflect.ValueOf(actual)
|
||||
@@ -1320,10 +1483,10 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m
|
||||
|
||||
// NoError asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.NoError(t, err) {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.NoError(t, err) {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool {
|
||||
if err != nil {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
@@ -1337,10 +1500,10 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool {
|
||||
|
||||
// Error asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.Error(t, err) {
|
||||
// assert.Equal(t, expectedError, err)
|
||||
// }
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.Error(t, err) {
|
||||
// assert.Equal(t, expectedError, err)
|
||||
// }
|
||||
func Error(t TestingT, err error, msgAndArgs ...interface{}) bool {
|
||||
if err == nil {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
@@ -1355,8 +1518,8 @@ func Error(t TestingT, err error, msgAndArgs ...interface{}) bool {
|
||||
// EqualError asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that it is equal to the provided error.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// assert.EqualError(t, err, expectedErrorString)
|
||||
// actualObj, err := SomeFunction()
|
||||
// assert.EqualError(t, err, expectedErrorString)
|
||||
func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -1375,6 +1538,27 @@ func EqualError(t TestingT, theError error, errString string, msgAndArgs ...inte
|
||||
return true
|
||||
}
|
||||
|
||||
// ErrorContains asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that the error contains the specified substring.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// assert.ErrorContains(t, err, expectedErrorSubString)
|
||||
func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if !Error(t, theError, msgAndArgs...) {
|
||||
return false
|
||||
}
|
||||
|
||||
actual := theError.Error()
|
||||
if !strings.Contains(actual, contains) {
|
||||
return Fail(t, fmt.Sprintf("Error %#v does not contain %#v", actual, contains), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// matchRegexp return true if a specified regexp matches a string.
|
||||
func matchRegexp(rx interface{}, str interface{}) bool {
|
||||
|
||||
@@ -1391,8 +1575,8 @@ func matchRegexp(rx interface{}, str interface{}) bool {
|
||||
|
||||
// Regexp asserts that a specified regexp matches a string.
|
||||
//
|
||||
// assert.Regexp(t, regexp.MustCompile("start"), "it's starting")
|
||||
// assert.Regexp(t, "start...$", "it's not starting")
|
||||
// assert.Regexp(t, regexp.MustCompile("start"), "it's starting")
|
||||
// assert.Regexp(t, "start...$", "it's not starting")
|
||||
func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -1409,8 +1593,8 @@ func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface
|
||||
|
||||
// NotRegexp asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting")
|
||||
// assert.NotRegexp(t, "^start", "it's not starting")
|
||||
// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting")
|
||||
// assert.NotRegexp(t, "^start", "it's not starting")
|
||||
func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -1522,7 +1706,7 @@ func NoDirExists(t TestingT, path string, msgAndArgs ...interface{}) bool {
|
||||
|
||||
// JSONEq asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
||||
// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
||||
func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -1588,12 +1772,17 @@ func diff(expected interface{}, actual interface{}) string {
|
||||
}
|
||||
|
||||
var e, a string
|
||||
if et != reflect.TypeOf("") {
|
||||
e = spewConfig.Sdump(expected)
|
||||
a = spewConfig.Sdump(actual)
|
||||
} else {
|
||||
|
||||
switch et {
|
||||
case reflect.TypeOf(""):
|
||||
e = reflect.ValueOf(expected).String()
|
||||
a = reflect.ValueOf(actual).String()
|
||||
case reflect.TypeOf(time.Time{}):
|
||||
e = spewConfigStringerEnabled.Sdump(expected)
|
||||
a = spewConfigStringerEnabled.Sdump(actual)
|
||||
default:
|
||||
e = spewConfig.Sdump(expected)
|
||||
a = spewConfig.Sdump(actual)
|
||||
}
|
||||
|
||||
diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
|
||||
@@ -1625,6 +1814,14 @@ var spewConfig = spew.ConfigState{
|
||||
MaxDepth: 10,
|
||||
}
|
||||
|
||||
var spewConfigStringerEnabled = spew.ConfigState{
|
||||
Indent: " ",
|
||||
DisablePointerAddresses: true,
|
||||
DisableCapacities: true,
|
||||
SortKeys: true,
|
||||
MaxDepth: 10,
|
||||
}
|
||||
|
||||
type tHelper interface {
|
||||
Helper()
|
||||
}
|
||||
@@ -1632,7 +1829,7 @@ type tHelper interface {
|
||||
// Eventually asserts that given condition will be met in waitFor time,
|
||||
// periodically checking target function each tick.
|
||||
//
|
||||
// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond)
|
||||
// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond)
|
||||
func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
@@ -1662,10 +1859,93 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t
|
||||
}
|
||||
}
|
||||
|
||||
// CollectT implements the TestingT interface and collects all errors.
|
||||
type CollectT struct {
|
||||
errors []error
|
||||
}
|
||||
|
||||
// Errorf collects the error.
|
||||
func (c *CollectT) Errorf(format string, args ...interface{}) {
|
||||
c.errors = append(c.errors, fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// FailNow panics.
|
||||
func (c *CollectT) FailNow() {
|
||||
panic("Assertion failed")
|
||||
}
|
||||
|
||||
// Reset clears the collected errors.
|
||||
func (c *CollectT) Reset() {
|
||||
c.errors = nil
|
||||
}
|
||||
|
||||
// Copy copies the collected errors to the supplied t.
|
||||
func (c *CollectT) Copy(t TestingT) {
|
||||
if tt, ok := t.(tHelper); ok {
|
||||
tt.Helper()
|
||||
}
|
||||
for _, err := range c.errors {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// EventuallyWithT asserts that given condition will be met in waitFor time,
|
||||
// periodically checking target function each tick. In contrast to Eventually,
|
||||
// it supplies a CollectT to the condition function, so that the condition
|
||||
// function can use the CollectT to call other assertions.
|
||||
// The condition is considered "met" if no errors are raised in a tick.
|
||||
// The supplied CollectT collects all errors from one tick (if there are any).
|
||||
// If the condition is not met before waitFor, the collected errors of
|
||||
// the last tick are copied to t.
|
||||
//
|
||||
// externalValue := false
|
||||
// go func() {
|
||||
// time.Sleep(8*time.Second)
|
||||
// externalValue = true
|
||||
// }()
|
||||
// assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
// // add assertions as needed; any assertion failure will fail the current tick
|
||||
// assert.True(c, externalValue, "expected 'externalValue' to be true")
|
||||
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
|
||||
func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
collect := new(CollectT)
|
||||
ch := make(chan bool, 1)
|
||||
|
||||
timer := time.NewTimer(waitFor)
|
||||
defer timer.Stop()
|
||||
|
||||
ticker := time.NewTicker(tick)
|
||||
defer ticker.Stop()
|
||||
|
||||
for tick := ticker.C; ; {
|
||||
select {
|
||||
case <-timer.C:
|
||||
collect.Copy(t)
|
||||
return Fail(t, "Condition never satisfied", msgAndArgs...)
|
||||
case <-tick:
|
||||
tick = nil
|
||||
collect.Reset()
|
||||
go func() {
|
||||
condition(collect)
|
||||
ch <- len(collect.errors) == 0
|
||||
}()
|
||||
case v := <-ch:
|
||||
if v {
|
||||
return true
|
||||
}
|
||||
tick = ticker.C
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Never asserts that the given condition doesn't satisfy in waitFor time,
|
||||
// periodically checking the target function each tick.
|
||||
//
|
||||
// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond)
|
||||
// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond)
|
||||
func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
||||
+22
-21
@@ -1,39 +1,40 @@
|
||||
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
|
||||
//
|
||||
// Example Usage
|
||||
// # Example Usage
|
||||
//
|
||||
// The following is a complete example using assert in a standard test function:
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
//
|
||||
// func TestSomething(t *testing.T) {
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
//
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
// func TestSomething(t *testing.T) {
|
||||
//
|
||||
// assert.Equal(t, a, b, "The two words should be the same.")
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
//
|
||||
// }
|
||||
// assert.Equal(t, a, b, "The two words should be the same.")
|
||||
//
|
||||
// }
|
||||
//
|
||||
// if you assert many times, use the format below:
|
||||
//
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
//
|
||||
// func TestSomething(t *testing.T) {
|
||||
// assert := assert.New(t)
|
||||
// func TestSomething(t *testing.T) {
|
||||
// assert := assert.New(t)
|
||||
//
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
//
|
||||
// assert.Equal(a, b, "The two words should be the same.")
|
||||
// }
|
||||
// assert.Equal(a, b, "The two words should be the same.")
|
||||
// }
|
||||
//
|
||||
// Assertions
|
||||
// # Assertions
|
||||
//
|
||||
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
|
||||
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
|
||||
|
||||
+6
-6
@@ -23,7 +23,7 @@ func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (
|
||||
|
||||
// HTTPSuccess asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
|
||||
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
@@ -45,7 +45,7 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value
|
||||
|
||||
// HTTPRedirect asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
@@ -67,7 +67,7 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu
|
||||
|
||||
// HTTPError asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
@@ -89,7 +89,7 @@ func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values
|
||||
|
||||
// HTTPStatusCode asserts that a specified handler returns a specified status code.
|
||||
//
|
||||
// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501)
|
||||
// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool {
|
||||
@@ -124,7 +124,7 @@ func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) s
|
||||
// HTTPBodyContains asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
@@ -144,7 +144,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string,
|
||||
// HTTPBodyNotContains asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
|
||||
@@ -39,3 +39,7 @@ zig-cache/
|
||||
zig-out/
|
||||
|
||||
.DS_Store
|
||||
|
||||
# Ignore compiled stdlib test cases.
|
||||
/internal/integration_test/stdlibs/testdata
|
||||
/internal/integration_test/libsodium/testdata
|
||||
+46
-12
@@ -1,10 +1,10 @@
|
||||
|
||||
gofumpt := mvdan.cc/[email protected]
|
||||
gosimports := github.com/rinchsan/gosimports/cmd/[email protected]
|
||||
golangci_lint := github.com/golangci/golangci-lint/cmd/[email protected]2.2
|
||||
golangci_lint := github.com/golangci/golangci-lint/cmd/[email protected]5.2
|
||||
asmfmt := github.com/klauspost/asmfmt/cmd/[email protected]
|
||||
# sync this with netlify.toml!
|
||||
hugo := github.com/gohugoio/[email protected]2.5
|
||||
hugo := github.com/gohugoio/[email protected]5.2
|
||||
|
||||
# Make 3.81 doesn't support '**' globbing: Set explicitly instead of recursion.
|
||||
all_sources := $(wildcard *.go */*.go */*/*.go */*/*/*.go */*/*/*.go */*/*/*/*.go)
|
||||
@@ -23,7 +23,6 @@ go_test_options ?= -timeout 300s
|
||||
ensureCompilerFastest := -ldflags '-X github.com/tetratelabs/wazero/internal/integration_test/vs.ensureCompilerFastest=true'
|
||||
.PHONY: bench
|
||||
bench:
|
||||
@go test -run=NONE -benchmem -bench=. ./internal/engine/compiler/...
|
||||
@go build ./internal/integration_test/bench/...
|
||||
@# Don't use -test.benchmem as it isn't accurate when comparing against CGO libs
|
||||
@for d in vs/time vs/wasmedge vs/wasmtime ; do \
|
||||
@@ -39,7 +38,7 @@ build.bench:
|
||||
|
||||
.PHONY: test.examples
|
||||
test.examples:
|
||||
@go test $(go_test_options) ./examples/... ./imports/assemblyscript/example/... ./imports/emscripten/... ./experimental/gojs/example/... ./imports/wasi_snapshot_preview1/example/...
|
||||
@go test $(go_test_options) ./examples/... ./imports/assemblyscript/example/... ./imports/emscripten/... ./imports/wasi_snapshot_preview1/example/...
|
||||
|
||||
.PHONY: build.examples.as
|
||||
build.examples.as:
|
||||
@@ -53,16 +52,17 @@ build.examples.as:
|
||||
build.examples.zig: examples/allocation/zig/testdata/greet.wasm imports/wasi_snapshot_preview1/example/testdata/zig/cat.wasm imports/wasi_snapshot_preview1/testdata/zig/wasi.wasm
|
||||
@cd internal/testing/dwarftestdata/testdata/zig; zig build; mv zig-out/*/main.wasm ./ # Need DWARF custom sections.
|
||||
|
||||
tinygo_sources := examples/basic/testdata/add.go examples/allocation/tinygo/testdata/greet.go examples/cli/testdata/cli.go imports/wasi_snapshot_preview1/example/testdata/tinygo/cat.go
|
||||
tinygo_sources := examples/basic/testdata/add.go examples/allocation/tinygo/testdata/greet.go examples/cli/testdata/cli.go imports/wasi_snapshot_preview1/example/testdata/tinygo/cat.go imports/wasi_snapshot_preview1/testdata/tinygo/wasi.go cmd/wazero/testdata/cat/cat.go
|
||||
.PHONY: build.examples.tinygo
|
||||
build.examples.tinygo: $(tinygo_sources)
|
||||
@for f in $^; do \
|
||||
tinygo build -o $$(echo $$f | sed -e 's/\.go/\.wasm/') -scheduler=none --no-debug --target=wasi $$f; \
|
||||
done
|
||||
@mv cmd/wazero/testdata/cat/cat.wasm cmd/wazero/testdata/cat/cat-tinygo.wasm
|
||||
|
||||
# We use zig to build C as it is easy to install and embeds a copy of zig-cc.
|
||||
# Note: Don't use "-Oz" as that breaks our wasi sock example.
|
||||
c_sources := imports/wasi_snapshot_preview1/example/testdata/zig-cc/cat.c imports/wasi_snapshot_preview1/testdata/zig-cc/wasi.c
|
||||
c_sources := imports/wasi_snapshot_preview1/example/testdata/zig-cc/cat.c imports/wasi_snapshot_preview1/testdata/zig-cc/wasi.c internal/testing/dwarftestdata/testdata/zig-cc/main.c
|
||||
.PHONY: build.examples.zig-cc
|
||||
build.examples.zig-cc: $(c_sources)
|
||||
@for f in $^; do \
|
||||
@@ -119,8 +119,13 @@ spectest_v1_testdata_dir := $(spectest_v1_dir)/testdata
|
||||
spec_version_v1 := wg-1.0
|
||||
spectest_v2_dir := $(spectest_base_dir)/v2
|
||||
spectest_v2_testdata_dir := $(spectest_v2_dir)/testdata
|
||||
# Latest draft state as of May 23, 2023.
|
||||
spec_version_v2 := 2e8912e88a3118a46b90e8ccb659e24b4e8f3c23
|
||||
# Latest draft state as of March 12, 2024.
|
||||
spec_version_v2 := 1c5e5d178bd75c79b7a12881c529098beaee2a05
|
||||
spectest_threads_dir := $(spectest_base_dir)/threads
|
||||
spectest_threads_testdata_dir := $(spectest_threads_dir)/testdata
|
||||
# From https://github.com/WebAssembly/threads/tree/upstream-rebuild which has not been merged to main yet.
|
||||
# It will likely be renamed to main in the future - https://github.com/WebAssembly/threads/issues/216.
|
||||
spec_version_threads := 3635ca51a17e57e106988846c5b0e0cc48ac04fc
|
||||
|
||||
.PHONY: build.spectest
|
||||
build.spectest:
|
||||
@@ -162,13 +167,25 @@ build.spectest.v2: # Note: SIMD cases are placed in the "simd" subdirectory.
|
||||
@cd $(spectest_v2_testdata_dir) \
|
||||
&& curl -sSL 'https://api.github.com/repos/WebAssembly/spec/contents/test/core/simd?ref=$(spec_version_v2)' | jq -r '.[]| .download_url' | grep -E ".wast" | xargs -Iurl curl -sJL url -O
|
||||
@cd $(spectest_v2_testdata_dir) && for f in `find . -name '*.wast'`; do \
|
||||
wast2json --debug-names --no-check $$f; \
|
||||
wast2json --debug-names --no-check $$f || true; \
|
||||
done # Ignore the error here as some tests (e.g. comments.wast right now) are not supported by wast2json yet.
|
||||
|
||||
# Note: We currently cannot build the "threads" subdirectory that spawns threads due to missing support in wast2json.
|
||||
# https://github.com/WebAssembly/wabt/issues/2348#issuecomment-1878003959
|
||||
.PHONY: build.spectest.threads
|
||||
build.spectest.threads:
|
||||
@mkdir -p $(spectest_threads_testdata_dir)
|
||||
@cd $(spectest_threads_testdata_dir) \
|
||||
&& curl -sSL 'https://api.github.com/repos/WebAssembly/threads/contents/test/core?ref=$(spec_version_threads)' | jq -r '.[]| .download_url' | grep -E "atomic.wast" | xargs -Iurl curl -sJL url -O
|
||||
@cd $(spectest_threads_testdata_dir) && for f in `find . -name '*.wast'`; do \
|
||||
wast2json --enable-threads --debug-names $$f; \
|
||||
done
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
@go test $(go_test_options) $$(go list ./... | grep -vE '$(spectest_v1_dir)|$(spectest_v2_dir)')
|
||||
@cd internal/version/testdata && go test $(go_test_options) ./...
|
||||
@cd internal/integration_test/fuzz/wazerolib && CGO_ENABLED=0 WASM_BINARY_PATH=testdata/test.wasm go test ./...
|
||||
|
||||
.PHONY: coverage
|
||||
# replace spaces with commas
|
||||
@@ -212,6 +229,14 @@ check:
|
||||
# This makes sure the intepreter can be used. Most often the package that can
|
||||
# drift here is "platform" or "sysfs":
|
||||
#
|
||||
# Ensure we build on plan9. See #1578
|
||||
@GOARCH=amd64 GOOS=plan9 go build ./...
|
||||
# Ensure we build on gojs. See #1526.
|
||||
@GOARCH=wasm GOOS=js go build ./...
|
||||
# Ensure we build on wasip1. See #1526.
|
||||
@GOARCH=wasm GOOS=wasip1 go build ./...
|
||||
# Ensure we build on aix. See #1723
|
||||
@GOARCH=ppc64 GOOS=aix go build ./...
|
||||
# Ensure we build on windows:
|
||||
@GOARCH=amd64 GOOS=windows go build ./...
|
||||
# Ensure we build on an arbitrary operating system:
|
||||
@@ -247,12 +272,21 @@ clean: ## Ensure a clean build
|
||||
@rm -rf dist build coverage.txt
|
||||
@go clean -testcache
|
||||
|
||||
fuzz_default_flags := --no-trace-compares --sanitizer=none -- -rss_limit_mb=8192
|
||||
|
||||
fuzz_timeout_seconds ?= 10
|
||||
.PHONY: fuzz
|
||||
fuzz:
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run basic -- -max_total_time=$(fuzz_timeout_seconds)
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run memory_no_diff -- -max_total_time=$(fuzz_timeout_seconds)
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run validation -- -max_total_time=$(fuzz_timeout_seconds)
|
||||
@cd internal/integration_test/fuzz && cargo test
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run logging_no_diff $(fuzz_default_flags) -max_total_time=$(fuzz_timeout_seconds)
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run no_diff $(fuzz_default_flags) -max_total_time=$(fuzz_timeout_seconds)
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run memory_no_diff $(fuzz_default_flags) -max_total_time=$(fuzz_timeout_seconds)
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run validation $(fuzz_default_flags) -max_total_time=$(fuzz_timeout_seconds)
|
||||
|
||||
libsodium:
|
||||
cd ./internal/integration_test/libsodium/testdata && \
|
||||
curl -s "https://api.github.com/repos/jedisct1/webassembly-benchmarks/contents/2022-12/wasm?ref=7e86d68e99e60130899fbe3b3ab6e9dce9187a7c" \
|
||||
| jq -r '.[] | .download_url' | xargs -n 1 curl -LO
|
||||
|
||||
#### CLI release related ####
|
||||
|
||||
|
||||
+566
-60
@@ -67,7 +67,7 @@ dependency.
|
||||
|
||||
## Project structure
|
||||
|
||||
wazero uses internal packages extensively to balance API compatability desires for end users with the need to safely
|
||||
wazero uses internal packages extensively to balance API compatibility desires for end users with the need to safely
|
||||
share internals between compilers.
|
||||
|
||||
End-user packages include `wazero`, with `Config` structs, `api`, with shared types, and the built-in `wasi` library.
|
||||
@@ -235,7 +235,7 @@ rt := wazero.NewRuntimeConfig() // initialized properly
|
||||
There are a few drawbacks to this, notably some work for maintainers.
|
||||
* Interfaces are decoupled from the structs implementing them, which means the signature has to be repeated twice.
|
||||
* Interfaces have to be documented and guarded at time of use, that 3rd party implementations aren't supported.
|
||||
* As of Golang 1.18, interfaces are still [not well supported](https://github.com/golang/go/issues/5860) in godoc.
|
||||
* As of Golang 1.21, interfaces are still [not well supported](https://github.com/golang/go/issues/5860) in godoc.
|
||||
|
||||
## Config
|
||||
|
||||
@@ -414,12 +414,49 @@ In reflection, this approach worked well for the nearly 1.5 year period leading
|
||||
to version 1.0. We've only had to create a single sub-configuration, `FSConfig`,
|
||||
and it was well understood why when it occurred.
|
||||
|
||||
## Why does InstantiateModule call "_start" by default?
|
||||
We formerly had functions like `StartWASICommand` that would verify preconditions and start WASI's "_start" command.
|
||||
However, this caused confusion because both many languages compiled a WASI dependency, and many did so inconsistently.
|
||||
## Why does `ModuleConfig.WithStartFunctions` default to `_start`?
|
||||
|
||||
That said, if "_start" isn't called, it causes issues in TinyGo, as it needs this in order to implement panic. To deal
|
||||
with this a different way, we have a configuration to call any start functions that exist, which defaults to "_start".
|
||||
We formerly had functions like `StartWASICommand` that would verify
|
||||
preconditions and start WASI's `_start` command. However, this caused confusion
|
||||
because both many languages compiled a WASI dependency, and many did so
|
||||
inconsistently.
|
||||
|
||||
The conflict is that exported functions need to use features the language
|
||||
runtime provides, such as garbage collection. There's a "chicken-egg problem"
|
||||
where `_start` needs to complete in order for exported behavior to work.
|
||||
|
||||
For example, unlike `GOOS=wasip1` in Go 1.21, TinyGo's "wasi" target supports
|
||||
function exports. So, the only way to use FFI style is via the "wasi" target.
|
||||
Not explicitly calling `_start` before an ABI such as wapc-go, would crash, due
|
||||
to setup not happening (e.g. to implement `panic`). Other embedders such as
|
||||
Envoy also called `_start` for the same reason. To avoid a common problem for
|
||||
users unaware of WASI, and also to simplify normal use of WASI (e.g. `main`),
|
||||
we added `_start` to `ModuleConfig.WithStartFunctions`.
|
||||
|
||||
In cases of multiple initializers, such as in wapc-go, users can override this
|
||||
to add the others *after* `_start`. Users who want to explicitly control
|
||||
`_start`, such as some of our unit tests, can clear the start functions and
|
||||
remove it.
|
||||
|
||||
This decision was made in 2022, and holds true in 2023, even with the
|
||||
introduction of "wasix". It holds because "wasix" is backwards compatible with
|
||||
"wasip1". In the future, there will be other ways to start applications, and
|
||||
may not be backwards compatible with "wasip1".
|
||||
|
||||
Most notably WASI "Preview 2" is not implemented in a way compatible with
|
||||
wasip1. Its start function is likely to be different, and defined in the
|
||||
wasi-cli "world". When the design settles, and it is implemented by compilers,
|
||||
wazero will attempt to support "wasip2". However, it won't do so in a way that
|
||||
breaks existing compilers.
|
||||
|
||||
In other words, we won't remove `_start` if "wasip2" continues a path of an
|
||||
alternate function name. If we did, we'd break existing users despite our
|
||||
compatibility promise saying we don't. The most likely case is that when we
|
||||
build-in something incompatible with "wasip1", that start function will be
|
||||
added to the start functions list in addition to `_start`.
|
||||
|
||||
See http://wasix.org
|
||||
See https://github.com/WebAssembly/wasi-cli
|
||||
|
||||
## Runtime == Engine+Store
|
||||
wazero defines a single user-type which combines the specification concept of `Store` with the unspecified `Engine`
|
||||
@@ -447,26 +484,26 @@ https://github.com/bytecodealliance/wasmtime/blob/v0.29.0/crates/lightbeam/src/m
|
||||
|
||||
## Exit
|
||||
|
||||
### Why do we return a `sys.ExitError` on exit code zero?
|
||||
### Why do we only return a `sys.ExitError` on a non-zero exit code?
|
||||
|
||||
It may be surprising to find an error returned on success (exit code zero).
|
||||
This can be explained easier when you think of function returns: When results
|
||||
aren't empty, then you must return an error. This is trickier to explain when
|
||||
results are empty, such as the case in the "_start" function in WASI.
|
||||
It is reasonable to think an exit error should be returned, even if the code is
|
||||
success (zero). Even on success, the module is no longer functional. For
|
||||
example, function exports would error later. However, wazero does not. The only
|
||||
time `sys.ExitError` is on error (non-zero).
|
||||
|
||||
The main rationale for returning an exit error even if the code is success is
|
||||
that the module is no longer functional. For example, function exports would
|
||||
error later. In cases like these, it is better to handle errors where they
|
||||
occur.
|
||||
This decision was to improve performance and ergonomics for guests that both
|
||||
use WASI (have a `_start` function), and also allow custom exports.
|
||||
Specifically, Rust, TinyGo and normal wasi-libc, don't exit the module during
|
||||
`_start`. If they did, it would invalidate their function exports. This means
|
||||
it is unlikely most compilers will change this behavior.
|
||||
|
||||
Luckily, it is not common to exit a module during the "_start" function. For
|
||||
example, the only known compilation target that does this is Emscripten. Most,
|
||||
such as Rust, TinyGo, or normal wasi-libc, don't. If they did, it would
|
||||
invalidate their function exports. This means it is unlikely most compilers
|
||||
will change this behavior.
|
||||
`GOOS=waspi1` from Go 1.21 does exit during `_start`. However, it doesn't
|
||||
support other exports besides `_start`, and `_start` is not defined to be
|
||||
called multiple times anyway.
|
||||
|
||||
In summary, we return a `sys.ExitError` to the caller whenever we get it, as it
|
||||
properly reflects the state of the module, which would be closed on this error.
|
||||
Since `sys.ExitError` is not always returned, we added `Module.IsClosed` for
|
||||
defensive checks. This helps integrators avoid calling functions which will
|
||||
always fail.
|
||||
|
||||
### Why panic with `sys.ExitError` after a host function exits?
|
||||
|
||||
@@ -531,7 +568,7 @@ In short, wazero defined system configuration in `ModuleConfig`, not a WASI type
|
||||
one spec to another with minimal impact. This has other helpful benefits, as centralized resources are simpler to close
|
||||
coherently (ex via `Module.Close`).
|
||||
|
||||
In reflection, this worked well as more ABI became usable in wazero. For example, `GOARCH=wasm GOOS=js` code uses the
|
||||
In reflection, this worked well as more ABI became usable in wazero. For example, `GOOS=js GOARCH=wasm` code uses the
|
||||
same `ModuleConfig` (and `FSConfig`) WASI uses, and in compatible ways.
|
||||
|
||||
### Background on `ModuleConfig` design
|
||||
@@ -579,6 +616,135 @@ act differently and document `ModuleConfig` is more about emulating, not necessa
|
||||
|
||||
## File systems
|
||||
|
||||
### Motivation on `sys.FS`
|
||||
|
||||
The `sys.FS` abstraction in wazero was created because of limitations in
|
||||
`fs.FS`, and `fs.File` in Go. Compilers targeting `wasip1` may access
|
||||
functionality that writes new files. The ability to overcome this was requested
|
||||
even before wazero was named this, via issue #21 in March 2021.
|
||||
|
||||
A month later, golang/go#45757 was raised by someone else on the same topic. As
|
||||
of July 2023, this has not resolved to a writeable file system abstraction.
|
||||
|
||||
Over the next year more use cases accumulated, consolidated in March 2022 into
|
||||
#390. This closed in January 2023 with a milestone of providing more
|
||||
functionality, limited to users giving a real directory. This didn't yet expose
|
||||
a file abstraction for general purpose use. Internally, this used `os.File`.
|
||||
However, a wasm module instance is a virtual machine. Only supporting `os.File`
|
||||
breaks sand-boxing use cases. Moreover, `os.File` is not an interface. Even
|
||||
though this abstracts functionality, it does allow interception use cases.
|
||||
|
||||
Hence, a few days later in January 2023, we had more issues asking to expose an
|
||||
abstraction, #1013 and later #1532, on use cases like masking access to files.
|
||||
In other words, the use case requests never stopped, and aren't solved by
|
||||
exposing only real files.
|
||||
|
||||
In summary, the primary motivation for exposing a replacement for `fs.FS` and
|
||||
`fs.File` was around repetitive use case requests for years, around
|
||||
interception and the ability to create new files, both virtual and real files.
|
||||
While some use cases are solved with real files, not all are. Regardless, an
|
||||
interface approach is necessary to ensure users can intercept I/O operations.
|
||||
|
||||
### Why doesn't `sys.File` have a `Fd()` method?
|
||||
|
||||
There are many features we could expose. We could make File expose underlying
|
||||
file descriptors in case they are supported, for integration of system calls
|
||||
that accept multiple ones, namely `poll` for multiplexing. This special case is
|
||||
described in a subsequent section.
|
||||
|
||||
As noted above, users have been asking for a file abstraction for over two
|
||||
years, and a common answer was to wait. Making users wait is a problem,
|
||||
especially so long. Good reasons to make people wait are stabilization. Edge
|
||||
case features are not a great reason to hold abstractions from users.
|
||||
|
||||
Another reason is implementation difficulty. Go did not attempt to abstract
|
||||
file descriptors. For example, unlike `fs.ReadFile` there is no `fs.FdFile`
|
||||
interface. Most likely, this is because file descriptors are an implementation
|
||||
detail of common features. Programming languages, including Go, do not require
|
||||
end users to know about file descriptors. Types such as `fs.File` can be used
|
||||
without any knowledge of them. Implementations may or may not have file
|
||||
descriptors. For example, in Go, `os.DirFS` has underlying file descriptors
|
||||
while `embed.FS` does not.
|
||||
|
||||
Despite this, some may want to expose a non-standard interface because
|
||||
`os.File` has `Fd() uintptr` to return a file descriptor. Mainly, this is
|
||||
handy to integrate with `syscall` package functions (on `GOOS` values that
|
||||
declare them). Notice, though that `uintptr` is unsafe and not an abstraction.
|
||||
Close inspection will find some `os.File` types internally use `poll.FD`
|
||||
instead, yet this is not possible to use abstractly because that type is not
|
||||
exposed. For example, `plan9` uses a different type than `poll.FD`. In other
|
||||
words, even in real files, `Fd()` is not wholly portable, despite it being
|
||||
useful on many operating systems with the `syscall` package.
|
||||
|
||||
The reasons above, why Go doesn't abstract `FdFile` interface are a subset of
|
||||
reasons why `sys.File` does not. If we exposed `File.Fd()` we not only would
|
||||
have to declare all the edge cases that Go describes including impact of
|
||||
finalizers, we would have to describe these in terms of virtualized files.
|
||||
Then, we would have to reason with this value vs our existing virtualized
|
||||
`sys.FileTable`, mapping whatever type we return to keys in that table, also
|
||||
in consideration of garbage collection impact. The combination of issues like
|
||||
this could lead down a path of not implementing a file system abstraction at
|
||||
all, and instead a weak key mapped abstraction of the `syscall` package. Once
|
||||
we finished with all the edge cases, we would have lost context of the original
|
||||
reason why we started.. simply to allow file write access!
|
||||
|
||||
When wazero attempts to do more than what the Go programming language team, it
|
||||
has to be carefully evaluated, to:
|
||||
* Be possible to implement at least for `os.File` backed files
|
||||
* Not be confusing or cognitively hard for virtual file systems and normal use.
|
||||
* Affordable: custom code is solely the responsible by the core team, a much
|
||||
smaller group of individuals than who maintain the Go programming language.
|
||||
|
||||
Due to problems well known in Go, consideration of the end users who constantly
|
||||
ask for basic file system functionality, and the difficulty virtualizing file
|
||||
descriptors at multiple levels, we don't expose `Fd()` and likely won't ever
|
||||
expose `Fd()` on `sys.File`.
|
||||
|
||||
### Why does `sys.File` have a `Poll()` method, while `sys.FS` does not?
|
||||
|
||||
wazero exposes `File.Poll` which allows one-at-a-time poll use cases,
|
||||
requested by multiple users. This not only includes abstract tests such as
|
||||
Go 1.21 `GOOS=wasip1`, but real use cases including python and container2wasm
|
||||
repls, as well listen sockets. The main use cases is non-blocking poll on a
|
||||
single file. Being a single file, this has no risk of problems such as
|
||||
head-of-line blocking, even when emulated.
|
||||
|
||||
The main use case of multi-poll are bidirectional network services, something
|
||||
not used in `GOOS=wasip1` standard libraries, but could be in the future.
|
||||
Moving forward without a multi-poller allows wazero to expose its file system
|
||||
abstraction instead of continuing to hold back it back for edge cases. We'll
|
||||
continue discussion below regardless, as rationale was requested.
|
||||
|
||||
You can loop through multiple `sys.File`, using `File.Poll` to see if an event
|
||||
is ready, but there is a head-of-line blocking problem. If a long timeout is
|
||||
used, bad luck could have a file that has nothing to read or write before one
|
||||
that does. This could cause more blocking than necessary, even if you could
|
||||
poll the others just after with a zero timeout. What's worse than this is if
|
||||
unlimited blocking was used (`timeout=-1`). The host implementations could use
|
||||
goroutines to avoid this, but interrupting a "forever" poll is problematic. All
|
||||
of these are reasons to consider a multi-poll API, but do not require exporting
|
||||
`File.Fd()`.
|
||||
|
||||
Should multi-poll becomes critical, `sys.FS` could expose a `Poll` function
|
||||
like below, despite it being the non-portable, complicated if possible to
|
||||
implement on all platforms and virtual file systems.
|
||||
```go
|
||||
ready, errno := fs.Poll([]sys.PollFile{{f1, sys.POLLIN}, {f2, sys.POLLOUT}}, timeoutMillis)
|
||||
```
|
||||
|
||||
A real filesystem could handle this by using an approach like the internal
|
||||
`unix.Poll` function in Go, passing file descriptors on unix platforms, or
|
||||
returning `sys.ENOSYS` for unsupported operating systems. Implementation for
|
||||
virtual files could have a strategy around timeout to avoid the worst case of
|
||||
head-of-line blocking (unlimited timeout).
|
||||
|
||||
Let's remember that when designing abstractions, it is not best to add an
|
||||
interface for everything. Certainly, Go doesn't, as evidenced by them not
|
||||
exposing `poll.FD` in `os.File`! Such a multi-poll could be limited to
|
||||
built-in filesystems in the wazero repository, avoiding complexity of trying to
|
||||
support and test this abstractly. This would still permit multiplexing for CLI
|
||||
users, and also permit single file polling as exists now.
|
||||
|
||||
### Why doesn't wazero implement the working directory?
|
||||
|
||||
An early design of wazero's API included a `WithWorkDirFS` which allowed
|
||||
@@ -664,7 +830,7 @@ WASI is an abstraction over syscalls. For example, the signature of `fs.Open`
|
||||
does not permit use of flags. This creates conflict on what default behaviors
|
||||
to take when Go implemented `os.DirFS`. On the other hand, `path_open` can pass
|
||||
flags, and in fact tests require them to be honored in specific ways. This
|
||||
extends beyond WASI as even `GOARCH=wasm GOOS=js` compiled code requires
|
||||
extends beyond WASI as even `GOOS=js GOARCH=wasm` compiled code requires
|
||||
certain flags passed to `os.OpenFile` which are impossible to pass due to the
|
||||
signature of `fs.FS`.
|
||||
|
||||
@@ -681,7 +847,61 @@ these to be easier to close.
|
||||
See https://github.com/WebAssembly/wasi-testsuite
|
||||
See https://github.com/golang/go/issues/58141
|
||||
|
||||
### fd_pread: io.Seeker fallback when io.ReaderAt is not supported
|
||||
## Why is our `Readdir` function more like Go's `os.File` than POSIX `readdir`?
|
||||
|
||||
At one point we attempted to move from a bulk `Readdir` function to something
|
||||
more like the POSIX `DIR` struct, exposing functions like `telldir`, `seekdir`
|
||||
and `readdir`. However, we chose the design more like `os.File.Readdir`,
|
||||
because it performs and fits wasip1 better.
|
||||
|
||||
### wasip1/wasix
|
||||
|
||||
`fd_readdir` in wasip1 (and so also wasix) is like `getdents` in Linux, not
|
||||
`readdir` in POSIX. `getdents` is more like Go's `os.File.Readdir`.
|
||||
|
||||
We currently have an internal type `sys.DirentCache` which only is used by
|
||||
wasip1 or wasix. When `HostModuleBuilder` adds support for instantiation state,
|
||||
we could move this to the `wasi_snapshot_preview1` package. Meanwhile, all
|
||||
filesystem code is internal anyway, so this special-case is acceptable.
|
||||
|
||||
### wasip2
|
||||
|
||||
`directory-entry-stream` in wasi-filesystem preview2 is defined in component
|
||||
model, not an ABI, but in wasmtime it is a consuming iterator. A consuming
|
||||
iterator is easy to support with anything (like `Readdir(1)`), even if it is
|
||||
inefficient as you can neither bulk read nor skip. The implementation of the
|
||||
preview1 adapter (uses preview2) confirms this. They use a dirent cache similar
|
||||
in some ways to our `sysfs.DirentCache`. As there is no seek concept in
|
||||
preview2, they interpret the cookie as numeric and read on repeat entries when
|
||||
a cache wasn't available. Note: we currently do not skip-read like this as it
|
||||
risks buffering large directories, and no user has requested entries before the
|
||||
cache, yet.
|
||||
|
||||
Regardless, wasip2 is not complete until the end of 2023. We can defer design
|
||||
discussion until after it is stable and after the reference impl wasmtime
|
||||
implements it.
|
||||
|
||||
See
|
||||
* https://github.com/WebAssembly/wasi-filesystem/blob/ef9fc87c07323a6827632edeb6a7388b31266c8e/example-world.md#directory_entry_stream
|
||||
* https://github.com/bytecodealliance/wasmtime/blob/b741f7c79d72492d17ab8a29c8ffe4687715938e/crates/wasi/src/preview2/preview2/filesystem.rs#L286-L296
|
||||
* https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L2131-L2137
|
||||
* https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L936
|
||||
|
||||
### wasip3
|
||||
|
||||
`directory-entry-stream` is documented to change significantly in wasip3 moving
|
||||
from synchronous to synchronous streams. This is dramatically different than
|
||||
POSIX `readdir` which is synchronous.
|
||||
|
||||
Regardless, wasip3 is not complete until after wasip2, which means 2024 or
|
||||
later. We can defer design discussion until after it is stable and after the
|
||||
reference impl wasmtime implements it.
|
||||
|
||||
See
|
||||
* https://github.com/WebAssembly/WASI/blob/ddfe3d1dda5d1473f37ecebc552ae20ce5fd319a/docs/WitInWasi.md#Streams
|
||||
* https://docs.google.com/presentation/d/1MNVOZ8hdofO3tI0szg_i-Yoy0N2QPU2C--LzVuoGSlE/edit#slide=id.g1270ef7d5b6_0_662
|
||||
|
||||
### How do we implement `Pread` with an `fs.File`?
|
||||
|
||||
`ReadAt` is the Go equivalent to `pread`: it does not affect, and is not
|
||||
affected by, the underlying file offset. Unfortunately, `io.ReaderAt` is not
|
||||
@@ -729,7 +949,7 @@ third `path_len` has ambiguous semantics.
|
||||
`path` offset for the exact length of `path_len`.
|
||||
|
||||
Wasmer considers `path_len` to be the maximum length instead of the exact
|
||||
length that should be written.
|
||||
length that should be written.
|
||||
See https://github.com/wasmerio/wasmer/blob/3463c51268ed551933392a4063bd4f8e7498b0f6/lib/wasi/src/syscalls/mod.rs#L764
|
||||
|
||||
The semantics in wazero follows that of wasmtime.
|
||||
@@ -738,11 +958,84 @@ See https://github.com/bytecodealliance/wasmtime/blob/2ca01ae9478f199337cf743a6a
|
||||
Their semantics match when `path_len` == the length of `path`, so in practice
|
||||
this difference won't matter match.
|
||||
|
||||
## Why does fd_readdir not include dot (".") and dot-dot ("..") entries?
|
||||
## fd_readdir
|
||||
|
||||
When reading a directory, wazero code does not return dot (".") and dot-dot
|
||||
("..") entries. The main reason is that Go does not return them from
|
||||
`os.ReadDir`, and materializing them is complicated (at least dot-dot is).
|
||||
### Why does "wasi_snapshot_preview1" require dot entries when POSIX does not?
|
||||
|
||||
In October 2019, WASI project knew requiring dot entries ("." and "..") was not
|
||||
documented in preview1, not required by POSIX and problematic to synthesize.
|
||||
For example, Windows runtimes backed by `FindNextFileW` could not return these.
|
||||
A year later, the tag representing WASI preview 1 (`snapshot-01`) was made.
|
||||
This did not include the requested change of making dot entries optional.
|
||||
|
||||
The `phases/snapshot/docs.md` document was altered in subsequent years in
|
||||
significant ways, often in lock-step with wasmtime or wasi-libc. In January
|
||||
2022, `sock_accept` was added to `phases/snapshot/docs.md`, a document later
|
||||
renamed to later renamed to `legacy/preview1/docs.md`.
|
||||
|
||||
As a result, the ABI and behavior remained unstable: The `snapshot-01` tag was
|
||||
not an effective basis of portability. A test suite was requested well before
|
||||
this tag, in April 2019. Meanwhile, compliance had no meaning. Developers had
|
||||
to track changes to the latest doc, while clarifying with wasi-libc or wasmtime
|
||||
behavior. This lack of stability could have permitted a fix to the dot entries
|
||||
problem, just as it permitted changes desired by other users.
|
||||
|
||||
In November 2022, the wasi-testsuite project began and started solidifying
|
||||
expectations. This quickly led to changes in runtimes and the spec doc. WASI
|
||||
began importing tests from wasmtime as required behaviors for all runtimes.
|
||||
Some changes implied changes to wasi-libc. For example, `readdir` began to
|
||||
imply inode fan-outs, which caused performance regressions. Most notably a
|
||||
test merged in January required dot entries. Tests were merged without running
|
||||
against any runtime, and even when run ad-hoc only against Linux. Hence,
|
||||
portability issues mentioned over three years earlier did not trigger any
|
||||
failure until wazero (which tests Windows) noticed.
|
||||
|
||||
In the same month, wazero requested to revert this change primarily because
|
||||
Go does not return them from `os.ReadDir`, and materializing them is
|
||||
complicated due to tests also requiring inodes. Moreover, they are discarded by
|
||||
not just Go, but other common programming languages. This was rejected by the
|
||||
WASI lead for preview1, but considered for the completely different ABI named
|
||||
preview2.
|
||||
|
||||
In February 2023, the WASI chair declared that new rule requiring preview1 to
|
||||
return dot entries "was decided by the subgroup as a whole", citing meeting
|
||||
notes. According to these notes, the WASI lead stated incorrectly that POSIX
|
||||
conformance required returning dot entries, something it explicitly says are
|
||||
optional. In other words, he said filtering them out would make Preview1
|
||||
non-conforming, and asked if anyone objects to this. The co-chair was noted to
|
||||
say "Because there are existing P1 programs, we shouldn’t make changes like
|
||||
this." No other were recorded to say anything.
|
||||
|
||||
In summary, preview1 was changed retrospectively to require dot entries and
|
||||
preview2 was changed to require their absence. This rule was reverse engineered
|
||||
from wasmtime tests, and affirmed on two false premises:
|
||||
|
||||
* POSIX compliance requires dot entries
|
||||
* POSIX literally says these are optional
|
||||
* WASI cannot make changes because there are existing P1 programs.
|
||||
* Changes to Preview 1 happened before and after this topic.
|
||||
|
||||
As of June 2023, wasi-testsuite still only runs on Linux, so compliance of this
|
||||
rule on Windows is left to runtimes to decide to validate. The preview2 adapter
|
||||
uses fake cookies zero and one to refer to dot dirents, uses a real inode for
|
||||
the dot(".") entry and zero inode for dot-dot("..").
|
||||
|
||||
See https://github.com/WebAssembly/wasi-filesystem/issues/3
|
||||
See https://github.com/WebAssembly/WASI/tree/snapshot-01
|
||||
See https://github.com/WebAssembly/WASI/issues/9
|
||||
See https://github.com/WebAssembly/WASI/pull/458
|
||||
See https://github.com/WebAssembly/wasi-testsuite/pull/32
|
||||
See https://github.com/WebAssembly/wasi-libc/pull/345
|
||||
See https://github.com/WebAssembly/wasi-testsuite/issues/52
|
||||
See https://github.com/WebAssembly/WASI/pull/516
|
||||
See https://github.com/WebAssembly/meetings/blob/main/wasi/2023/WASI-02-09.md#should-preview1-fd_readdir-filter-out--and-
|
||||
See https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1026-L1041
|
||||
|
||||
### Why are dot (".") and dot-dot ("..") entries problematic?
|
||||
|
||||
When reading a directory, dot (".") and dot-dot ("..") entries are problematic.
|
||||
For example, Go does not return them from `os.ReadDir`, and materializing them
|
||||
is complicated (at least dot-dot is).
|
||||
|
||||
A directory entry has stat information in it. The stat information includes
|
||||
inode which is used for comparing file equivalence. In the simple case of dot,
|
||||
@@ -758,7 +1051,19 @@ well, such as the decision to not return ".." from a root path. In any case,
|
||||
this should start to explain that faking entries when underlying stdlib doesn't
|
||||
return them is tricky and requires quite a lot of state.
|
||||
|
||||
Even if we did that, it would cause expense to all users of wazero, so we'd
|
||||
Another issue is around the `Dirent.Off` value of a directory entry, sometimes
|
||||
called a "cookie" in Linux man pagers. When the host operating system or
|
||||
library function does not return dot entries, to support functions such as
|
||||
`seekdir`, you still need a value for `Dirent.Off`. Naively, you can synthesize
|
||||
these by choosing sequential offsets zero and one. However, POSIX strictly says
|
||||
offsets should be treated opaquely. The backing filesystem could use these to
|
||||
represent real entries. For example, a directory with one entry could use zero
|
||||
as the `Dirent.Off` value. If you also used zero for the "." dirent, there
|
||||
would be a clash. This means if you synthesize `Dirent.Off` for any entry, you
|
||||
need to synthesize this value for all entries. In practice, the simplest way is
|
||||
using an incrementing number, such as done in the WASI preview2 adapter.
|
||||
|
||||
Working around these issues causes expense to all users of wazero, so we'd
|
||||
then look to see if that would be justified or not. However, the most common
|
||||
compilers involved in end user questions, as of early 2023 are TinyGo, Rust and
|
||||
Zig. All of these compile code which ignores dot and dot-dot entries. In other
|
||||
@@ -773,12 +1078,100 @@ which summarizes to "these are optional"
|
||||
|
||||
> The readdir() function shall not return directory entries containing empty names. If entries for dot or dot-dot exist, one entry shall be returned for dot and one entry shall be returned for dot-dot; otherwise, they shall not be returned.
|
||||
|
||||
In summary, wazero not only doesn't return dot and dot-dot entries because Go
|
||||
doesn't and emulating them in spite of that would result in no difference
|
||||
except hire overhead to the majority of our users.
|
||||
Unfortunately, as described above, the WASI project decided in early 2023 to
|
||||
require dot entries in both the spec and the wasi-testsuite. For only this
|
||||
reason, wazero adds overhead to synthesize dot entries despite it being
|
||||
unnecessary for most users.
|
||||
|
||||
See https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html
|
||||
See https://github.com/golang/go/blob/go1.20/src/os/dir_unix.go#L108-L110
|
||||
See https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1026-L1041
|
||||
|
||||
### Why don't we pre-populate an inode for the dot-dot ("..") entry?
|
||||
|
||||
We only populate an inode for dot (".") because wasi-testsuite requires it, and
|
||||
we likely already have it (because we cache it). We could attempt to populate
|
||||
one for dot-dot (".."), but chose not to.
|
||||
|
||||
Firstly, wasi-testsuite does not require the inode of dot-dot, possibly because
|
||||
the wasip2 adapter doesn't populate it (but we don't really know why).
|
||||
|
||||
The only other reason to populate it would be to avoid wasi-libc's stat fanout
|
||||
when it is missing. However, wasi-libc explicitly doesn't fan-out to lstat on
|
||||
the ".." entry on a zero ino.
|
||||
|
||||
Fetching dot-dot's inode despite the above not only doesn't help wasi-libc, but
|
||||
it also hurts languages that don't use it, such as Go. These languages would
|
||||
pay a stat syscall penalty even if they don't need the inode. In fact, Go
|
||||
discards both dot entries!
|
||||
|
||||
In summary, there are no significant upsides in attempting to pre-fetch
|
||||
dot-dot's inode, and there are downsides to doing it anyway.
|
||||
|
||||
See
|
||||
* https://github.com/WebAssembly/wasi-libc/blob/bd950eb128bff337153de217b11270f948d04bb4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L87-L94
|
||||
* https://github.com/WebAssembly/wasi-testsuite/blob/main/tests/rust/src/bin/fd_readdir.rs#L108
|
||||
* https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1037
|
||||
|
||||
### Why don't we require inodes to be non-zero?
|
||||
|
||||
We don't require a non-zero value for `Dirent.Ino` because doing so can prevent
|
||||
a real one from resolving later via `Stat_t.Ino`.
|
||||
|
||||
We define `Ino` like `d_ino` in POSIX which doesn't special-case zero. It can
|
||||
be zero for a few reasons:
|
||||
|
||||
* The file is not a regular file or directory.
|
||||
* The underlying filesystem does not support inodes. e.g. embed:fs
|
||||
* A directory doesn't include inodes, but a later stat can. e.g. Windows
|
||||
* The backend is based on wasi-filesystem (a.k.a wasip2), which has
|
||||
`directory_entry.inode` optional, and might remove it entirely.
|
||||
|
||||
There are other downsides to returning a zero inode in widely used compilers:
|
||||
|
||||
* File equivalence utilities, like `os.SameFile` will not work.
|
||||
* wasi-libc's `wasip1` mode will call `lstat` and attempt to retrieve a
|
||||
non-zero value (unless the entry is named "..").
|
||||
|
||||
A new compiler may accidentally skip a `Dirent` with a zero `Ino` if emulating
|
||||
a non-POSIX function and re-using `Dirent.Ino` for `d_fileno`.
|
||||
|
||||
* Linux `getdents` doesn't define `d_fileno` must be non-zero
|
||||
* BSD `getdirentries` is implementation specific. For example, OpenBSD will
|
||||
return dirents with a zero `d_fileno`, but Darwin will skip them.
|
||||
|
||||
The above shouldn't be a problem, even in the case of BSD, because `wasip1` is
|
||||
defined more in terms of `getdents` than `getdirentries`. The bottom half of
|
||||
either should treat `wasip1` (or any similar ABI such as wasix or wasip2) as a
|
||||
different operating system and either use different logic that doesn't skip, or
|
||||
synthesize a fake non-zero `d_fileno` when `d_ino` is zero.
|
||||
|
||||
However, this has been a problem. Go's `syscall.ParseDirent` utility is shared
|
||||
for all `GOOS=unix`. For simplicity, this abstracts `direntIno` with data from
|
||||
`d_fileno` or `d_ino`, and drops if either are zero, even if `d_fileno` is the
|
||||
only field with zero explicitly defined. This led to a change to special case
|
||||
`GOOS=wasip1` as otherwise virtual files would be unconditionally skipped.
|
||||
|
||||
In practice, this problem is rather unique due to so many compilers relying on
|
||||
wasi-libc, which tolerates a zero inode. For example, while issues were
|
||||
reported about the performance regression when wasi-libc began doing a fan-out
|
||||
on zero `Dirent.Ino`, no issues were reported about dirents being dropped as a
|
||||
result.
|
||||
|
||||
In summary, rather than complicating implementation and forcing non-zero inodes
|
||||
for a rare case, we permit zero. We instead document this topic thoroughly, so
|
||||
that emerging compilers can re-use the research and reference it on conflict.
|
||||
We also document that `Ino` should be non-zero, so that users implementing that
|
||||
field will attempt to get it.
|
||||
|
||||
See
|
||||
* https://github.com/WebAssembly/wasi-filesystem/pull/81
|
||||
* https://github.com/WebAssembly/wasi-libc/blob/bd950eb128bff337153de217b11270f948d04bb4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L87-L94
|
||||
* https://linux.die.net/man/3/getdents
|
||||
* https://www.unix.com/man-page/osx/2/getdirentries/
|
||||
* https://man.openbsd.org/OpenBSD-5.4/getdirentries.2
|
||||
* https://github.com/golang/go/blob/go1.20/src/syscall/dirent.go#L60-L102
|
||||
* https://go-review.googlesource.com/c/go/+/507915
|
||||
|
||||
## sys.Walltime and Nanotime
|
||||
|
||||
@@ -907,6 +1300,68 @@ See https://github.com/WebAssembly/stack-switching/discussions/38
|
||||
See https://github.com/WebAssembly/wasi-threads#what-can-be-skipped
|
||||
See https://slinkydeveloper.com/Kubernetes-controllers-A-New-Hope/
|
||||
|
||||
## sys.Stat_t
|
||||
|
||||
We expose `stat` information as `sys.Stat_t`, like `syscall.Stat_t` except
|
||||
defined without build constraints. For example, you can use `sys.Stat_t` on
|
||||
`GOOS=windows` which doesn't define `syscall.Stat_t`.
|
||||
|
||||
The first use case of this is to return inodes from `fs.FileInfo` without
|
||||
relying on platform-specifics. For example, a user could return `*sys.Stat_t`
|
||||
from `info.Sys()` and define a non-zero inode for a virtual file, or map a
|
||||
real inode to a virtual one.
|
||||
|
||||
Notable choices per field are listed below, where `sys.Stat_t` is unlike
|
||||
`syscall.Stat_t` on `GOOS=linux`, or needs clarification. One common issue
|
||||
not repeated below is that numeric fields are 64-bit when at least one platform
|
||||
defines it that large. Also, zero values are equivalent to nil or absent.
|
||||
|
||||
* `Dev` and `Ino` (`Inode`) are both defined unsigned as they are defined
|
||||
opaque, and most `syscall.Stat_t` also defined them unsigned. There are
|
||||
separate sections in this document discussing the impact of zero in `Ino`.
|
||||
* `Mode` is defined as a `fs.FileMode` even though that is not defined in POSIX
|
||||
and will not map to all possible values. This is because the current use is
|
||||
WASI, which doesn't define any types or features not already supported. By
|
||||
using `fs.FileMode`, we can re-use routine experience in Go.
|
||||
* `NLink` is unsigned because it is defined that way in `syscall.Stat_t`: there
|
||||
can never be less than zero links to a file. We suggest defaulting to 1 in
|
||||
conversions when information is not knowable because at least that many links
|
||||
exist.
|
||||
* `Size` is signed because it is defined that way in `syscall.Stat_t`: while
|
||||
regular files and directories will always be non-negative, irregular files
|
||||
are possibly negative or not defined. Notably sparse files are known to
|
||||
return negative values.
|
||||
* `Atim`, `Mtim` and `Ctim` are signed because they are defined that way in
|
||||
`syscall.Stat_t`: Negative values are time before 1970. The resolution is
|
||||
nanosecond because that's the maximum resolution currently supported in Go.
|
||||
|
||||
### Why do we use `sys.EpochNanos` instead of `time.Time` or similar?
|
||||
|
||||
To simplify documentation, we defined a type alias `sys.EpochNanos` for int64.
|
||||
`time.Time` is a data structure, and we could have used this for
|
||||
`syscall.Stat_t` time values. The most important reason we do not is conversion
|
||||
penalty deriving time from common types.
|
||||
|
||||
The most common ABI used in `wasip2`. This, and compatible ABI such as `wasix`,
|
||||
encode timestamps in memory as a 64-bit number. If we used `time.Time`, we
|
||||
would have to convert an underlying type like `syscall.Timespec` to `time.Time`
|
||||
only to later have to call `.UnixNano()` to convert it back to a 64-bit number.
|
||||
|
||||
In the future, the component model module "wasi-filesystem" may represent stat
|
||||
timestamps with a type shared with "wasi-clocks", abstractly structured similar
|
||||
to `time.Time`. However, component model intentionally does not define an ABI.
|
||||
It is likely that the canonical ABI for timestamp will be in two parts, but it
|
||||
is not required for it to be intermediately represented this way. A utility
|
||||
like `syscall.NsecToTimespec` could split an int64 so that it could be written
|
||||
to memory as 96 bytes (int64, int32), without allocating a struct.
|
||||
|
||||
Finally, some may confuse epoch nanoseconds with 32-bit epoch seconds. While
|
||||
32-bit epoch seconds has "The year 2038" problem, epoch nanoseconds has
|
||||
"The Year 2262" problem, which is even less concerning for this library. If
|
||||
the Go programming language and wazero exist in the 2200's, we can make a major
|
||||
version increment to adjust the `sys.EpochNanos` approach. Meanwhile, we have
|
||||
faster code.
|
||||
|
||||
## poll_oneoff
|
||||
|
||||
`poll_oneoff` is a WASI API for waiting for I/O events on multiple handles.
|
||||
@@ -944,19 +1399,18 @@ as for regular file descriptors: data is assumed to be present and
|
||||
a success is written back to the result buffer.
|
||||
|
||||
However, if the reader is detected to read from `os.Stdin`,
|
||||
a special code path is followed, invoking `platform.Select()`.
|
||||
a special code path is followed, invoking `sysfs.poll()`.
|
||||
|
||||
`platform.Select()` is a wrapper for `select(2)` on POSIX systems,
|
||||
and it is mocked for a handful of cases also on Windows.
|
||||
`sysfs.poll()` is a wrapper for `poll(2)` on POSIX systems,
|
||||
and it is emulated on Windows.
|
||||
|
||||
### Select on POSIX
|
||||
### Poll on POSIX
|
||||
|
||||
On POSIX systems,`select(2)` allows to wait for incoming data on a file
|
||||
On POSIX systems, `poll(2)` allows to wait for incoming data on a file
|
||||
descriptor, and block until either data becomes available or the timeout
|
||||
expires. It is not surprising that `select(2)` and `poll(2)` have lot in common:
|
||||
the main difference is how the file descriptor parameters are passed.
|
||||
expires.
|
||||
|
||||
Usage of `platform.Select()` is only reserved for the standard input case, because
|
||||
Usage of `syfs.poll()` is currently only reserved for standard input, because
|
||||
|
||||
1. it is really only necessary to handle interactive input: otherwise,
|
||||
there is no way in Go to peek from Standard Input without actually
|
||||
@@ -965,11 +1419,11 @@ Usage of `platform.Select()` is only reserved for the standard input case, becau
|
||||
2. if `Stdin` is connected to a pipe, it is ok in most cases to return
|
||||
with success immediately;
|
||||
|
||||
3. `platform.Select()` is currently a blocking call, irrespective of goroutines,
|
||||
3. `syfs.poll()` is currently a blocking call, irrespective of goroutines,
|
||||
because the underlying syscall is; thus, it is better to limit its usage.
|
||||
|
||||
So, if the subscription is for `os.Stdin` and the handle is detected
|
||||
to correspond to an interactive session, then `platform.Select()` will be
|
||||
to correspond to an interactive session, then `sysfs.poll()` will be
|
||||
invoked with a the `Stdin` handle *and* the timeout.
|
||||
|
||||
This also means that in this specific case, the timeout is uninterruptible,
|
||||
@@ -977,25 +1431,46 @@ unless data becomes available on `Stdin` itself.
|
||||
|
||||
### Select on Windows
|
||||
|
||||
On Windows the `platform.Select()` is much more straightforward,
|
||||
and it really just replicates the behavior found in the general cases
|
||||
for `FdRead` subscriptions: in other words, the subscription to `Stdin`
|
||||
is immediately acknowledged.
|
||||
On Windows `sysfs.poll()` cannot be delegated to a single
|
||||
syscall, because there is no single syscall to handle sockets,
|
||||
pipes and regular files.
|
||||
|
||||
The implementation also support a timeout, but in this case
|
||||
it relies on `time.Sleep()`, which notably, as compared to the POSIX
|
||||
case, interruptible and compatible with goroutines.
|
||||
Instead, we emulate its behavior for the cases that are currently
|
||||
of interest.
|
||||
|
||||
However, because `Stdin` subscriptions are always acknowledged
|
||||
without wait and because this code path is always followed only
|
||||
when at least one `Stdin` subscription is present, then the
|
||||
timeout is effectively always handled externally.
|
||||
- For regular files, we _always_ report them as ready, as
|
||||
[most operating systems do anyway][async-io-windows].
|
||||
|
||||
In any case, the behavior of `platform.Select` on Windows
|
||||
is sensibly different from the behavior on POSIX platforms;
|
||||
we plan to refine and further align it in semantics in the future.
|
||||
- For pipes, we invoke [`PeekNamedPipe`][peeknamedpipe]
|
||||
for each file handle we detect is a pipe open for reading.
|
||||
We currently ignore pipes open for writing.
|
||||
|
||||
- Notably, we include also support for sockets using the [WinSock
|
||||
implementation of `poll`][wsapoll], but instead
|
||||
of relying on the timeout argument of the `WSAPoll` function,
|
||||
we set a 0-duration timeout so that it behaves like a peek.
|
||||
|
||||
This way, we can check for regular files all at once,
|
||||
at the beginning of the function, then we poll pipes and
|
||||
sockets periodically using a cancellable `time.Tick`,
|
||||
which plays nicely with the rest of the Go runtime.
|
||||
|
||||
### Impact of blocking
|
||||
|
||||
Because this is a blocking syscall, it will also block the carrier thread of
|
||||
the goroutine, preventing any means to support context cancellation directly.
|
||||
|
||||
There are ways to obviate this issue. We outline here one idea, that is however
|
||||
not currently implemented. A common approach to support context cancellation is
|
||||
to add a signal file descriptor to the set, e.g. the read-end of a pipe or an
|
||||
eventfd on Linux. When the context is canceled, we may unblock a Select call by
|
||||
writing to the fd, causing it to return immediately. This however requires to
|
||||
do a bit of housekeeping to hide the "special" FD from the end-user.
|
||||
|
||||
[poll_oneoff]: https://github.com/WebAssembly/wasi-poll#why-is-the-function-called-poll_oneoff
|
||||
[async-io-windows]: https://tinyclouds.org/iocp_links
|
||||
[peeknamedpipe]: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe
|
||||
[wsapoll]: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll
|
||||
|
||||
## Signed encoding of integer global constant initializers
|
||||
|
||||
@@ -1061,7 +1536,38 @@ If a module reaches this limit, an error is returned at the compilation phase.
|
||||
|
||||
## Compiler engine implementation
|
||||
|
||||
See [compiler/RATIONALE.md](internal/engine/compiler/RATIONALE.md).
|
||||
### Why it's safe to execute runtime-generated machine codes against async Goroutine preemption
|
||||
|
||||
Goroutine preemption is the mechanism of the Go runtime to switch goroutines contexts on an OS thread.
|
||||
There are two types of preemption: cooperative preemption and async preemption. The former happens, for example,
|
||||
when making a function call, and it is not an issue for our runtime-generated functions as they do not make
|
||||
direct function calls to Go-implemented functions. On the other hand, the latter, async preemption, can be problematic
|
||||
since it tries to interrupt the execution of Goroutine at any point of function, and manipulates CPU register states.
|
||||
|
||||
Fortunately, our runtime-generated machine codes do not need to take the async preemption into account.
|
||||
All the assembly codes are entered via the trampoline implemented as Go Assembler Function (e.g. [arch_amd64.s](./arch_amd64.s)),
|
||||
and as of Go 1.20, these assembler functions are considered as _unsafe_ for async preemption:
|
||||
- https://github.com/golang/go/blob/go1.20rc1/src/runtime/preempt.go#L406-L407
|
||||
- https://github.com/golang/go/blob/9f0234214473dfb785a5ad84a8fc62a6a395cbc3/src/runtime/traceback.go#L227
|
||||
|
||||
From the Go runtime point of view, the execution of runtime-generated machine codes is considered as a part of
|
||||
that trampoline function. Therefore, runtime-generated machine code is also correctly considered unsafe for async preemption.
|
||||
|
||||
## Why context cancellation is handled in Go code rather than native code
|
||||
|
||||
Since [wazero v1.0.0-pre.9](https://github.com/tetratelabs/wazero/releases/tag/v1.0.0-pre.9), the runtime
|
||||
supports integration with Go contexts to interrupt execution after a timeout, or in response to explicit cancellation.
|
||||
This support is internally implemented as a special opcode `builtinFunctionCheckExitCode` that triggers the execution of
|
||||
a Go function (`ModuleInstance.FailIfClosed`) that atomically checks a sentinel value at strategic points in the code
|
||||
(e.g. [within loops][checkexitcode_loop]).
|
||||
|
||||
[It _is indeed_ possible to check the sentinel value directly, without leaving the native world][native_check], thus sparing some cycles;
|
||||
however, because native code never preempts (see section above), this may lead to a state where the other goroutines
|
||||
never get the chance to run, and thus never get the chance to set the sentinel value; effectively preventing
|
||||
cancellation from taking place.
|
||||
|
||||
[checkexitcode_loop]: https://github.com/tetratelabs/wazero/blob/86444c67a37dbf9e693ae5b365901f64968d9025/internal/wazeroir/compiler.go#L467-L476
|
||||
[native_check]: https://github.com/tetratelabs/wazero/issues/1409
|
||||
|
||||
## Golang patterns
|
||||
|
||||
|
||||
+1
-4
@@ -41,8 +41,6 @@ during `Runtime.CompileModule`. This means your WebAssembly functions execute
|
||||
natively at runtime. Compiler is faster than Interpreter, often by order of
|
||||
magnitude (10x) or more. This is done without host-specific dependencies.
|
||||
|
||||
If interested, check out the [RATIONALE.md][8] and help us optimize further!
|
||||
|
||||
### Conformance
|
||||
|
||||
Both runtimes pass WebAssembly Core [1.0][7] and [2.0][14] specification tests
|
||||
@@ -55,7 +53,7 @@ on supported platforms:
|
||||
|
||||
## Support Policy
|
||||
|
||||
The below support policy focuses on compatability concerns of those embedding
|
||||
The below support policy focuses on compatibility concerns of those embedding
|
||||
wazero into their Go applications.
|
||||
|
||||
### wazero
|
||||
@@ -123,7 +121,6 @@ wazero is a registered trademark of Tetrate.io, Inc. in the United States and/or
|
||||
[5]: https://github.com/WebAssembly/WASI
|
||||
[6]: https://pkg.go.dev/golang.org/x/sys/unix
|
||||
[7]: https://github.com/WebAssembly/spec/tree/wg-1.0/test/core
|
||||
[8]: internal/engine/compiler/RATIONALE.md
|
||||
[9]: https://github.com/tetratelabs/wazero/issues/506
|
||||
[10]: https://go.dev/doc/devel/release
|
||||
[11]: https://github.com/actions/virtual-environments
|
||||
|
||||
+2
@@ -143,6 +143,8 @@ const (
|
||||
// Note: The instruction list is too long to enumerate in godoc.
|
||||
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md
|
||||
CoreFeatureSIMD
|
||||
|
||||
// Update experimental/features.go when adding elements here.
|
||||
)
|
||||
|
||||
// SetEnabled enables or disables the feature or group of features.
|
||||
|
||||
+29
-3
@@ -150,6 +150,10 @@ type Module interface {
|
||||
Memory() Memory
|
||||
|
||||
// ExportedFunction returns a function exported from this module or nil if it wasn't.
|
||||
//
|
||||
// Note: The default wazero.ModuleConfig attempts to invoke `_start`, which
|
||||
// in rare cases can close the module. When in doubt, check IsClosed prior
|
||||
// to invoking a function export after instantiation.
|
||||
ExportedFunction(name string) Function
|
||||
|
||||
// ExportedFunctionDefinitions returns all the exported function
|
||||
@@ -190,6 +194,21 @@ type Module interface {
|
||||
// Closer closes this module by delegating to CloseWithExitCode with an exit code of zero.
|
||||
Closer
|
||||
|
||||
// IsClosed returns true if the module is closed, so no longer usable.
|
||||
//
|
||||
// This can happen for the following reasons:
|
||||
// - Closer was called directly.
|
||||
// - A guest function called Closer indirectly, such as `_start` calling
|
||||
// `proc_exit`, which internally closed the module.
|
||||
// - wazero.RuntimeConfig `WithCloseOnContextDone` was enabled and a
|
||||
// context completion closed the module.
|
||||
//
|
||||
// Where any of the above are possible, check this value before calling an
|
||||
// ExportedFunction, even if you didn't formerly receive a sys.ExitError.
|
||||
// sys.ExitError is only returned on non-zero code, something that closes
|
||||
// the module successfully will not result it one.
|
||||
IsClosed() bool
|
||||
|
||||
internalapi.WazeroOnly
|
||||
}
|
||||
|
||||
@@ -355,6 +374,7 @@ type Function interface {
|
||||
// Call is not goroutine-safe, therefore it is recommended to create
|
||||
// another Function if you want to invoke the same function concurrently.
|
||||
// On the other hand, sequential invocations of Call is allowed.
|
||||
// However, this should not be called multiple times until the previous Call returns.
|
||||
//
|
||||
// To safely encode/decode params/results expressed as uint64, users are encouraged to
|
||||
// use api.EncodeXXX or DecodeXXX functions. See the docs on api.ValueType.
|
||||
@@ -539,8 +559,14 @@ type Memory interface {
|
||||
// Definition is metadata about this memory from its defining module.
|
||||
Definition() MemoryDefinition
|
||||
|
||||
// Size returns the size in bytes available. e.g. If the underlying memory
|
||||
// has 1 page: 65536
|
||||
// Size returns the memory size in bytes available.
|
||||
// e.g. If the underlying memory has 1 page: 65536
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This overflows (returns zero) if the memory has the maximum 65536 pages.
|
||||
// As a workaround until wazero v2 to fix the return type, use Grow(0) to obtain the current pages and
|
||||
// multiply by 65536.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-memorymathsfmemorysize%E2%91%A0
|
||||
Size() uint32
|
||||
@@ -552,7 +578,7 @@ type Memory interface {
|
||||
// # Notes
|
||||
//
|
||||
// - This is the same as the "memory.grow" instruction defined in the
|
||||
// WebAssembly Core Specification, except returns false instead of -1.
|
||||
// WebAssembly Core Specification, except returns false instead of -1.
|
||||
// - When this returns true, any shared views via Read must be refreshed.
|
||||
//
|
||||
// See MemorySizer Read and https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
|
||||
|
||||
+19
-13
@@ -11,10 +11,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/engine/compiler"
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/engine/interpreter"
|
||||
"github.com/tetratelabs/wazero/internal/engine/wazevo"
|
||||
"github.com/tetratelabs/wazero/internal/filecache"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/internal/internalapi"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
internalsock "github.com/tetratelabs/wazero/internal/sock"
|
||||
@@ -157,7 +157,7 @@ type RuntimeConfig interface {
|
||||
// This is especially useful when one wants to run untrusted Wasm binaries since otherwise, any invocation of
|
||||
// api.Function can potentially block the corresponding Goroutine forever. Moreover, it might block the
|
||||
// entire underlying OS thread which runs the api.Function call. See "Why it's safe to execute runtime-generated
|
||||
// machine codes against async Goroutine preemption" section in internal/engine/compiler/RATIONALE.md for detail.
|
||||
// machine codes against async Goroutine preemption" section in RATIONALE.md for detail.
|
||||
//
|
||||
// Note that this comes with a bit of extra cost when enabled. The reason is that internally this forces
|
||||
// interpreter and compiler runtimes to insert the periodical checks on the conditions above. For that reason,
|
||||
@@ -223,7 +223,7 @@ const (
|
||||
func NewRuntimeConfigCompiler() RuntimeConfig {
|
||||
ret := engineLessConfig.clone()
|
||||
ret.engineKind = engineKindCompiler
|
||||
ret.newEngine = compiler.NewEngine
|
||||
ret.newEngine = wazevo.NewEngine
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -490,12 +490,19 @@ type ModuleConfig interface {
|
||||
// WithStartFunctions configures the functions to call after the module is
|
||||
// instantiated. Defaults to "_start".
|
||||
//
|
||||
// Clearing the default is supported, via `WithStartFunctions()`.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - If any function doesn't exist, it is skipped. However, all functions
|
||||
// that do exist are called in order.
|
||||
// - Some start functions may exit the module during instantiate with a
|
||||
// sys.ExitError (e.g. emscripten), preventing use of exported functions.
|
||||
// - If a start function doesn't exist, it is skipped. However, any that
|
||||
// do exist are called in order.
|
||||
// - Start functions are not intended to be called multiple times.
|
||||
// Functions that should be called multiple times should be invoked
|
||||
// manually via api.Module's `ExportedFunction` method.
|
||||
// - Start functions commonly exit the module during instantiation,
|
||||
// preventing use of any functions later. This is the case in "wasip1",
|
||||
// which defines the default value "_start".
|
||||
// - See /RATIONALE.md for motivation of this feature.
|
||||
WithStartFunctions(...string) ModuleConfig
|
||||
|
||||
// WithStderr configures where standard error (file descriptor 2) is written. Defaults to io.Discard.
|
||||
@@ -839,11 +846,10 @@ func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) {
|
||||
environ = append(environ, result)
|
||||
}
|
||||
|
||||
var fs fsapi.FS
|
||||
var fs []experimentalsys.FS
|
||||
var guestPaths []string
|
||||
if f, ok := c.fsConfig.(*fsConfig); ok {
|
||||
if fs, err = f.toFS(); err != nil {
|
||||
return
|
||||
}
|
||||
fs, guestPaths = f.preopens()
|
||||
}
|
||||
|
||||
var listeners []*net.TCPListener
|
||||
@@ -864,7 +870,7 @@ func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) {
|
||||
c.walltime, c.walltimeResolution,
|
||||
c.nanotime, c.nanotimeResolution,
|
||||
c.nanosleep, c.osyield,
|
||||
fs,
|
||||
fs, guestPaths,
|
||||
listeners,
|
||||
)
|
||||
}
|
||||
BIN
Binary file not shown.
+48
@@ -0,0 +1,48 @@
|
||||
package experimental
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/expctxkeys"
|
||||
)
|
||||
|
||||
// Snapshot holds the execution state at the time of a Snapshotter.Snapshot call.
|
||||
type Snapshot interface {
|
||||
// Restore sets the Wasm execution state to the capture. Because a host function
|
||||
// calling this is resetting the pointer to the executation stack, the host function
|
||||
// will not be able to return values in the normal way. ret is a slice of values the
|
||||
// host function intends to return from the restored function.
|
||||
Restore(ret []uint64)
|
||||
}
|
||||
|
||||
// Snapshotter allows host functions to snapshot the WebAssembly execution environment.
|
||||
type Snapshotter interface {
|
||||
// Snapshot captures the current execution state.
|
||||
Snapshot() Snapshot
|
||||
}
|
||||
|
||||
// EnableSnapshotterKey is a context key to indicate that snapshotting should be enabled.
|
||||
// The context.Context passed to a exported function invocation should have this key set
|
||||
// to a non-nil value, and host functions will be able to retrieve it using SnapshotterKey.
|
||||
//
|
||||
// Deprecated: use WithSnapshotter to enable snapshots.
|
||||
type EnableSnapshotterKey = expctxkeys.EnableSnapshotterKey
|
||||
|
||||
// WithSnapshotter enables snapshots.
|
||||
// Passing the returned context to a exported function invocation enables snapshots,
|
||||
// and allows host functions to retrieve the Snapshotter using GetSnapshotter.
|
||||
func WithSnapshotter(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, expctxkeys.EnableSnapshotterKey{}, struct{}{})
|
||||
}
|
||||
|
||||
// SnapshotterKey is a context key to access a Snapshotter from a host function.
|
||||
// It is only present if EnableSnapshotter was set in the function invocation context.
|
||||
//
|
||||
// Deprecated: use GetSnapshotter to get the snapshotter.
|
||||
type SnapshotterKey = expctxkeys.SnapshotterKey
|
||||
|
||||
// GetSnapshotter gets the Snapshotter from a host function.
|
||||
// It is only present if WithSnapshotter was called with the function invocation context.
|
||||
func GetSnapshotter(ctx context.Context) Snapshotter {
|
||||
return ctx.Value(expctxkeys.SnapshotterKey{}).(Snapshotter)
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package experimental
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/expctxkeys"
|
||||
)
|
||||
|
||||
// CloseNotifier is a notification hook, invoked when a module is closed.
|
||||
//
|
||||
// Note: This is experimental progress towards #1197, and likely to change. Do
|
||||
// not expose this in shared libraries as it can cause version locks.
|
||||
type CloseNotifier interface {
|
||||
// CloseNotify is a notification that occurs *before* an api.Module is
|
||||
// closed. `exitCode` is zero on success or in the case there was no exit
|
||||
// code.
|
||||
//
|
||||
// Notes:
|
||||
// - This does not return an error because the module will be closed
|
||||
// unconditionally.
|
||||
// - Do not panic from this function as it doing so could cause resource
|
||||
// leaks.
|
||||
// - While this is only called once per module, if configured for
|
||||
// multiple modules, it will be called for each, e.g. on runtime close.
|
||||
CloseNotify(ctx context.Context, exitCode uint32)
|
||||
}
|
||||
|
||||
// ^-- Note: This might need to be a part of the listener or become a part of
|
||||
// host state implementation. For example, if this is used to implement state
|
||||
// cleanup for host modules, possibly something like below would be better, as
|
||||
// it could be implemented in a way that allows concurrent module use.
|
||||
//
|
||||
// // key is like a context key, stateFactory is invoked per instantiate and
|
||||
// // is associated with the key (exposed as `Module.State` similar to go
|
||||
// // context). Using a key is better than the module name because we can
|
||||
// // de-dupe it for host modules that can be instantiated into different
|
||||
// // names. Also, you can make the key package private.
|
||||
// HostModuleBuilder.WithState(key any, stateFactory func() Cleanup)`
|
||||
//
|
||||
// Such a design could work to isolate state only needed for wasip1, for
|
||||
// example the dirent cache. However, if end users use this for different
|
||||
// things, we may need separate designs.
|
||||
//
|
||||
// In summary, the purpose of this iteration is to identify projects that
|
||||
// would use something like this, and then we can figure out which way it
|
||||
// should go.
|
||||
|
||||
// CloseNotifyFunc is a convenience for defining inlining a CloseNotifier.
|
||||
type CloseNotifyFunc func(ctx context.Context, exitCode uint32)
|
||||
|
||||
// CloseNotify implements CloseNotifier.CloseNotify.
|
||||
func (f CloseNotifyFunc) CloseNotify(ctx context.Context, exitCode uint32) {
|
||||
f(ctx, exitCode)
|
||||
}
|
||||
|
||||
// WithCloseNotifier registers the given CloseNotifier into the given
|
||||
// context.Context.
|
||||
func WithCloseNotifier(ctx context.Context, notifier CloseNotifier) context.Context {
|
||||
if notifier != nil {
|
||||
return context.WithValue(ctx, expctxkeys.CloseNotifierKey{}, notifier)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package experimental
|
||||
|
||||
import "github.com/tetratelabs/wazero/api"
|
||||
|
||||
// CoreFeaturesThreads enables threads instructions ("threads").
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - The instruction list is too long to enumerate in godoc.
|
||||
// See https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md
|
||||
// - Atomic operations are guest-only until api.Memory or otherwise expose them to host functions.
|
||||
// - On systems without mmap available, the memory will pre-allocate to the maximum size. Many
|
||||
// binaries will use a theroetical maximum like 4GB, so if using such a binary on a system
|
||||
// without mmap, consider editing the binary to reduce the max size setting of memory.
|
||||
const CoreFeaturesThreads = api.CoreFeatureSIMD << 1
|
||||
+15
-46
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/expctxkeys"
|
||||
)
|
||||
|
||||
// StackIterator allows iterating on each function of the call stack, starting
|
||||
@@ -21,16 +22,19 @@ type StackIterator interface {
|
||||
// ProgramCounter returns the program counter associated with the
|
||||
// function call.
|
||||
ProgramCounter() ProgramCounter
|
||||
// Parameters returns api.ValueType-encoded parameters of the current
|
||||
// function. Do not modify the content of the slice, and copy out any
|
||||
// value you need.
|
||||
Parameters() []uint64
|
||||
}
|
||||
|
||||
// FunctionListenerFactoryKey is a context.Context Value key. Its associated value should be a FunctionListenerFactory.
|
||||
// FunctionListenerFactoryKey is a context.Context Value key.
|
||||
// Its associated value should be a FunctionListenerFactory.
|
||||
//
|
||||
// See https://github.com/tetratelabs/wazero/issues/451
|
||||
type FunctionListenerFactoryKey struct{}
|
||||
// Deprecated: use WithFunctionListenerFactory to enable snapshots.
|
||||
type FunctionListenerFactoryKey = expctxkeys.FunctionListenerFactoryKey
|
||||
|
||||
// WithFunctionListenerFactory registers a FunctionListenerFactory
|
||||
// with the context.
|
||||
func WithFunctionListenerFactory(ctx context.Context, factory FunctionListenerFactory) context.Context {
|
||||
return context.WithValue(ctx, expctxkeys.FunctionListenerFactoryKey{}, factory)
|
||||
}
|
||||
|
||||
// FunctionListenerFactory returns FunctionListeners to be notified when a
|
||||
// function is called.
|
||||
@@ -194,48 +198,21 @@ func (multi *multiFunctionListener) Abort(ctx context.Context, mod api.Module, d
|
||||
}
|
||||
}
|
||||
|
||||
type parameters struct {
|
||||
values []uint64
|
||||
limits []uint8
|
||||
}
|
||||
|
||||
func (ps *parameters) append(values []uint64) {
|
||||
ps.values = append(ps.values, values...)
|
||||
ps.limits = append(ps.limits, uint8(len(ps.values)))
|
||||
}
|
||||
|
||||
func (ps *parameters) clear() {
|
||||
ps.values = ps.values[:0]
|
||||
ps.limits = ps.limits[:0]
|
||||
}
|
||||
|
||||
func (ps *parameters) index(i int) []uint64 {
|
||||
j := uint8(0)
|
||||
k := ps.limits[i]
|
||||
if i > 0 {
|
||||
j = ps.limits[i-1]
|
||||
}
|
||||
return ps.values[j:k:k]
|
||||
}
|
||||
|
||||
type stackIterator struct {
|
||||
base StackIterator
|
||||
index int
|
||||
pcs []uint64
|
||||
fns []InternalFunction
|
||||
params parameters
|
||||
base StackIterator
|
||||
index int
|
||||
pcs []uint64
|
||||
fns []InternalFunction
|
||||
}
|
||||
|
||||
func (si *stackIterator) Next() bool {
|
||||
if si.base != nil {
|
||||
si.pcs = si.pcs[:0]
|
||||
si.fns = si.fns[:0]
|
||||
si.params.clear()
|
||||
|
||||
for si.base.Next() {
|
||||
si.pcs = append(si.pcs, uint64(si.base.ProgramCounter()))
|
||||
si.fns = append(si.fns, si.base.Function())
|
||||
si.params.append(si.base.Parameters())
|
||||
}
|
||||
|
||||
si.base = nil
|
||||
@@ -252,10 +229,6 @@ func (si *stackIterator) Function() InternalFunction {
|
||||
return si.fns[si.index]
|
||||
}
|
||||
|
||||
func (si *stackIterator) Parameters() []uint64 {
|
||||
return si.params.index(si.index)
|
||||
}
|
||||
|
||||
// StackFrame represents a frame on the call stack.
|
||||
type StackFrame struct {
|
||||
Function api.Function
|
||||
@@ -302,10 +275,6 @@ func (si *stackFrameIterator) ProgramCounter() ProgramCounter {
|
||||
return ProgramCounter(si.stack[si.index].PC)
|
||||
}
|
||||
|
||||
func (si *stackFrameIterator) Parameters() []uint64 {
|
||||
return si.stack[si.index].Params
|
||||
}
|
||||
|
||||
// NewStackIterator constructs a stack iterator from a list of stack frames.
|
||||
// The top most frame is the last one.
|
||||
func NewStackIterator(stack ...StackFrame) StackIterator {
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package experimental
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/expctxkeys"
|
||||
)
|
||||
|
||||
// MemoryAllocator is a memory allocation hook,
|
||||
// invoked to create a LinearMemory.
|
||||
type MemoryAllocator interface {
|
||||
// Allocate should create a new LinearMemory with the given specification:
|
||||
// cap is the suggested initial capacity for the backing []byte,
|
||||
// and max the maximum length that will ever be requested.
|
||||
//
|
||||
// Notes:
|
||||
// - To back a shared memory, the address of the backing []byte cannot
|
||||
// change. This is checked at runtime. Implementations should document
|
||||
// if the returned LinearMemory meets this requirement.
|
||||
Allocate(cap, max uint64) LinearMemory
|
||||
}
|
||||
|
||||
// MemoryAllocatorFunc is a convenience for defining inlining a MemoryAllocator.
|
||||
type MemoryAllocatorFunc func(cap, max uint64) LinearMemory
|
||||
|
||||
// Allocate implements MemoryAllocator.Allocate.
|
||||
func (f MemoryAllocatorFunc) Allocate(cap, max uint64) LinearMemory {
|
||||
return f(cap, max)
|
||||
}
|
||||
|
||||
// LinearMemory is an expandable []byte that backs a Wasm linear memory.
|
||||
type LinearMemory interface {
|
||||
// Reallocates the linear memory to size bytes in length.
|
||||
//
|
||||
// Notes:
|
||||
// - To back a shared memory, Reallocate can't change the address of the
|
||||
// backing []byte (only its length/capacity may change).
|
||||
Reallocate(size uint64) []byte
|
||||
// Free the backing memory buffer.
|
||||
Free()
|
||||
}
|
||||
|
||||
// WithMemoryAllocator registers the given MemoryAllocator into the given
|
||||
// context.Context.
|
||||
func WithMemoryAllocator(ctx context.Context, allocator MemoryAllocator) context.Context {
|
||||
if allocator != nil {
|
||||
return context.WithValue(ctx, expctxkeys.MemoryAllocatorKey{}, allocator)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
Generated
Vendored
+15
-32
@@ -1,11 +1,9 @@
|
||||
package fsapi
|
||||
package sys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"time"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
@@ -22,7 +20,7 @@ type FileType = fs.FileMode
|
||||
// - This extends `dirent` defined in POSIX with some fields defined by
|
||||
// Linux. See https://man7.org/linux/man-pages/man3/readdir.3.html and
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/dirent.h.html
|
||||
// - This has a subset of fields defined in Stat_t. Notably, there is no
|
||||
// - This has a subset of fields defined in sys.Stat_t. Notably, there is no
|
||||
// field corresponding to Stat_t.Dev because that value will be constant
|
||||
// for all files in a directory. To get the Dev value, call File.Stat on
|
||||
// the directory File.Readdir was called on.
|
||||
@@ -59,51 +57,36 @@ func (DirFile) IsAppend() bool {
|
||||
}
|
||||
|
||||
// SetAppend implements File.SetAppend
|
||||
func (DirFile) SetAppend(bool) experimentalsys.Errno {
|
||||
return experimentalsys.EISDIR
|
||||
}
|
||||
|
||||
// IsNonblock implements File.IsNonblock
|
||||
func (DirFile) IsNonblock() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetNonblock implements File.SetNonblock
|
||||
func (DirFile) SetNonblock(bool) experimentalsys.Errno {
|
||||
return experimentalsys.EISDIR
|
||||
func (DirFile) SetAppend(bool) Errno {
|
||||
return EISDIR
|
||||
}
|
||||
|
||||
// IsDir implements File.IsDir
|
||||
func (DirFile) IsDir() (bool, experimentalsys.Errno) {
|
||||
func (DirFile) IsDir() (bool, Errno) {
|
||||
return true, 0
|
||||
}
|
||||
|
||||
// Read implements File.Read
|
||||
func (DirFile) Read([]byte) (int, experimentalsys.Errno) {
|
||||
return 0, experimentalsys.EISDIR
|
||||
func (DirFile) Read([]byte) (int, Errno) {
|
||||
return 0, EISDIR
|
||||
}
|
||||
|
||||
// Pread implements File.Pread
|
||||
func (DirFile) Pread([]byte, int64) (int, experimentalsys.Errno) {
|
||||
return 0, experimentalsys.EISDIR
|
||||
}
|
||||
|
||||
// PollRead implements File.PollRead
|
||||
func (DirFile) PollRead(*time.Duration) (ready bool, errno experimentalsys.Errno) {
|
||||
return false, experimentalsys.ENOSYS
|
||||
func (DirFile) Pread([]byte, int64) (int, Errno) {
|
||||
return 0, EISDIR
|
||||
}
|
||||
|
||||
// Write implements File.Write
|
||||
func (DirFile) Write([]byte) (int, experimentalsys.Errno) {
|
||||
return 0, experimentalsys.EISDIR
|
||||
func (DirFile) Write([]byte) (int, Errno) {
|
||||
return 0, EISDIR
|
||||
}
|
||||
|
||||
// Pwrite implements File.Pwrite
|
||||
func (DirFile) Pwrite([]byte, int64) (int, experimentalsys.Errno) {
|
||||
return 0, experimentalsys.EISDIR
|
||||
func (DirFile) Pwrite([]byte, int64) (int, Errno) {
|
||||
return 0, EISDIR
|
||||
}
|
||||
|
||||
// Truncate implements File.Truncate
|
||||
func (DirFile) Truncate(int64) experimentalsys.Errno {
|
||||
return experimentalsys.EISDIR
|
||||
func (DirFile) Truncate(int64) Errno {
|
||||
return EISDIR
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package sys
|
||||
|
||||
import "strconv"
|
||||
|
||||
// Errno is a subset of POSIX errno used by wazero interfaces. Zero is not an
|
||||
// error. Other values should not be interpreted numerically, rather by constants
|
||||
// prefixed with 'E'.
|
||||
//
|
||||
// See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html
|
||||
type Errno uint16
|
||||
|
||||
// ^-- Note: This will eventually move to the public /sys package. It is
|
||||
// experimental until we audit the socket related APIs to ensure we have all
|
||||
// the Errno it returns, and we export fs.FS. This is not in /internal/sys as
|
||||
// that would introduce a package cycle.
|
||||
|
||||
// This is a subset of errors to reduce implementation burden. `wasip1` defines
|
||||
// almost all POSIX error numbers, but not all are used in practice. wazero
|
||||
// will add ones needed in POSIX order, as needed by functions that explicitly
|
||||
// document returning them.
|
||||
//
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-errno-enumu16
|
||||
const (
|
||||
EACCES Errno = iota + 1
|
||||
EAGAIN
|
||||
EBADF
|
||||
EEXIST
|
||||
EFAULT
|
||||
EINTR
|
||||
EINVAL
|
||||
EIO
|
||||
EISDIR
|
||||
ELOOP
|
||||
ENAMETOOLONG
|
||||
ENOENT
|
||||
ENOSYS
|
||||
ENOTDIR
|
||||
ERANGE
|
||||
ENOTEMPTY
|
||||
ENOTSOCK
|
||||
ENOTSUP
|
||||
EPERM
|
||||
EROFS
|
||||
|
||||
// NOTE ENOTCAPABLE is defined in wasip1, but not in POSIX. wasi-libc
|
||||
// converts it to EBADF, ESPIPE or EINVAL depending on the call site.
|
||||
// It isn't known if compilers who don't use ENOTCAPABLE would crash on it.
|
||||
)
|
||||
|
||||
// Error implements error
|
||||
func (e Errno) Error() string {
|
||||
switch e {
|
||||
case 0: // not an error
|
||||
return "success"
|
||||
case EACCES:
|
||||
return "permission denied"
|
||||
case EAGAIN:
|
||||
return "resource unavailable, try again"
|
||||
case EBADF:
|
||||
return "bad file descriptor"
|
||||
case EEXIST:
|
||||
return "file exists"
|
||||
case EFAULT:
|
||||
return "bad address"
|
||||
case EINTR:
|
||||
return "interrupted function"
|
||||
case EINVAL:
|
||||
return "invalid argument"
|
||||
case EIO:
|
||||
return "input/output error"
|
||||
case EISDIR:
|
||||
return "is a directory"
|
||||
case ELOOP:
|
||||
return "too many levels of symbolic links"
|
||||
case ENAMETOOLONG:
|
||||
return "filename too long"
|
||||
case ENOENT:
|
||||
return "no such file or directory"
|
||||
case ENOSYS:
|
||||
return "functionality not supported"
|
||||
case ENOTDIR:
|
||||
return "not a directory or a symbolic link to a directory"
|
||||
case ERANGE:
|
||||
return "result too large"
|
||||
case ENOTEMPTY:
|
||||
return "directory not empty"
|
||||
case ENOTSOCK:
|
||||
return "not a socket"
|
||||
case ENOTSUP:
|
||||
return "not supported (may be the same value as [EOPNOTSUPP])"
|
||||
case EPERM:
|
||||
return "operation not permitted"
|
||||
case EROFS:
|
||||
return "read-only file system"
|
||||
default:
|
||||
return "Errno(" + strconv.Itoa(int(e)) + ")"
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+10
-17
@@ -1,39 +1,32 @@
|
||||
package platform
|
||||
package sys
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// UnwrapOSError returns a syscall.Errno or zero if the input is nil.
|
||||
func UnwrapOSError(err error) syscall.Errno {
|
||||
// UnwrapOSError returns an Errno or zero if the input is nil.
|
||||
func UnwrapOSError(err error) Errno {
|
||||
if err == nil {
|
||||
return 0
|
||||
}
|
||||
err = underlyingError(err)
|
||||
if se, ok := err.(syscall.Errno); ok {
|
||||
return adjustErrno(se)
|
||||
}
|
||||
// Below are all the fs.ErrXXX in fs.go.
|
||||
//
|
||||
// Note: Once we have our own file type, we should never see these.
|
||||
switch err {
|
||||
case nil, io.EOF:
|
||||
return 0 // EOF is not a syscall.Errno
|
||||
return 0 // EOF is not a Errno
|
||||
case fs.ErrInvalid:
|
||||
return syscall.EINVAL
|
||||
return EINVAL
|
||||
case fs.ErrPermission:
|
||||
return syscall.EPERM
|
||||
return EPERM
|
||||
case fs.ErrExist:
|
||||
return syscall.EEXIST
|
||||
return EEXIST
|
||||
case fs.ErrNotExist:
|
||||
return syscall.ENOENT
|
||||
return ENOENT
|
||||
case fs.ErrClosed:
|
||||
return syscall.EBADF
|
||||
return EBADF
|
||||
}
|
||||
return syscall.EIO
|
||||
return errorToErrno(err)
|
||||
}
|
||||
|
||||
// underlyingError returns the underlying error if a well-known OS error type.
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
package sys
|
||||
|
||||
import "github.com/tetratelabs/wazero/sys"
|
||||
|
||||
// File is a writeable fs.File bridge backed by syscall functions needed for ABI
|
||||
// including WASI and runtime.GOOS=js.
|
||||
//
|
||||
// Implementations should embed UnimplementedFile for forward compatibility. Any
|
||||
// unsupported method or parameter should return ENOSYS.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// All methods that can return an error return a Errno, which is zero
|
||||
// on success.
|
||||
//
|
||||
// Restricting to Errno matches current WebAssembly host functions,
|
||||
// which are constrained to well-known error codes. For example, `GOOS=js` maps
|
||||
// hard coded values and panics otherwise. More commonly, WASI maps syscall
|
||||
// errors to u32 numeric values.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - You must call Close to avoid file resource conflicts. For example,
|
||||
// Windows cannot delete the underlying directory while a handle to it
|
||||
// remains open.
|
||||
// - A writable filesystem abstraction is not yet implemented as of Go 1.20.
|
||||
// See https://github.com/golang/go/issues/45757
|
||||
type File interface {
|
||||
// Dev returns the device ID (Stat_t.Dev) of this file, zero if unknown or
|
||||
// an error retrieving it.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// Possible errors are those from Stat, except ENOSYS should not
|
||||
// be returned. Zero should be returned if there is no implementation.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - Implementations should cache this result.
|
||||
// - This combined with Ino can implement os.SameFile.
|
||||
Dev() (uint64, Errno)
|
||||
|
||||
// Ino returns the serial number (Stat_t.Ino) of this file, zero if unknown
|
||||
// or an error retrieving it.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// Possible errors are those from Stat, except ENOSYS should not
|
||||
// be returned. Zero should be returned if there is no implementation.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - Implementations should cache this result.
|
||||
// - This combined with Dev can implement os.SameFile.
|
||||
Ino() (sys.Inode, Errno)
|
||||
|
||||
// IsDir returns true if this file is a directory or an error there was an
|
||||
// error retrieving this information.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// Possible errors are those from Stat, except ENOSYS should not
|
||||
// be returned. false should be returned if there is no implementation.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - Implementations should cache this result.
|
||||
IsDir() (bool, Errno)
|
||||
|
||||
// IsAppend returns true if the file was opened with O_APPEND, or
|
||||
// SetAppend was successfully enabled on this file.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This might not match the underlying state of the file descriptor if
|
||||
// the file was not opened via OpenFile.
|
||||
IsAppend() bool
|
||||
|
||||
// SetAppend toggles the append mode (O_APPEND) of this file.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - There is no `O_APPEND` for `fcntl` in POSIX, so implementations may
|
||||
// have to re-open the underlying file to apply this. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
|
||||
SetAppend(enable bool) Errno
|
||||
|
||||
// Stat is similar to syscall.Fstat.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Fstat and `fstatat` with `AT_FDCWD` in POSIX.
|
||||
// See https://pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html
|
||||
// - A fs.FileInfo backed implementation sets atim, mtim and ctim to the
|
||||
// same value.
|
||||
// - Windows allows you to stat a closed directory.
|
||||
Stat() (sys.Stat_t, Errno)
|
||||
|
||||
// Read attempts to read all bytes in the file into `buf`, and returns the
|
||||
// count read even on error.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed or not readable.
|
||||
// - EISDIR: the file was a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like io.Reader and `read` in POSIX, preferring semantics of
|
||||
// io.Reader. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html
|
||||
// - Unlike io.Reader, there is no io.EOF returned on end-of-file. To
|
||||
// read the file completely, the caller must repeat until `n` is zero.
|
||||
Read(buf []byte) (n int, errno Errno)
|
||||
|
||||
// Pread attempts to read all bytes in the file into `p`, starting at the
|
||||
// offset `off`, and returns the count read even on error.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed or not readable.
|
||||
// - EINVAL: the offset was negative.
|
||||
// - EISDIR: the file was a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like io.ReaderAt and `pread` in POSIX, preferring semantics
|
||||
// of io.ReaderAt. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html
|
||||
// - Unlike io.ReaderAt, there is no io.EOF returned on end-of-file. To
|
||||
// read the file completely, the caller must repeat until `n` is zero.
|
||||
Pread(buf []byte, off int64) (n int, errno Errno)
|
||||
|
||||
// Seek attempts to set the next offset for Read or Write and returns the
|
||||
// resulting absolute offset or an error.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// The `offset` parameters is interpreted in terms of `whence`:
|
||||
// - io.SeekStart: relative to the start of the file, e.g. offset=0 sets
|
||||
// the next Read or Write to the beginning of the file.
|
||||
// - io.SeekCurrent: relative to the current offset, e.g. offset=16 sets
|
||||
// the next Read or Write 16 bytes past the prior.
|
||||
// - io.SeekEnd: relative to the end of the file, e.g. offset=-1 sets the
|
||||
// next Read or Write to the last byte in the file.
|
||||
//
|
||||
// # Behavior when a directory
|
||||
//
|
||||
// The only supported use case for a directory is seeking to `offset` zero
|
||||
// (`whence` = io.SeekStart). This should have the same behavior as
|
||||
// os.File, which resets any internal state used by Readdir.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed or not readable.
|
||||
// - EINVAL: the offset was negative.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like io.Seeker and `fseek` in POSIX, preferring semantics
|
||||
// of io.Seeker. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fseek.html
|
||||
Seek(offset int64, whence int) (newOffset int64, errno Errno)
|
||||
|
||||
// Readdir reads the contents of the directory associated with file and
|
||||
// returns a slice of up to n Dirent values in an arbitrary order. This is
|
||||
// a stateful function, so subsequent calls return any next values.
|
||||
//
|
||||
// If n > 0, Readdir returns at most n entries or an error.
|
||||
// If n <= 0, Readdir returns all remaining entries or an error.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file was closed or not a directory.
|
||||
// - ENOENT: the directory could not be read (e.g. deleted).
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like `Readdir` on os.File, but unlike `readdir` in POSIX.
|
||||
// See https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html
|
||||
// - Unlike os.File, there is no io.EOF returned on end-of-directory. To
|
||||
// read the directory completely, the caller must repeat until the
|
||||
// count read (`len(dirents)`) is less than `n`.
|
||||
// - See /RATIONALE.md for design notes.
|
||||
Readdir(n int) (dirents []Dirent, errno Errno)
|
||||
|
||||
// Write attempts to write all bytes in `p` to the file, and returns the
|
||||
// count written even on error.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file was closed, not writeable, or a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like io.Writer and `write` in POSIX, preferring semantics of
|
||||
// io.Writer. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html
|
||||
Write(buf []byte) (n int, errno Errno)
|
||||
|
||||
// Pwrite attempts to write all bytes in `p` to the file at the given
|
||||
// offset `off`, and returns the count written even on error.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed or not writeable.
|
||||
// - EINVAL: the offset was negative.
|
||||
// - EISDIR: the file was a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like io.WriterAt and `pwrite` in POSIX, preferring semantics
|
||||
// of io.WriterAt. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/pwrite.html
|
||||
Pwrite(buf []byte, off int64) (n int, errno Errno)
|
||||
|
||||
// Truncate truncates a file to a specified length.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed.
|
||||
// - EINVAL: the `size` is negative.
|
||||
// - EISDIR: the file was a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Ftruncate and `ftruncate` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html
|
||||
// - Windows does not error when calling Truncate on a closed file.
|
||||
Truncate(size int64) Errno
|
||||
|
||||
// Sync synchronizes changes to the file.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - EBADF: the file or directory was closed.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Fsync and `fsync` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fsync.html
|
||||
// - This returns with no error instead of ENOSYS when
|
||||
// unimplemented. This prevents fake filesystems from erring.
|
||||
// - Windows does not error when calling Sync on a closed file.
|
||||
Sync() Errno
|
||||
|
||||
// Datasync synchronizes the data of a file.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - EBADF: the file or directory was closed.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Fdatasync and `fdatasync` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdatasync.html
|
||||
// - This returns with no error instead of ENOSYS when
|
||||
// unimplemented. This prevents fake filesystems from erring.
|
||||
// - As this is commonly missing, some implementations dispatch to Sync.
|
||||
Datasync() Errno
|
||||
|
||||
// Utimens set file access and modification times of this file, at
|
||||
// nanosecond precision.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// The `atim` and `mtim` parameters refer to access and modification time
|
||||
// stamps as defined in sys.Stat_t. To retain one or the other, substitute
|
||||
// it with the pseudo-timestamp UTIME_OMIT.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.UtimesNano and `futimens` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
|
||||
// - Windows requires files to be open with O_RDWR, which means you
|
||||
// cannot use this to update timestamps on a directory (EPERM).
|
||||
Utimens(atim, mtim int64) Errno
|
||||
|
||||
// Close closes the underlying file.
|
||||
//
|
||||
// A zero Errno is returned if unimplemented or success.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Close and `close` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html
|
||||
Close() Errno
|
||||
}
|
||||
Generated
Vendored
+65
-68
@@ -1,17 +1,15 @@
|
||||
package fsapi
|
||||
package sys
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"syscall"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
// FS is a writeable fs.FS bridge backed by syscall functions needed for ABI
|
||||
// including WASI and runtime.GOOS=js.
|
||||
//
|
||||
// Implementations should embed UnimplementedFS for forward compatability. Any
|
||||
// Implementations should embed UnimplementedFS for forward compatibility. Any
|
||||
// unsupported method or parameter should return ENO
|
||||
//
|
||||
// # Errors
|
||||
@@ -34,11 +32,11 @@ type FS interface {
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` or `flag` is invalid.
|
||||
// - sys.EISDIR: the path was a directory, but flag included O_RDWR or
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EINVAL: `path` or `flag` is invalid.
|
||||
// - EISDIR: the path was a directory, but flag included O_RDWR or
|
||||
// O_WRONLY
|
||||
// - sys.ENOENT: `path` doesn't exist and `flag` doesn't contain O_CREAT.
|
||||
// - ENOENT: `path` doesn't exist and `flag` doesn't contain O_CREAT.
|
||||
//
|
||||
// # Constraints on the returned file
|
||||
//
|
||||
@@ -59,15 +57,15 @@ type FS interface {
|
||||
// - Implications of permissions when O_CREAT are described in Chmod notes.
|
||||
// - This is like `open` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
|
||||
OpenFile(path string, flag Oflag, perm fs.FileMode) (File, experimentalsys.Errno)
|
||||
OpenFile(path string, flag Oflag, perm fs.FileMode) (File, Errno)
|
||||
|
||||
// Lstat gets file status without following symbolic links.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.ENOENT: `path` doesn't exist.
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - ENOENT: `path` doesn't exist.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -79,15 +77,15 @@ type FS interface {
|
||||
// same value.
|
||||
// - When the path is a symbolic link, the stat returned is for the link,
|
||||
// not the file it refers to.
|
||||
Lstat(path string) (sys.Stat_t, experimentalsys.Errno)
|
||||
Lstat(path string) (sys.Stat_t, Errno)
|
||||
|
||||
// Stat gets file status.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.ENOENT: `path` doesn't exist.
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - ENOENT: `path` doesn't exist.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -99,17 +97,17 @@ type FS interface {
|
||||
// same value.
|
||||
// - When the path is a symbolic link, the stat returned is for the file
|
||||
// it refers to.
|
||||
Stat(path string) (sys.Stat_t, experimentalsys.Errno)
|
||||
Stat(path string) (sys.Stat_t, Errno)
|
||||
|
||||
// Mkdir makes a directory.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` is invalid.
|
||||
// - sys.EEXIST: `path` exists and is a directory.
|
||||
// - sys.ENOTDIR: `path` exists and is a file.
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EINVAL: `path` is invalid.
|
||||
// - EEXIST: `path` exists and is a directory.
|
||||
// - ENOTDIR: `path` exists and is a file.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -118,16 +116,16 @@ type FS interface {
|
||||
// - This is like `mkdir` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdir.html
|
||||
// - Implications of permissions are described in Chmod notes.
|
||||
Mkdir(path string, perm fs.FileMode) experimentalsys.Errno
|
||||
Mkdir(path string, perm fs.FileMode) Errno
|
||||
|
||||
// Chmod changes the mode of the file.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` is invalid.
|
||||
// - sys.ENOENT: `path` does not exist.
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EINVAL: `path` is invalid.
|
||||
// - ENOENT: `path` does not exist.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -138,19 +136,19 @@ type FS interface {
|
||||
// - Windows ignores the execute bit, and any permissions come back as
|
||||
// group and world. For example, chmod of 0400 reads back as 0444, and
|
||||
// 0700 0666. Also, permissions on directories aren't supported at all.
|
||||
Chmod(path string, perm fs.FileMode) experimentalsys.Errno
|
||||
Chmod(path string, perm fs.FileMode) Errno
|
||||
|
||||
// Rename renames file or directory.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `from` or `to` is invalid.
|
||||
// - sys.ENOENT: `from` or `to` don't exist.
|
||||
// - sys.ENOTDIR: `from` is a directory and `to` exists as a file.
|
||||
// - sys.EISDIR: `from` is a file and `to` exists as a directory.
|
||||
// - sys.ENOTEMPTY: `both from` and `to` are existing directory, but
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EINVAL: `from` or `to` is invalid.
|
||||
// - ENOENT: `from` or `to` don't exist.
|
||||
// - ENOTDIR: `from` is a directory and `to` exists as a file.
|
||||
// - EISDIR: `from` is a file and `to` exists as a directory.
|
||||
// - ENOTEMPTY: `both from` and `to` are existing directory, but
|
||||
// `to` is not empty.
|
||||
//
|
||||
// # Notes
|
||||
@@ -160,18 +158,18 @@ type FS interface {
|
||||
// - This is like `rename` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html
|
||||
// - Windows doesn't let you overwrite an existing directory.
|
||||
Rename(from, to string) experimentalsys.Errno
|
||||
Rename(from, to string) Errno
|
||||
|
||||
// Rmdir removes a directory.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` is invalid.
|
||||
// - sys.ENOENT: `path` doesn't exist.
|
||||
// - sys.ENOTDIR: `path` exists, but isn't a directory.
|
||||
// - sys.ENOTEMPTY: `path` exists, but isn't empty.
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EINVAL: `path` is invalid.
|
||||
// - ENOENT: `path` doesn't exist.
|
||||
// - ENOTDIR: `path` exists, but isn't a directory.
|
||||
// - ENOTEMPTY: `path` exists, but isn't empty.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -179,18 +177,18 @@ type FS interface {
|
||||
// file system.
|
||||
// - This is like `rmdir` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html
|
||||
// - As of Go 1.19, Windows maps sys.ENOTDIR to sys.ENOENT.
|
||||
Rmdir(path string) experimentalsys.Errno
|
||||
// - As of Go 1.19, Windows maps ENOTDIR to ENOENT.
|
||||
Rmdir(path string) Errno
|
||||
|
||||
// Unlink removes a directory entry.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` is invalid.
|
||||
// - sys.ENOENT: `path` doesn't exist.
|
||||
// - sys.EISDIR: `path` exists, but is a directory.
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EINVAL: `path` is invalid.
|
||||
// - ENOENT: `path` doesn't exist.
|
||||
// - EISDIR: `path` exists, but is a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -201,7 +199,7 @@ type FS interface {
|
||||
// - On Windows, syscall.Unlink doesn't delete symlink to directory unlike other platforms. Implementations might
|
||||
// want to combine syscall.RemoveDirectory with syscall.Unlink in order to delete such links on Windows.
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-removedirectorya
|
||||
Unlink(path string) experimentalsys.Errno
|
||||
Unlink(path string) Errno
|
||||
|
||||
// Link creates a "hard" link from oldPath to newPath, in contrast to a
|
||||
// soft link (via Symlink).
|
||||
@@ -209,10 +207,10 @@ type FS interface {
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EPERM: `oldPath` is invalid.
|
||||
// - sys.ENOENT: `oldPath` doesn't exist.
|
||||
// - sys.EISDIR: `newPath` exists, but is a directory.
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EPERM: `oldPath` is invalid.
|
||||
// - ENOENT: `oldPath` doesn't exist.
|
||||
// - EISDIR: `newPath` exists, but is a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -220,7 +218,7 @@ type FS interface {
|
||||
// file system.
|
||||
// - This is like `link` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html
|
||||
Link(oldPath, newPath string) experimentalsys.Errno
|
||||
Link(oldPath, newPath string) Errno
|
||||
|
||||
// Symlink creates a "soft" link from oldPath to newPath, in contrast to a
|
||||
// hard link (via Link).
|
||||
@@ -228,9 +226,9 @@ type FS interface {
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EPERM: `oldPath` or `newPath` is invalid.
|
||||
// - sys.EEXIST: `newPath` exists.
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EPERM: `oldPath` or `newPath` is invalid.
|
||||
// - EEXIST: `newPath` exists.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -244,17 +242,17 @@ type FS interface {
|
||||
// See https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409
|
||||
// for how others implement this.
|
||||
// - Symlinks in Windows requires `SeCreateSymbolicLinkPrivilege`.
|
||||
// Otherwise, sys.EPERM results.
|
||||
// Otherwise, EPERM results.
|
||||
// See https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links
|
||||
Symlink(oldPath, linkName string) experimentalsys.Errno
|
||||
Symlink(oldPath, linkName string) Errno
|
||||
|
||||
// Readlink reads the contents of a symbolic link.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` is invalid.
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EINVAL: `path` is invalid.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -265,32 +263,31 @@ type FS interface {
|
||||
// - On Windows, the path separator is different from other platforms,
|
||||
// but to provide consistent results to Wasm, this normalizes to a "/"
|
||||
// separator.
|
||||
Readlink(path string) (string, experimentalsys.Errno)
|
||||
Readlink(path string) (string, Errno)
|
||||
|
||||
// Utimens set file access and modification times on a path relative to
|
||||
// this file system, at nanosecond precision.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// The `times` parameter includes the access and modification timestamps to
|
||||
// assign. Special syscall.Timespec NSec values UTIME_NOW and UTIME_OMIT
|
||||
// may be specified instead of real timestamps. A nil `times` parameter
|
||||
// behaves the same as if both were set to UTIME_NOW. If the path is a
|
||||
// symbolic link, the target of expanding that link is updated.
|
||||
// If the path is a symbolic link, the target of expanding that link is
|
||||
// updated.
|
||||
//
|
||||
// The `atim` and `mtim` parameters refer to access and modification time
|
||||
// stamps as defined in sys.Stat_t. To retain one or the other, substitute
|
||||
// it with the pseudo-timestamp UTIME_OMIT.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` is invalid.
|
||||
// - sys.EEXIST: `path` exists and is a directory.
|
||||
// - sys.ENOTDIR: `path` exists and is a file.
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EINVAL: `path` is invalid.
|
||||
// - EEXIST: `path` exists and is a directory.
|
||||
// - ENOTDIR: `path` exists and is a file.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.UtimesNano and `utimensat` with `AT_FDCWD` in
|
||||
// POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
|
||||
Utimens(path string, times *[2]syscall.Timespec) experimentalsys.Errno
|
||||
// TODO: change impl to not use syscall package,
|
||||
// possibly by being just a pair of int64s..
|
||||
Utimens(path string, atim, mtim int64) Errno
|
||||
}
|
||||
Generated
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
package fsapi
|
||||
package sys
|
||||
|
||||
// Oflag are flags used for FS.OpenFile. Values, including zero, should not be
|
||||
// interpreted numerically. Instead, use by constants prefixed with 'O_' with
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
//go:build !plan9 && !aix
|
||||
|
||||
package sys
|
||||
|
||||
import "syscall"
|
||||
|
||||
func syscallToErrno(err error) (Errno, bool) {
|
||||
errno, ok := err.(syscall.Errno)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
switch errno {
|
||||
case 0:
|
||||
return 0, true
|
||||
case syscall.EACCES:
|
||||
return EACCES, true
|
||||
case syscall.EAGAIN:
|
||||
return EAGAIN, true
|
||||
case syscall.EBADF:
|
||||
return EBADF, true
|
||||
case syscall.EEXIST:
|
||||
return EEXIST, true
|
||||
case syscall.EFAULT:
|
||||
return EFAULT, true
|
||||
case syscall.EINTR:
|
||||
return EINTR, true
|
||||
case syscall.EINVAL:
|
||||
return EINVAL, true
|
||||
case syscall.EIO:
|
||||
return EIO, true
|
||||
case syscall.EISDIR:
|
||||
return EISDIR, true
|
||||
case syscall.ELOOP:
|
||||
return ELOOP, true
|
||||
case syscall.ENAMETOOLONG:
|
||||
return ENAMETOOLONG, true
|
||||
case syscall.ENOENT:
|
||||
return ENOENT, true
|
||||
case syscall.ENOSYS:
|
||||
return ENOSYS, true
|
||||
case syscall.ENOTDIR:
|
||||
return ENOTDIR, true
|
||||
case syscall.ERANGE:
|
||||
return ERANGE, true
|
||||
case syscall.ENOTEMPTY:
|
||||
return ENOTEMPTY, true
|
||||
case syscall.ENOTSOCK:
|
||||
return ENOTSOCK, true
|
||||
case syscall.ENOTSUP:
|
||||
return ENOTSUP, true
|
||||
case syscall.EPERM:
|
||||
return EPERM, true
|
||||
case syscall.EROFS:
|
||||
return EROFS, true
|
||||
default:
|
||||
return EIO, true
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap is a convenience for runtime.GOOS which define syscall.Errno.
|
||||
func (e Errno) Unwrap() error {
|
||||
switch e {
|
||||
case 0:
|
||||
return nil
|
||||
case EACCES:
|
||||
return syscall.EACCES
|
||||
case EAGAIN:
|
||||
return syscall.EAGAIN
|
||||
case EBADF:
|
||||
return syscall.EBADF
|
||||
case EEXIST:
|
||||
return syscall.EEXIST
|
||||
case EFAULT:
|
||||
return syscall.EFAULT
|
||||
case EINTR:
|
||||
return syscall.EINTR
|
||||
case EINVAL:
|
||||
return syscall.EINVAL
|
||||
case EIO:
|
||||
return syscall.EIO
|
||||
case EISDIR:
|
||||
return syscall.EISDIR
|
||||
case ELOOP:
|
||||
return syscall.ELOOP
|
||||
case ENAMETOOLONG:
|
||||
return syscall.ENAMETOOLONG
|
||||
case ENOENT:
|
||||
return syscall.ENOENT
|
||||
case ENOSYS:
|
||||
return syscall.ENOSYS
|
||||
case ENOTDIR:
|
||||
return syscall.ENOTDIR
|
||||
case ENOTEMPTY:
|
||||
return syscall.ENOTEMPTY
|
||||
case ENOTSOCK:
|
||||
return syscall.ENOTSOCK
|
||||
case ENOTSUP:
|
||||
return syscall.ENOTSUP
|
||||
case EPERM:
|
||||
return syscall.EPERM
|
||||
case EROFS:
|
||||
return syscall.EROFS
|
||||
default:
|
||||
return syscall.EIO
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
//go:build !windows
|
||||
|
||||
package sys
|
||||
|
||||
func errorToErrno(err error) Errno {
|
||||
if errno, ok := err.(Errno); ok {
|
||||
return errno
|
||||
}
|
||||
if errno, ok := syscallToErrno(err); ok {
|
||||
return errno
|
||||
}
|
||||
return EIO
|
||||
}
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
//go:build plan9 || aix
|
||||
|
||||
package sys
|
||||
|
||||
func syscallToErrno(err error) (Errno, bool) {
|
||||
return 0, false
|
||||
}
|
||||
Generated
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
package sys
|
||||
|
||||
import "syscall"
|
||||
|
||||
// These are errors not defined in the syscall package. They are prefixed with
|
||||
// underscore to avoid exporting them.
|
||||
//
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
|
||||
const (
|
||||
// _ERROR_INVALID_HANDLE is a Windows error returned by syscall.Write
|
||||
// instead of syscall.EBADF
|
||||
_ERROR_INVALID_HANDLE = syscall.Errno(6)
|
||||
|
||||
// _ERROR_INVALID_NAME is a Windows error returned by open when a file
|
||||
// path has a trailing slash
|
||||
_ERROR_INVALID_NAME = syscall.Errno(0x7B)
|
||||
|
||||
// _ERROR_NEGATIVE_SEEK is a Windows error returned by os.Truncate
|
||||
// instead of syscall.EINVAL
|
||||
_ERROR_NEGATIVE_SEEK = syscall.Errno(0x83)
|
||||
|
||||
// _ERROR_DIRECTORY is a Windows error returned by syscall.Rmdir
|
||||
// instead of syscall.ENOTDIR
|
||||
_ERROR_DIRECTORY = syscall.Errno(0x10B)
|
||||
|
||||
// _ERROR_INVALID_SOCKET is a Windows error returned by winsock_select
|
||||
// when a given handle is not a socket.
|
||||
_ERROR_INVALID_SOCKET = syscall.Errno(0x2736)
|
||||
)
|
||||
|
||||
func errorToErrno(err error) Errno {
|
||||
switch err := err.(type) {
|
||||
case Errno:
|
||||
return err
|
||||
case syscall.Errno:
|
||||
// Note: In windows, _ERROR_PATH_NOT_FOUND(0x3) maps to syscall.ENOTDIR
|
||||
switch err {
|
||||
case syscall.ERROR_ALREADY_EXISTS:
|
||||
return EEXIST
|
||||
case _ERROR_DIRECTORY:
|
||||
return ENOTDIR
|
||||
case syscall.ERROR_DIR_NOT_EMPTY:
|
||||
return ENOTEMPTY
|
||||
case syscall.ERROR_FILE_EXISTS:
|
||||
return EEXIST
|
||||
case _ERROR_INVALID_HANDLE, _ERROR_INVALID_SOCKET:
|
||||
return EBADF
|
||||
case syscall.ERROR_ACCESS_DENIED:
|
||||
// POSIX read and write functions expect EBADF, not EACCES when not
|
||||
// open for reading or writing.
|
||||
return EBADF
|
||||
case syscall.ERROR_PRIVILEGE_NOT_HELD:
|
||||
return EPERM
|
||||
case _ERROR_NEGATIVE_SEEK, _ERROR_INVALID_NAME:
|
||||
return EINVAL
|
||||
}
|
||||
errno, _ := syscallToErrno(err)
|
||||
return errno
|
||||
default:
|
||||
return EIO
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package sys
|
||||
|
||||
import "math"
|
||||
|
||||
// UTIME_OMIT is a special constant for use in updating times via FS.Utimens
|
||||
// or File.Utimens. When used for atim or mtim, the value is retained.
|
||||
//
|
||||
// Note: This may be implemented via a stat when the underlying filesystem
|
||||
// does not support this value.
|
||||
const UTIME_OMIT int64 = math.MinInt64
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
// UnimplementedFS is an FS that returns ENOSYS for all functions,
|
||||
// This should be embedded to have forward compatible implementations.
|
||||
type UnimplementedFS struct{}
|
||||
|
||||
// OpenFile implements FS.OpenFile
|
||||
func (UnimplementedFS) OpenFile(path string, flag Oflag, perm fs.FileMode) (File, Errno) {
|
||||
return nil, ENOSYS
|
||||
}
|
||||
|
||||
// Lstat implements FS.Lstat
|
||||
func (UnimplementedFS) Lstat(path string) (sys.Stat_t, Errno) {
|
||||
return sys.Stat_t{}, ENOSYS
|
||||
}
|
||||
|
||||
// Stat implements FS.Stat
|
||||
func (UnimplementedFS) Stat(path string) (sys.Stat_t, Errno) {
|
||||
return sys.Stat_t{}, ENOSYS
|
||||
}
|
||||
|
||||
// Readlink implements FS.Readlink
|
||||
func (UnimplementedFS) Readlink(path string) (string, Errno) {
|
||||
return "", ENOSYS
|
||||
}
|
||||
|
||||
// Mkdir implements FS.Mkdir
|
||||
func (UnimplementedFS) Mkdir(path string, perm fs.FileMode) Errno {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
// Chmod implements FS.Chmod
|
||||
func (UnimplementedFS) Chmod(path string, perm fs.FileMode) Errno {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
// Rename implements FS.Rename
|
||||
func (UnimplementedFS) Rename(from, to string) Errno {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
// Rmdir implements FS.Rmdir
|
||||
func (UnimplementedFS) Rmdir(path string) Errno {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
// Link implements FS.Link
|
||||
func (UnimplementedFS) Link(_, _ string) Errno {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
// Symlink implements FS.Symlink
|
||||
func (UnimplementedFS) Symlink(_, _ string) Errno {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
// Unlink implements FS.Unlink
|
||||
func (UnimplementedFS) Unlink(path string) Errno {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
// Utimens implements FS.Utimens
|
||||
func (UnimplementedFS) Utimens(path string, atim, mtim int64) Errno {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
// UnimplementedFile is a File that returns ENOSYS for all functions,
|
||||
// except where no-op are otherwise documented.
|
||||
//
|
||||
// This should be embedded to have forward compatible implementations.
|
||||
type UnimplementedFile struct{}
|
||||
|
||||
// Dev implements File.Dev
|
||||
func (UnimplementedFile) Dev() (uint64, Errno) {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Ino implements File.Ino
|
||||
func (UnimplementedFile) Ino() (sys.Inode, Errno) {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// IsDir implements File.IsDir
|
||||
func (UnimplementedFile) IsDir() (bool, Errno) {
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// IsAppend implements File.IsAppend
|
||||
func (UnimplementedFile) IsAppend() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAppend implements File.SetAppend
|
||||
func (UnimplementedFile) SetAppend(bool) Errno {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
// Stat implements File.Stat
|
||||
func (UnimplementedFile) Stat() (sys.Stat_t, Errno) {
|
||||
return sys.Stat_t{}, ENOSYS
|
||||
}
|
||||
|
||||
// Read implements File.Read
|
||||
func (UnimplementedFile) Read([]byte) (int, Errno) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
|
||||
// Pread implements File.Pread
|
||||
func (UnimplementedFile) Pread([]byte, int64) (int, Errno) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
|
||||
// Seek implements File.Seek
|
||||
func (UnimplementedFile) Seek(int64, int) (int64, Errno) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
|
||||
// Readdir implements File.Readdir
|
||||
func (UnimplementedFile) Readdir(int) (dirents []Dirent, errno Errno) {
|
||||
return nil, ENOSYS
|
||||
}
|
||||
|
||||
// Write implements File.Write
|
||||
func (UnimplementedFile) Write([]byte) (int, Errno) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
|
||||
// Pwrite implements File.Pwrite
|
||||
func (UnimplementedFile) Pwrite([]byte, int64) (int, Errno) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
|
||||
// Truncate implements File.Truncate
|
||||
func (UnimplementedFile) Truncate(int64) Errno {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
// Sync implements File.Sync
|
||||
func (UnimplementedFile) Sync() Errno {
|
||||
return 0 // not ENOSYS
|
||||
}
|
||||
|
||||
// Datasync implements File.Datasync
|
||||
func (UnimplementedFile) Datasync() Errno {
|
||||
return 0 // not ENOSYS
|
||||
}
|
||||
|
||||
// Utimens implements File.Utimens
|
||||
func (UnimplementedFile) Utimens(int64, int64) Errno {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
// Close implements File.Close
|
||||
func (UnimplementedFile) Close() (errno Errno) { return }
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+44
-14
@@ -3,7 +3,8 @@ package wazero
|
||||
import (
|
||||
"io/fs"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/internal/sysfs"
|
||||
)
|
||||
|
||||
@@ -30,8 +31,7 @@ import (
|
||||
//
|
||||
// More notes on `guestPath`
|
||||
// - Go compiled with runtime.GOOS=js do not pay attention to this value.
|
||||
// Hence, you need to normalize the filesystem with NewRootFS to ensure
|
||||
// paths requested resolve as expected.
|
||||
// It only works with root mounts ("").
|
||||
// - Working directories are typically tracked in wasm, though possible some
|
||||
// relative paths are requested. For example, TinyGo may attempt to resolve
|
||||
// a path "../.." in unit tests.
|
||||
@@ -84,7 +84,7 @@ type FSConfig interface {
|
||||
//
|
||||
// # os.DirFS
|
||||
//
|
||||
// This configuration optimizes for WASI compatability which is sometimes
|
||||
// This configuration optimizes for WASI compatibility which is sometimes
|
||||
// at odds with the behavior of os.DirFS. Hence, this will not behave
|
||||
// exactly the same as os.DirFS. See /RATIONALE.md for more.
|
||||
WithDirMount(dir, guestPath string) FSConfig
|
||||
@@ -118,12 +118,24 @@ type FSConfig interface {
|
||||
// advise using WithDirMount instead. There will be behavior differences
|
||||
// between os.DirFS and WithDirMount, as the latter biases towards what's
|
||||
// expected from WASI implementations.
|
||||
//
|
||||
// # Custom fs.FileInfo
|
||||
//
|
||||
// The underlying implementation supports data not usually in fs.FileInfo
|
||||
// when `info.Sys` returns *sys.Stat_t. For example, a custom fs.FS can use
|
||||
// this approach to generate or mask sys.Inode data. Such a filesystem
|
||||
// needs to decorate any functions that can return fs.FileInfo:
|
||||
//
|
||||
// - `Stat` as defined on `fs.File` (always)
|
||||
// - `Readdir` as defined on `os.File` (if defined)
|
||||
//
|
||||
// See sys.NewStat_t for examples.
|
||||
WithFSMount(fs fs.FS, guestPath string) FSConfig
|
||||
}
|
||||
|
||||
type fsConfig struct {
|
||||
// fs are the currently configured filesystems.
|
||||
fs []fsapi.FS
|
||||
fs []experimentalsys.FS
|
||||
// guestPaths are the user-supplied names of the filesystems, retained for
|
||||
// error messages and fmt.Stringer.
|
||||
guestPaths []string
|
||||
@@ -140,7 +152,7 @@ func NewFSConfig() FSConfig {
|
||||
// clone makes a deep copy of this module config.
|
||||
func (c *fsConfig) clone() *fsConfig {
|
||||
ret := *c // copy except slice and maps which share a ref
|
||||
ret.fs = make([]fsapi.FS, 0, len(c.fs))
|
||||
ret.fs = make([]experimentalsys.FS, 0, len(c.fs))
|
||||
ret.fs = append(ret.fs, c.fs...)
|
||||
ret.guestPaths = make([]string, 0, len(c.guestPaths))
|
||||
ret.guestPaths = append(ret.guestPaths, c.guestPaths...)
|
||||
@@ -153,26 +165,34 @@ func (c *fsConfig) clone() *fsConfig {
|
||||
|
||||
// WithDirMount implements FSConfig.WithDirMount
|
||||
func (c *fsConfig) WithDirMount(dir, guestPath string) FSConfig {
|
||||
return c.withMount(sysfs.NewDirFS(dir), guestPath)
|
||||
return c.WithSysFSMount(sysfs.DirFS(dir), guestPath)
|
||||
}
|
||||
|
||||
// WithReadOnlyDirMount implements FSConfig.WithReadOnlyDirMount
|
||||
func (c *fsConfig) WithReadOnlyDirMount(dir, guestPath string) FSConfig {
|
||||
return c.withMount(sysfs.NewReadFS(sysfs.NewDirFS(dir)), guestPath)
|
||||
return c.WithSysFSMount(&sysfs.ReadFS{FS: sysfs.DirFS(dir)}, guestPath)
|
||||
}
|
||||
|
||||
// WithFSMount implements FSConfig.WithFSMount
|
||||
func (c *fsConfig) WithFSMount(fs fs.FS, guestPath string) FSConfig {
|
||||
return c.withMount(sysfs.Adapt(fs), guestPath)
|
||||
var adapted experimentalsys.FS
|
||||
if fs != nil {
|
||||
adapted = &sysfs.AdaptFS{FS: fs}
|
||||
}
|
||||
return c.WithSysFSMount(adapted, guestPath)
|
||||
}
|
||||
|
||||
func (c *fsConfig) withMount(fs fsapi.FS, guestPath string) FSConfig {
|
||||
cleaned := sysfs.StripPrefixesAndTrailingSlash(guestPath)
|
||||
// WithSysFSMount implements sysfs.FSConfig
|
||||
func (c *fsConfig) WithSysFSMount(fs experimentalsys.FS, guestPath string) FSConfig {
|
||||
if _, ok := fs.(experimentalsys.UnimplementedFS); ok {
|
||||
return c // don't add fake paths.
|
||||
}
|
||||
cleaned := sys.StripPrefixesAndTrailingSlash(guestPath)
|
||||
ret := c.clone()
|
||||
if i, ok := ret.guestPathToFS[cleaned]; ok {
|
||||
ret.fs[i] = fs
|
||||
ret.guestPaths[i] = guestPath
|
||||
} else {
|
||||
} else if fs != nil {
|
||||
ret.guestPathToFS[cleaned] = len(ret.fs)
|
||||
ret.fs = append(ret.fs, fs)
|
||||
ret.guestPaths = append(ret.guestPaths, guestPath)
|
||||
@@ -180,6 +200,16 @@ func (c *fsConfig) withMount(fs fsapi.FS, guestPath string) FSConfig {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *fsConfig) toFS() (fsapi.FS, error) {
|
||||
return sysfs.NewRootFS(c.fs, c.guestPaths)
|
||||
// preopens returns the possible nil index-correlated preopened filesystems
|
||||
// with guest paths.
|
||||
func (c *fsConfig) preopens() ([]experimentalsys.FS, []string) {
|
||||
preopenCount := len(c.fs)
|
||||
if preopenCount == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
fs := make([]experimentalsys.FS, len(c.fs))
|
||||
copy(fs, c.fs)
|
||||
guestPaths := make([]string, len(c.guestPaths))
|
||||
copy(guestPaths, c.guestPaths)
|
||||
return fs, guestPaths
|
||||
}
|
||||
Generated
Vendored
+7
-7
@@ -2,9 +2,9 @@ package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// - syscall.EFAULT: there is not enough memory to write results
|
||||
// - sys.EFAULT: there is not enough memory to write results
|
||||
//
|
||||
// For example, if argsSizesGet wrote argc=2 and argvLen=5 for arguments:
|
||||
// "a" and "bc" parameters argv=7 and argvBuf=1, this function writes the below
|
||||
@@ -43,7 +43,7 @@ import (
|
||||
// See https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
var argsGet = newHostFunc(wasip1.ArgsGetName, argsGetFn, []api.ValueType{i32, i32}, "argv", "argv_buf")
|
||||
|
||||
func argsGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func argsGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
argv, argvBuf := uint32(params[0]), uint32(params[1])
|
||||
return writeOffsetsAndNullTerminatedValues(mod.Memory(), sysCtx.Args(), argv, argvBuf, sysCtx.ArgsSize())
|
||||
@@ -61,7 +61,7 @@ func argsGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// - syscall.EFAULT: there is not enough memory to write results
|
||||
// - sys.EFAULT: there is not enough memory to write results
|
||||
//
|
||||
// For example, if args are "a", "bc" and parameters resultArgc=1 and
|
||||
// resultArgvLen=6, this function writes the below to api.Memory:
|
||||
@@ -80,7 +80,7 @@ func argsGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno
|
||||
// See https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
var argsSizesGet = newHostFunc(wasip1.ArgsSizesGetName, argsSizesGetFn, []api.ValueType{i32, i32}, "result.argc", "result.argv_len")
|
||||
|
||||
func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
mem := mod.Memory()
|
||||
resultArgc, resultArgvLen := uint32(params[0]), uint32(params[1])
|
||||
@@ -88,10 +88,10 @@ func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) syscall.
|
||||
// argc and argv_len offsets are not necessarily sequential, so we have to
|
||||
// write them independently.
|
||||
if !mem.WriteUint32Le(resultArgc, uint32(len(sysCtx.Args()))) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
if !mem.WriteUint32Le(resultArgvLen, sysCtx.ArgsSize()) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Generated
Vendored
+13
-13
@@ -2,9 +2,9 @@ package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -21,9 +21,9 @@ import (
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is 0 except the following error conditions:
|
||||
// - syscall.ENOTSUP: the clock ID is not supported.
|
||||
// - syscall.EINVAL: the clock ID is invalid.
|
||||
// - syscall.EFAULT: there is not enough memory to write results
|
||||
// - sys.ENOTSUP: the clock ID is not supported.
|
||||
// - sys.EINVAL: the clock ID is invalid.
|
||||
// - sys.EFAULT: there is not enough memory to write results
|
||||
//
|
||||
// For example, if the resolution is 100ns, this function writes the below to
|
||||
// api.Memory:
|
||||
@@ -39,7 +39,7 @@ import (
|
||||
// See https://linux.die.net/man/3/clock_getres
|
||||
var clockResGet = newHostFunc(wasip1.ClockResGetName, clockResGetFn, []api.ValueType{i32, i32}, "id", "result.resolution")
|
||||
|
||||
func clockResGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func clockResGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
id, resultResolution := uint32(params[0]), uint32(params[1])
|
||||
|
||||
@@ -50,11 +50,11 @@ func clockResGetFn(_ context.Context, mod api.Module, params []uint64) syscall.E
|
||||
case wasip1.ClockIDMonotonic:
|
||||
resolution = uint64(sysCtx.NanotimeResolution())
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
|
||||
if !mod.Memory().WriteUint64Le(resultResolution, resolution) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -73,9 +73,9 @@ func clockResGetFn(_ context.Context, mod api.Module, params []uint64) syscall.E
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is 0 except the following error conditions:
|
||||
// - syscall.ENOTSUP: the clock ID is not supported.
|
||||
// - syscall.EINVAL: the clock ID is invalid.
|
||||
// - syscall.EFAULT: there is not enough memory to write results
|
||||
// - sys.ENOTSUP: the clock ID is not supported.
|
||||
// - sys.EINVAL: the clock ID is invalid.
|
||||
// - sys.EFAULT: there is not enough memory to write results
|
||||
//
|
||||
// For example, if time.Now returned exactly midnight UTC 2022-01-01
|
||||
// (1640995200000000000), and parameters resultTimestamp=1, this function
|
||||
@@ -92,7 +92,7 @@ func clockResGetFn(_ context.Context, mod api.Module, params []uint64) syscall.E
|
||||
// See https://linux.die.net/man/3/clock_gettime
|
||||
var clockTimeGet = newHostFunc(wasip1.ClockTimeGetName, clockTimeGetFn, []api.ValueType{i32, i64, i32}, "id", "precision", "result.timestamp")
|
||||
|
||||
func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
id := uint32(params[0])
|
||||
// TODO: precision is currently ignored.
|
||||
@@ -106,11 +106,11 @@ func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) syscall.
|
||||
case wasip1.ClockIDMonotonic:
|
||||
val = sysCtx.Nanotime()
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
|
||||
if !mod.Memory().WriteUint64Le(resultTimestamp, uint64(val)) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Generated
Vendored
+7
-7
@@ -2,9 +2,9 @@ package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is 0 except the following error conditions:
|
||||
// - syscall.EFAULT: there is not enough memory to write results
|
||||
// - sys.EFAULT: there is not enough memory to write results
|
||||
//
|
||||
// For example, if environSizesGet wrote environc=2 and environLen=9 for
|
||||
// environment variables: "a=b", "b=cd" and parameters environ=11 and
|
||||
@@ -43,7 +43,7 @@ import (
|
||||
// See https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
var environGet = newHostFunc(wasip1.EnvironGetName, environGetFn, []api.ValueType{i32, i32}, "environ", "environ_buf")
|
||||
|
||||
func environGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func environGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
environ, environBuf := uint32(params[0]), uint32(params[1])
|
||||
|
||||
@@ -63,7 +63,7 @@ func environGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is 0 except the following error conditions:
|
||||
// - syscall.EFAULT: there is not enough memory to write results
|
||||
// - sys.EFAULT: there is not enough memory to write results
|
||||
//
|
||||
// For example, if environ are "a=b","b=cd" and parameters resultEnvironc=1 and
|
||||
// resultEnvironvLen=6, this function writes the below to api.Memory:
|
||||
@@ -83,7 +83,7 @@ func environGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
// and https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
var environSizesGet = newHostFunc(wasip1.EnvironSizesGetName, environSizesGetFn, []api.ValueType{i32, i32}, "result.environc", "result.environv_len")
|
||||
|
||||
func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
mem := mod.Memory()
|
||||
resultEnvironc, resultEnvironvLen := uint32(params[0]), uint32(params[1])
|
||||
@@ -91,10 +91,10 @@ func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) sysca
|
||||
// environc and environv_len offsets are not necessarily sequential, so we
|
||||
// have to write them independently.
|
||||
if !mem.WriteUint32Le(resultEnvironc, uint32(len(sysCtx.Environ()))) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
if !mem.WriteUint32Le(resultEnvironvLen, sysCtx.EnvironSize()) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Generated
Vendored
+351
-301
File diff suppressed because it is too large.
Load diff
Generated
Vendored
+77
-89
@@ -2,10 +2,11 @@ package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
@@ -18,15 +19,15 @@ import (
|
||||
//
|
||||
// - in: pointer to the subscriptions (48 bytes each)
|
||||
// - out: pointer to the resulting events (32 bytes each)
|
||||
// - nsubscriptions: count of subscriptions, zero returns syscall.EINVAL.
|
||||
// - nsubscriptions: count of subscriptions, zero returns sys.EINVAL.
|
||||
// - resultNevents: count of events.
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is 0 except the following error conditions:
|
||||
// - syscall.EINVAL: the parameters are invalid
|
||||
// - syscall.ENOTSUP: a parameters is valid, but not yet supported.
|
||||
// - syscall.EFAULT: there is not enough memory to read the subscriptions or
|
||||
// - sys.EINVAL: the parameters are invalid
|
||||
// - sys.ENOTSUP: a parameters is valid, but not yet supported.
|
||||
// - sys.EFAULT: there is not enough memory to read the subscriptions or
|
||||
// write results.
|
||||
//
|
||||
// # Notes
|
||||
@@ -46,17 +47,16 @@ type event struct {
|
||||
eventType byte
|
||||
userData []byte
|
||||
errno wasip1.Errno
|
||||
outOffset uint32
|
||||
}
|
||||
|
||||
func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
in := uint32(params[0])
|
||||
out := uint32(params[1])
|
||||
nsubscriptions := uint32(params[2])
|
||||
resultNevents := uint32(params[3])
|
||||
|
||||
if nsubscriptions == 0 {
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
|
||||
mem := mod.Memory()
|
||||
@@ -64,7 +64,7 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
// Ensure capacity prior to the read loop to reduce error handling.
|
||||
inBuf, ok := mem.Read(in, nsubscriptions*48)
|
||||
if !ok {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
outBuf, ok := mem.Read(out, nsubscriptions*32)
|
||||
// zero-out all buffer before writing
|
||||
@@ -73,33 +73,33 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
|
||||
// Eagerly write the number of events which will equal subscriptions unless
|
||||
// there's a fault in parsing (not processing).
|
||||
if !mod.Memory().WriteUint32Le(resultNevents, nsubscriptions) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
|
||||
// Loop through all subscriptions and write their output.
|
||||
|
||||
// Extract FS context, used in the body of the for loop for FS access.
|
||||
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
|
||||
// Slice of events that are processed out of the loop (stdin subscribers).
|
||||
var stdinSubs []*event
|
||||
// Slice of events that are processed out of the loop (blocking stdin subscribers).
|
||||
var blockingStdinSubs []*event
|
||||
// The timeout is initialized at max Duration, the loop will find the minimum.
|
||||
var timeout time.Duration = 1<<63 - 1
|
||||
// Count of all the clock subscribers that have been already written back to outBuf.
|
||||
clockEvents := uint32(0)
|
||||
// Count of all the non-clock subscribers that have been already written back to outBuf.
|
||||
readySubs := uint32(0)
|
||||
// Count of all the subscriptions that have been already written back to outBuf.
|
||||
// nevents*32 returns at all times the offset where the next event should be written:
|
||||
// this way we ensure that there are no gaps between records.
|
||||
nevents := uint32(0)
|
||||
|
||||
// Layout is subscription_u: Union
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#subscription_u
|
||||
for i := uint32(0); i < nsubscriptions; i++ {
|
||||
inOffset := i * 48
|
||||
outOffset := i * 32
|
||||
outOffset := nevents * 32
|
||||
|
||||
eventType := inBuf[inOffset+8] // +8 past userdata
|
||||
// +8 past userdata +8 contents_offset
|
||||
@@ -110,12 +110,10 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
eventType: eventType,
|
||||
userData: userData,
|
||||
errno: wasip1.ErrnoSuccess,
|
||||
outOffset: outOffset,
|
||||
}
|
||||
|
||||
switch eventType {
|
||||
case wasip1.EventTypeClock: // handle later
|
||||
clockEvents++
|
||||
newTimeout, err := processClockEvent(argBuf)
|
||||
if err != 0 {
|
||||
return err
|
||||
@@ -125,69 +123,76 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
timeout = newTimeout
|
||||
}
|
||||
// Ack the clock event to the outBuf.
|
||||
writeEvent(outBuf, evt)
|
||||
writeEvent(outBuf[outOffset:], evt)
|
||||
nevents++
|
||||
case wasip1.EventTypeFdRead:
|
||||
fd := int32(le.Uint32(argBuf))
|
||||
if fd < 0 {
|
||||
return syscall.EBADF
|
||||
return sys.EBADF
|
||||
}
|
||||
if fd == internalsys.FdStdin {
|
||||
// if the fd is Stdin, do not ack yet,
|
||||
// append to a slice for delayed evaluation.
|
||||
stdinSubs = append(stdinSubs, evt)
|
||||
if file, ok := fsc.LookupFile(fd); !ok {
|
||||
evt.errno = wasip1.ErrnoBadf
|
||||
writeEvent(outBuf[outOffset:], evt)
|
||||
nevents++
|
||||
} else if fd != internalsys.FdStdin && file.File.IsNonblock() {
|
||||
writeEvent(outBuf[outOffset:], evt)
|
||||
nevents++
|
||||
} else {
|
||||
evt.errno = processFDEventRead(fsc, fd)
|
||||
writeEvent(outBuf, evt)
|
||||
readySubs++
|
||||
// if the fd is Stdin, and it is in blocking mode,
|
||||
// do not ack yet, append to a slice for delayed evaluation.
|
||||
blockingStdinSubs = append(blockingStdinSubs, evt)
|
||||
}
|
||||
case wasip1.EventTypeFdWrite:
|
||||
fd := int32(le.Uint32(argBuf))
|
||||
if fd < 0 {
|
||||
return syscall.EBADF
|
||||
return sys.EBADF
|
||||
}
|
||||
evt.errno = processFDEventWrite(fsc, fd)
|
||||
readySubs++
|
||||
writeEvent(outBuf, evt)
|
||||
if _, ok := fsc.LookupFile(fd); ok {
|
||||
evt.errno = wasip1.ErrnoNotsup
|
||||
} else {
|
||||
evt.errno = wasip1.ErrnoBadf
|
||||
}
|
||||
nevents++
|
||||
writeEvent(outBuf[outOffset:], evt)
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
}
|
||||
|
||||
// If there are subscribers with data ready, we have already written them to outBuf,
|
||||
// and we don't need to wait for the timeout: clear it.
|
||||
if readySubs != 0 {
|
||||
timeout = 0
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
if nevents == nsubscriptions {
|
||||
// We already wrote back all the results. We already wrote this number
|
||||
// earlier to offset `resultNevents`.
|
||||
// We only need to observe the timeout (nonzero if there are clock subscriptions)
|
||||
// and return.
|
||||
if timeout > 0 {
|
||||
sysCtx.Nanosleep(int64(timeout))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// If there are stdin subscribers, check for data with given timeout.
|
||||
if len(stdinSubs) > 0 {
|
||||
stdin, ok := fsc.LookupFile(internalsys.FdStdin)
|
||||
if !ok {
|
||||
return syscall.EBADF
|
||||
// If there are blocking stdin subscribers, check for data with given timeout.
|
||||
stdin, ok := fsc.LookupFile(internalsys.FdStdin)
|
||||
if !ok {
|
||||
return sys.EBADF
|
||||
}
|
||||
// Wait for the timeout to expire, or for some data to become available on Stdin.
|
||||
|
||||
if stdinReady, errno := stdin.File.Poll(fsapi.POLLIN, int32(timeout.Milliseconds())); errno != 0 {
|
||||
return errno
|
||||
} else if stdinReady {
|
||||
// stdin has data ready to for reading, write back all the events
|
||||
for i := range blockingStdinSubs {
|
||||
evt := blockingStdinSubs[i]
|
||||
evt.errno = 0
|
||||
writeEvent(outBuf[nevents*32:], evt)
|
||||
nevents++
|
||||
}
|
||||
// Wait for the timeout to expire, or for some data to become available on Stdin.
|
||||
stdinReady, errno := stdin.File.PollRead(&timeout)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
if stdinReady {
|
||||
// stdin has data ready to for reading, write back all the events
|
||||
for i := range stdinSubs {
|
||||
readySubs++
|
||||
evt := stdinSubs[i]
|
||||
evt.errno = 0
|
||||
writeEvent(outBuf, evt)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No subscribers, just wait for the given timeout.
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
sysCtx.Nanosleep(int64(timeout))
|
||||
}
|
||||
|
||||
if readySubs != nsubscriptions {
|
||||
if !mod.Memory().WriteUint32Le(resultNevents, readySubs+clockEvents) {
|
||||
return syscall.EFAULT
|
||||
if nevents != nsubscriptions {
|
||||
if !mod.Memory().WriteUint32Le(resultNevents, nevents) {
|
||||
return sys.EFAULT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,20 +201,20 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
|
||||
// processClockEvent supports only relative name events, as that's what's used
|
||||
// to implement sleep in various compilers including Rust, Zig and TinyGo.
|
||||
func processClockEvent(inBuf []byte) (time.Duration, syscall.Errno) {
|
||||
func processClockEvent(inBuf []byte) (time.Duration, sys.Errno) {
|
||||
_ /* ID */ = le.Uint32(inBuf[0:8]) // See below
|
||||
timeout := le.Uint64(inBuf[8:16]) // nanos if relative
|
||||
_ /* precision */ = le.Uint64(inBuf[16:24]) // Unused
|
||||
flags := le.Uint16(inBuf[24:32])
|
||||
|
||||
var err syscall.Errno
|
||||
var err sys.Errno
|
||||
// subclockflags has only one flag defined: subscription_clock_abstime
|
||||
switch flags {
|
||||
case 0: // relative time
|
||||
case 1: // subscription_clock_abstime
|
||||
err = syscall.ENOTSUP
|
||||
err = sys.ENOTSUP
|
||||
default: // subclockflags has only one flag defined.
|
||||
err = syscall.EINVAL
|
||||
err = sys.EINVAL
|
||||
}
|
||||
|
||||
if err != 0 {
|
||||
@@ -223,29 +228,12 @@ func processClockEvent(inBuf []byte) (time.Duration, syscall.Errno) {
|
||||
}
|
||||
}
|
||||
|
||||
// processFDEventRead returns ErrnoSuccess if the file exists and ErrnoBadf otherwise.
|
||||
func processFDEventRead(fsc *internalsys.FSContext, fd int32) wasip1.Errno {
|
||||
if _, ok := fsc.LookupFile(fd); ok {
|
||||
return wasip1.ErrnoSuccess
|
||||
} else {
|
||||
return wasip1.ErrnoBadf
|
||||
}
|
||||
}
|
||||
|
||||
// processFDEventWrite returns ErrnoNotsup if the file exists and ErrnoBadf otherwise.
|
||||
func processFDEventWrite(fsc *internalsys.FSContext, fd int32) wasip1.Errno {
|
||||
if _, ok := fsc.LookupFile(fd); ok {
|
||||
return wasip1.ErrnoNotsup
|
||||
}
|
||||
return wasip1.ErrnoBadf
|
||||
}
|
||||
|
||||
// writeEvent writes the event corresponding to the processed subscription.
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct
|
||||
func writeEvent(outBuf []byte, evt *event) {
|
||||
copy(outBuf[evt.outOffset:], evt.userData) // userdata
|
||||
outBuf[evt.outOffset+8] = byte(evt.errno) // uint16, but safe as < 255
|
||||
outBuf[evt.outOffset+9] = 0
|
||||
le.PutUint32(outBuf[evt.outOffset+10:], uint32(evt.eventType))
|
||||
copy(outBuf, evt.userData) // userdata
|
||||
outBuf[8] = byte(evt.errno) // uint16, but safe as < 255
|
||||
outBuf[9] = 0
|
||||
le.PutUint32(outBuf[10:], uint32(evt.eventType))
|
||||
// TODO: When FD events are supported, write outOffset+16
|
||||
}
|
||||
Generated
Vendored
+6
-6
@@ -3,9 +3,9 @@ package wasi_snapshot_preview1
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// - syscall.EFAULT: `buf` or `bufLen` point to an offset out of memory
|
||||
// - syscall.EIO: a file system error
|
||||
// - sys.EFAULT: `buf` or `bufLen` point to an offset out of memory
|
||||
// - sys.EIO: a file system error
|
||||
//
|
||||
// For example, if underlying random source was seeded like
|
||||
// `rand.NewSource(42)`, we expect api.Memory to contain:
|
||||
@@ -36,19 +36,19 @@ import (
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-bufLen-size---errno
|
||||
var randomGet = newHostFunc(wasip1.RandomGetName, randomGetFn, []api.ValueType{i32, i32}, "buf", "buf_len")
|
||||
|
||||
func randomGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func randomGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
randSource := sysCtx.RandSource()
|
||||
buf, bufLen := uint32(params[0]), uint32(params[1])
|
||||
|
||||
randomBytes, ok := mod.Memory().Read(buf, bufLen)
|
||||
if !ok { // out-of-range
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
|
||||
// We can ignore the returned n as it only != byteCount on error
|
||||
if _, err := io.ReadAtLeast(randSource, randomBytes, int(bufLen)); err != nil {
|
||||
return syscall.EIO
|
||||
return sys.EIO
|
||||
}
|
||||
|
||||
return 0
|
||||
|
||||
Generated
Vendored
+2
-2
@@ -2,9 +2,9 @@ package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sched_yield---errno
|
||||
var schedYield = newHostFunc(wasip1.SchedYieldName, schedYieldFn, nil)
|
||||
|
||||
func schedYieldFn(_ context.Context, mod api.Module, _ []uint64) syscall.Errno {
|
||||
func schedYieldFn(_ context.Context, mod api.Module, _ []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
sysCtx.Osyield()
|
||||
return 0
|
||||
|
||||
Generated
Vendored
+21
-20
@@ -2,9 +2,9 @@ package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
socketapi "github.com/tetratelabs/wazero/internal/sock"
|
||||
"github.com/tetratelabs/wazero/internal/sysfs"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
@@ -23,7 +23,7 @@ var sockAccept = newHostFunc(
|
||||
"fd", "flags", "result.fd",
|
||||
)
|
||||
|
||||
func sockAcceptFn(_ context.Context, mod api.Module, params []uint64) (errno syscall.Errno) {
|
||||
func sockAcceptFn(_ context.Context, mod api.Module, params []uint64) (errno sys.Errno) {
|
||||
mem := mod.Memory()
|
||||
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
|
||||
|
||||
@@ -50,7 +50,7 @@ var sockRecv = newHostFunc(
|
||||
"fd", "ri_data", "ri_data_len", "ri_flags", "result.ro_datalen", "result.ro_flags",
|
||||
)
|
||||
|
||||
func sockRecvFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func sockRecvFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
mem := mod.Memory()
|
||||
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
|
||||
|
||||
@@ -63,13 +63,13 @@ func sockRecvFn(_ context.Context, mod api.Module, params []uint64) syscall.Errn
|
||||
|
||||
var conn socketapi.TCPConn
|
||||
if e, ok := fsc.LookupFile(fd); !ok {
|
||||
return syscall.EBADF // Not open
|
||||
return sys.EBADF // Not open
|
||||
} else if conn, ok = e.File.(socketapi.TCPConn); !ok {
|
||||
return syscall.EBADF // Not a conn
|
||||
return sys.EBADF // Not a conn
|
||||
}
|
||||
|
||||
if riFlags & ^(wasip1.RI_RECV_PEEK|wasip1.RI_RECV_WAITALL) != 0 {
|
||||
return syscall.ENOTSUP
|
||||
return sys.ENOTSUP
|
||||
}
|
||||
|
||||
if riFlags&wasip1.RI_RECV_PEEK != 0 {
|
||||
@@ -78,16 +78,16 @@ func sockRecvFn(_ context.Context, mod api.Module, params []uint64) syscall.Errn
|
||||
// This means that the first `uint32` is a `buf *uint8`.
|
||||
firstIovecBufAddr, ok := mem.ReadUint32Le(riData)
|
||||
if !ok {
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
// Read bufLen
|
||||
firstIovecBufLen, ok := mem.ReadUint32Le(riData + 4)
|
||||
if !ok {
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
firstIovecBuf, ok := mem.Read(firstIovecBufAddr, firstIovecBufLen)
|
||||
if !ok {
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
n, err := conn.Recvfrom(firstIovecBuf, sysfs.MSG_PEEK)
|
||||
if err != 0 {
|
||||
@@ -122,7 +122,7 @@ var sockSend = newHostFunc(
|
||||
"fd", "si_data", "si_data_len", "si_flags", "result.so_datalen",
|
||||
)
|
||||
|
||||
func sockSendFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func sockSendFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
mem := mod.Memory()
|
||||
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
|
||||
|
||||
@@ -133,14 +133,14 @@ func sockSendFn(_ context.Context, mod api.Module, params []uint64) syscall.Errn
|
||||
resultSoDatalen := uint32(params[4])
|
||||
|
||||
if siFlags != 0 {
|
||||
return syscall.ENOTSUP
|
||||
return sys.ENOTSUP
|
||||
}
|
||||
|
||||
var conn socketapi.TCPConn
|
||||
if e, ok := fsc.LookupFile(fd); !ok {
|
||||
return syscall.EBADF // Not open
|
||||
return sys.EBADF // Not open
|
||||
} else if conn, ok = e.File.(socketapi.TCPConn); !ok {
|
||||
return syscall.EBADF // Not a conn
|
||||
return sys.EBADF // Not a conn
|
||||
}
|
||||
|
||||
bufSize, errno := writev(mem, siData, siDataCount, conn.Write)
|
||||
@@ -157,7 +157,7 @@ func sockSendFn(_ context.Context, mod api.Module, params []uint64) syscall.Errn
|
||||
// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_shutdownfd-fd-how-sdflags---errno
|
||||
var sockShutdown = newHostFunc(wasip1.SockShutdownName, sockShutdownFn, []wasm.ValueType{i32, i32}, "fd", "how")
|
||||
|
||||
func sockShutdownFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func sockShutdownFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
|
||||
|
||||
fd := int32(params[0])
|
||||
@@ -165,23 +165,24 @@ func sockShutdownFn(_ context.Context, mod api.Module, params []uint64) syscall.
|
||||
|
||||
var conn socketapi.TCPConn
|
||||
if e, ok := fsc.LookupFile(fd); !ok {
|
||||
return syscall.EBADF // Not open
|
||||
return sys.EBADF // Not open
|
||||
} else if conn, ok = e.File.(socketapi.TCPConn); !ok {
|
||||
return syscall.EBADF // Not a conn
|
||||
return sys.EBADF // Not a conn
|
||||
}
|
||||
|
||||
sysHow := 0
|
||||
|
||||
switch how {
|
||||
case wasip1.SD_RD | wasip1.SD_WR:
|
||||
sysHow = syscall.SHUT_RD | syscall.SHUT_WR
|
||||
sysHow = socketapi.SHUT_RD | socketapi.SHUT_WR
|
||||
case wasip1.SD_RD:
|
||||
sysHow = syscall.SHUT_RD
|
||||
sysHow = socketapi.SHUT_RD
|
||||
case wasip1.SD_WR:
|
||||
sysHow = syscall.SHUT_WR
|
||||
sysHow = socketapi.SHUT_WR
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
|
||||
// TODO: Map this instead of relying on syscall symbols.
|
||||
return conn.Shutdown(sysHow)
|
||||
}
|
||||
File renamed without changes.
Generated
Vendored
-45
@@ -1,45 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
switch os.Args[1] {
|
||||
case "sock":
|
||||
if err := mainSock(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mainSock() error {
|
||||
// Get a listener from the pre-opened file descriptor.
|
||||
// The listener is the first pre-open, with a file-descriptor of 3.
|
||||
f := os.NewFile(3, "")
|
||||
l, err := net.FileListener(f)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
// Accept a connection
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Do a blocking read of up to 32 bytes.
|
||||
// Note: the test should write: "wazero", so that's all we should read.
|
||||
var buf [32]byte
|
||||
n, err := conn.Read(buf[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(buf[:n]))
|
||||
return nil
|
||||
}
|
||||
+2
@@ -29,8 +29,10 @@ func main() {
|
||||
}
|
||||
case "sock":
|
||||
// TODO: undefined: net.FileListener
|
||||
// See https://github.com/tinygo-org/tinygo/pull/2748
|
||||
case "nonblock":
|
||||
// TODO: undefined: syscall.SetNonblock
|
||||
// See https://github.com/tinygo-org/tinygo/issues/3840
|
||||
}
|
||||
}
|
||||
|
||||
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
+26
@@ -162,6 +162,30 @@ void main_sock() {
|
||||
}
|
||||
}
|
||||
|
||||
void main_nonblock(char* fpath) {
|
||||
struct timespec tim, tim2;
|
||||
tim.tv_sec = 0;
|
||||
tim.tv_nsec = 100 * 1000000; // 100 msec
|
||||
int fd = open(fpath, O_RDONLY | O_NONBLOCK);
|
||||
char buf[32];
|
||||
ssize_t newLen = 0;
|
||||
while (newLen == 0) {
|
||||
newLen = read(fd, buf, sizeof(buf));
|
||||
// If an empty string is read, newLen might be 1,
|
||||
// causing the loop to terminate.
|
||||
if (strlen(buf) == 0) {
|
||||
newLen = 0;
|
||||
}
|
||||
if (errno == EAGAIN || newLen == 0) {
|
||||
printf(".");
|
||||
nanosleep(&tim , &tim2) ;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
printf("\n%s\n", buf);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (strcmp(argv[1],"ls")==0) {
|
||||
bool repeat = false;
|
||||
@@ -193,6 +217,8 @@ int main(int argc, char** argv) {
|
||||
main_open_wronly();
|
||||
} else if (strcmp(argv[1],"sock")==0) {
|
||||
main_sock();
|
||||
} else if (strcmp(argv[1],"nonblock")==0) {
|
||||
main_nonblock(argv[2]);
|
||||
} else {
|
||||
fprintf(stderr, "unknown command: %s\n", argv[1]);
|
||||
return 1;
|
||||
|
||||
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
+1
-1
@@ -12,5 +12,5 @@ pub fn build(b: *std.build.Builder) void {
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
exe.install();
|
||||
std.Build.installArtifact(b, exe);
|
||||
}
|
||||
Generated
Vendored
+5
-5
@@ -19,10 +19,10 @@ package wasi_snapshot_preview1
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -229,18 +229,18 @@ func exportFunctions(builder wazero.HostModuleBuilder) {
|
||||
// writeOffsetsAndNullTerminatedValues is used to write NUL-terminated values
|
||||
// for args or environ, given a pre-defined bytesLen (which includes NUL
|
||||
// terminators).
|
||||
func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offsets, bytes, bytesLen uint32) syscall.Errno {
|
||||
func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offsets, bytes, bytesLen uint32) sys.Errno {
|
||||
// The caller may not place bytes directly after offsets, so we have to
|
||||
// read them independently.
|
||||
valuesLen := len(values)
|
||||
offsetsLen := uint32(valuesLen * 4) // uint32Le
|
||||
offsetsBuf, ok := mem.Read(offsets, offsetsLen)
|
||||
if !ok {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
bytesBuf, ok := mem.Read(bytes, bytesLen)
|
||||
if !ok {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
|
||||
// Loop through the values, first writing the location of its data to
|
||||
@@ -285,7 +285,7 @@ func newHostFunc(
|
||||
|
||||
// wasiFunc special cases that all WASI functions return a single Errno
|
||||
// result. The returned value will be written back to the stack at index zero.
|
||||
type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) syscall.Errno
|
||||
type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) sys.Errno
|
||||
|
||||
// Call implements the same method as documented on api.GoModuleFunction.
|
||||
func (f wasiFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
package amd64
|
||||
|
||||
import (
|
||||
"github.com/tetratelabs/wazero/internal/asm"
|
||||
)
|
||||
|
||||
// Assembler is the interface used by amd64 compiler.
|
||||
type Assembler interface {
|
||||
asm.AssemblerBase
|
||||
|
||||
// CompileJumpToMemory adds jump-type instruction whose destination is stored in the memory address specified by `baseReg+offset`,
|
||||
// and returns the corresponding Node in the assembled linked list.
|
||||
CompileJumpToMemory(jmpInstruction asm.Instruction, baseReg asm.Register, offset asm.ConstantValue)
|
||||
|
||||
// CompileRegisterToRegisterWithArg adds an instruction where source and destination
|
||||
// are `from` and `to` registers.
|
||||
CompileRegisterToRegisterWithArg(instruction asm.Instruction, from, to asm.Register, arg byte)
|
||||
|
||||
// CompileMemoryWithIndexToRegister adds an instruction where source operand is the memory address
|
||||
// specified as `srcBaseReg + srcOffsetConst + srcIndex*srcScale` and destination is the register `dstReg`.
|
||||
// Note: sourceScale must be one of 1, 2, 4, 8.
|
||||
CompileMemoryWithIndexToRegister(
|
||||
instruction asm.Instruction,
|
||||
srcBaseReg asm.Register,
|
||||
srcOffsetConst int64,
|
||||
srcIndex asm.Register,
|
||||
srcScale int16,
|
||||
dstReg asm.Register,
|
||||
)
|
||||
|
||||
// CompileMemoryWithIndexAndArgToRegister is the same as CompileMemoryWithIndexToRegister except that this
|
||||
// also accepts one argument.
|
||||
CompileMemoryWithIndexAndArgToRegister(
|
||||
instruction asm.Instruction,
|
||||
srcBaseReg asm.Register,
|
||||
srcOffsetConst int64,
|
||||
srcIndex asm.Register,
|
||||
srcScale int16,
|
||||
dstReg asm.Register,
|
||||
arg byte,
|
||||
)
|
||||
|
||||
// CompileRegisterToMemoryWithIndex adds an instruction where source operand is the register `srcReg`,
|
||||
// and the destination is the memory address specified as `dstBaseReg + dstOffsetConst + dstIndex*dstScale`
|
||||
// Note: dstScale must be one of 1, 2, 4, 8.
|
||||
CompileRegisterToMemoryWithIndex(
|
||||
instruction asm.Instruction,
|
||||
srcReg asm.Register,
|
||||
dstBaseReg asm.Register,
|
||||
dstOffsetConst int64,
|
||||
dstIndex asm.Register,
|
||||
dstScale int16,
|
||||
)
|
||||
|
||||
// CompileRegisterToMemoryWithIndexAndArg is the same as CompileRegisterToMemoryWithIndex except that this
|
||||
// also accepts one argument.
|
||||
CompileRegisterToMemoryWithIndexAndArg(
|
||||
instruction asm.Instruction,
|
||||
srcReg asm.Register,
|
||||
dstBaseReg asm.Register,
|
||||
dstOffsetConst int64,
|
||||
dstIndex asm.Register,
|
||||
dstScale int16,
|
||||
arg byte,
|
||||
)
|
||||
|
||||
// CompileRegisterToConst adds an instruction where source operand is the register `srcRegister`,
|
||||
// and the destination is the const `value`.
|
||||
CompileRegisterToConst(instruction asm.Instruction, srcRegister asm.Register, value int64) asm.Node
|
||||
|
||||
// CompileRegisterToNone adds an instruction where source operand is the register `register`,
|
||||
// and there's no destination operand.
|
||||
CompileRegisterToNone(instruction asm.Instruction, register asm.Register)
|
||||
|
||||
// CompileNoneToRegister adds an instruction where destination operand is the register `register`,
|
||||
// and there's no source operand.
|
||||
CompileNoneToRegister(instruction asm.Instruction, register asm.Register)
|
||||
|
||||
// CompileNoneToMemory adds an instruction where destination operand is the memory address specified
|
||||
// as `baseReg+offset`. and there's no source operand.
|
||||
CompileNoneToMemory(instruction asm.Instruction, baseReg asm.Register, offset int64)
|
||||
|
||||
// CompileConstToMemory adds an instruction where source operand is the constant `value` and
|
||||
// the destination is the memory address specified as `dstBaseReg+dstOffset`.
|
||||
CompileConstToMemory(instruction asm.Instruction, value int64, dstBaseReg asm.Register, dstOffset int64) asm.Node
|
||||
|
||||
// CompileMemoryToConst adds an instruction where source operand is the memory address, and
|
||||
// the destination is the constant `value`.
|
||||
CompileMemoryToConst(instruction asm.Instruction, srcBaseReg asm.Register, srcOffset int64, value int64) asm.Node
|
||||
|
||||
// CompileStaticConstToRegister adds an instruction where the source operand is asm.StaticConst located in the
|
||||
// memory and the destination is the dstReg.
|
||||
CompileStaticConstToRegister(instruction asm.Instruction, c *asm.StaticConst, dstReg asm.Register) error
|
||||
|
||||
// CompileRegisterToStaticConst adds an instruction where the destination operand is asm.StaticConst located in the
|
||||
// memory and the source is the srcReg.
|
||||
CompileRegisterToStaticConst(instruction asm.Instruction, srcReg asm.Register, c *asm.StaticConst) error
|
||||
}
|
||||
-1248
File diff suppressed because it is too large.
Load diff
-2812
File diff suppressed because it is too large.
Load diff
Generated
Vendored
-181
@@ -1,181 +0,0 @@
|
||||
package amd64
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/asm"
|
||||
)
|
||||
|
||||
// defaultMaxDisplacementForConstantPool is the maximum displacement allowed for literal move instructions which access
|
||||
// the constant pool. This is set as 2 ^30 conservatively while the actual limit is 2^31 since we actually allow this
|
||||
// limit plus max(length(c) for c in the pool) so we must ensure that limit is less than 2^31.
|
||||
const defaultMaxDisplacementForConstantPool = 1 << 30
|
||||
|
||||
func (a *AssemblerImpl) maybeFlushConstants(buf asm.Buffer, isEndOfFunction bool) {
|
||||
if a.pool.Empty() {
|
||||
return
|
||||
}
|
||||
|
||||
if isEndOfFunction ||
|
||||
// If the distance between (the first use in binary) and (end of constant pool) can be larger
|
||||
// than MaxDisplacementForConstantPool, we have to emit the constant pool now, otherwise
|
||||
// a const might be unreachable by a literal move whose maximum offset is +- 2^31.
|
||||
((a.pool.PoolSizeInBytes+buf.Len())-int(a.pool.FirstUseOffsetInBinary)) >= a.MaxDisplacementForConstantPool {
|
||||
|
||||
if !isEndOfFunction {
|
||||
// Adds the jump instruction to skip the constants if this is not the end of function.
|
||||
//
|
||||
// TODO: consider NOP padding for this jump, though this rarely happens as most functions should be
|
||||
// small enough to fit all consts after the end of function.
|
||||
if a.pool.PoolSizeInBytes >= math.MaxInt8-2 {
|
||||
// long (near-relative) jump: https://www.felixcloutier.com/x86/jmp
|
||||
buf.AppendByte(0xe9)
|
||||
buf.AppendUint32(uint32(a.pool.PoolSizeInBytes))
|
||||
} else {
|
||||
// short jump: https://www.felixcloutier.com/x86/jmp
|
||||
buf.AppendByte(0xeb)
|
||||
buf.AppendByte(byte(a.pool.PoolSizeInBytes))
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range a.pool.Consts {
|
||||
c.SetOffsetInBinary(uint64(buf.Len()))
|
||||
buf.AppendBytes(c.Raw)
|
||||
}
|
||||
|
||||
a.pool.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AssemblerImpl) encodeRegisterToStaticConst(buf asm.Buffer, n *nodeImpl) (err error) {
|
||||
var opc []byte
|
||||
var rex byte
|
||||
switch n.instruction {
|
||||
case CMPL:
|
||||
opc, rex = []byte{0x3b}, rexPrefixNone
|
||||
case CMPQ:
|
||||
opc, rex = []byte{0x3b}, rexPrefixW
|
||||
default:
|
||||
return errorEncodingUnsupported(n)
|
||||
}
|
||||
return a.encodeStaticConstImpl(buf, n, opc, rex, 0)
|
||||
}
|
||||
|
||||
var staticConstToRegisterOpcodes = [...]struct {
|
||||
opcode, vopcode []byte
|
||||
mandatoryPrefix, vmandatoryPrefix byte
|
||||
rex rexPrefix
|
||||
}{
|
||||
// https://www.felixcloutier.com/x86/movdqu:vmovdqu8:vmovdqu16:vmovdqu32:vmovdqu64
|
||||
MOVDQU: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x6f}},
|
||||
// https://www.felixcloutier.com/x86/lea
|
||||
LEAQ: {opcode: []byte{0x8d}, rex: rexPrefixW},
|
||||
// https://www.felixcloutier.com/x86/movupd
|
||||
MOVUPD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x10}},
|
||||
// https://www.felixcloutier.com/x86/mov
|
||||
MOVL: {opcode: []byte{0x8b}, vopcode: []byte{0x0f, 0x6e}, vmandatoryPrefix: 0x66},
|
||||
MOVQ: {opcode: []byte{0x8b}, rex: rexPrefixW, vopcode: []byte{0x0f, 0x7e}, vmandatoryPrefix: 0xf3},
|
||||
// https://www.felixcloutier.com/x86/ucomisd
|
||||
UCOMISD: {opcode: []byte{0x0f, 0x2e}, mandatoryPrefix: 0x66},
|
||||
// https://www.felixcloutier.com/x86/ucomiss
|
||||
UCOMISS: {opcode: []byte{0x0f, 0x2e}},
|
||||
// https://www.felixcloutier.com/x86/subss
|
||||
SUBSS: {opcode: []byte{0x0f, 0x5c}, mandatoryPrefix: 0xf3},
|
||||
// https://www.felixcloutier.com/x86/subsd
|
||||
SUBSD: {opcode: []byte{0x0f, 0x5c}, mandatoryPrefix: 0xf2},
|
||||
// https://www.felixcloutier.com/x86/cmp
|
||||
CMPL: {opcode: []byte{0x39}},
|
||||
CMPQ: {opcode: []byte{0x39}, rex: rexPrefixW},
|
||||
// https://www.felixcloutier.com/x86/add
|
||||
ADDL: {opcode: []byte{0x03}},
|
||||
ADDQ: {opcode: []byte{0x03}, rex: rexPrefixW},
|
||||
}
|
||||
|
||||
func (a *AssemblerImpl) encodeStaticConstToRegister(buf asm.Buffer, n *nodeImpl) (err error) {
|
||||
var opc []byte
|
||||
var rex, mandatoryPrefix byte
|
||||
info := staticConstToRegisterOpcodes[n.instruction]
|
||||
switch n.instruction {
|
||||
case MOVL, MOVQ:
|
||||
if isVectorRegister(n.dstReg) {
|
||||
opc, mandatoryPrefix = info.vopcode, info.vmandatoryPrefix
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
opc, rex, mandatoryPrefix = info.opcode, info.rex, info.mandatoryPrefix
|
||||
}
|
||||
return a.encodeStaticConstImpl(buf, n, opc, rex, mandatoryPrefix)
|
||||
}
|
||||
|
||||
// encodeStaticConstImpl encodes an instruction where mod:r/m points to the memory location of the static constant n.staticConst,
|
||||
// and the other operand is the register given at n.srcReg or n.dstReg.
|
||||
func (a *AssemblerImpl) encodeStaticConstImpl(buf asm.Buffer, n *nodeImpl, opcode []byte, rex rexPrefix, mandatoryPrefix byte) error {
|
||||
a.pool.AddConst(n.staticConst, uint64(buf.Len()))
|
||||
|
||||
var reg asm.Register
|
||||
if n.dstReg != asm.NilRegister {
|
||||
reg = n.dstReg
|
||||
} else {
|
||||
reg = n.srcReg
|
||||
}
|
||||
|
||||
reg3Bits, rexPrefix := register3bits(reg, registerSpecifierPositionModRMFieldReg)
|
||||
rexPrefix |= rex
|
||||
|
||||
base := buf.Len()
|
||||
code := buf.Append(len(opcode) + 7)[:0]
|
||||
|
||||
if mandatoryPrefix != 0 {
|
||||
code = append(code, mandatoryPrefix)
|
||||
}
|
||||
|
||||
if rexPrefix != rexPrefixNone {
|
||||
code = append(code, rexPrefix)
|
||||
}
|
||||
|
||||
code = append(code, opcode...)
|
||||
|
||||
// https://wiki.osdev.org/X86-64_Instruction_Encoding#32.2F64-bit_addressing
|
||||
modRM := 0b00_000_101 | // Indicate "[RIP + 32bit displacement]" encoding.
|
||||
(reg3Bits << 3) // Place the reg on ModRM:reg.
|
||||
code = append(code, modRM)
|
||||
|
||||
// Preserve 4 bytes for displacement which will be filled after we finalize the location.
|
||||
code = append(code, 0, 0, 0, 0)
|
||||
|
||||
if !n.staticConstReferrersAdded {
|
||||
a.staticConstReferrers = append(a.staticConstReferrers, staticConstReferrer{n: n, instLen: len(code)})
|
||||
n.staticConstReferrersAdded = true
|
||||
}
|
||||
|
||||
buf.Truncate(base + len(code))
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompileStaticConstToRegister implements Assembler.CompileStaticConstToRegister.
|
||||
func (a *AssemblerImpl) CompileStaticConstToRegister(instruction asm.Instruction, c *asm.StaticConst, dstReg asm.Register) (err error) {
|
||||
if len(c.Raw)%2 != 0 {
|
||||
err = fmt.Errorf("the length of a static constant must be even but was %d", len(c.Raw))
|
||||
return
|
||||
}
|
||||
|
||||
n := a.newNode(instruction, operandTypesStaticConstToRegister)
|
||||
n.dstReg = dstReg
|
||||
n.staticConst = c
|
||||
return
|
||||
}
|
||||
|
||||
// CompileRegisterToStaticConst implements Assembler.CompileRegisterToStaticConst.
|
||||
func (a *AssemblerImpl) CompileRegisterToStaticConst(instruction asm.Instruction, srcReg asm.Register, c *asm.StaticConst) (err error) {
|
||||
if len(c.Raw)%2 != 0 {
|
||||
err = fmt.Errorf("the length of a static constant must be even but was %d", len(c.Raw))
|
||||
return
|
||||
}
|
||||
|
||||
n := a.newNode(instruction, operandTypesRegisterToStaticConst)
|
||||
n.srcReg = srcReg
|
||||
n.staticConst = c
|
||||
return
|
||||
}
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
package arm64
|
||||
|
||||
import (
|
||||
"github.com/tetratelabs/wazero/internal/asm"
|
||||
)
|
||||
|
||||
// Assembler is the interface for arm64 specific assembler.
|
||||
type Assembler interface {
|
||||
asm.AssemblerBase
|
||||
|
||||
// CompileMemoryWithRegisterOffsetToRegister adds an instruction where source operand is the memory address
|
||||
// specified as `srcBaseReg + srcOffsetReg` and dst is the register `dstReg`.
|
||||
CompileMemoryWithRegisterOffsetToRegister(instruction asm.Instruction, srcBaseReg, srcOffsetReg, dstReg asm.Register)
|
||||
|
||||
// CompileRegisterToMemoryWithRegisterOffset adds an instruction where source operand is the register `srcReg`,
|
||||
// and the destination is the memory address specified as `dstBaseReg + dstOffsetReg`
|
||||
CompileRegisterToMemoryWithRegisterOffset(instruction asm.Instruction, srcReg, dstBaseReg, dstOffsetReg asm.Register)
|
||||
|
||||
// CompileTwoRegistersToRegister adds an instruction where source operands consists of two registers `src1` and `src2`,
|
||||
// and the destination is the register `dst`.
|
||||
CompileTwoRegistersToRegister(instruction asm.Instruction, src1, src2, dst asm.Register)
|
||||
|
||||
// CompileThreeRegistersToRegister adds an instruction where source operands consist of three registers
|
||||
// `src1`, `src2` and `src3`, and destination operands consist of `dst` register.
|
||||
CompileThreeRegistersToRegister(instruction asm.Instruction, src1, src2, src3, dst asm.Register)
|
||||
|
||||
// CompileTwoRegistersToNone adds an instruction where source operands consist of two registers `src1` and `src2`,
|
||||
// and destination operand is unspecified.
|
||||
CompileTwoRegistersToNone(instruction asm.Instruction, src1, src2 asm.Register)
|
||||
|
||||
// CompileRegisterAndConstToNone adds an instruction where source operands consist of one register `src` and
|
||||
// constant `srcConst`, and destination operand is unspecified.
|
||||
CompileRegisterAndConstToNone(instruction asm.Instruction, src asm.Register, srcConst asm.ConstantValue)
|
||||
|
||||
// CompileRegisterAndConstToRegister adds an instruction where source operands consist of one register `src` and
|
||||
// constant `srcConst`, and destination operand is a register `dst`.
|
||||
CompileRegisterAndConstToRegister(instruction asm.Instruction, src asm.Register, srcConst asm.ConstantValue, dst asm.Register)
|
||||
|
||||
// CompileLeftShiftedRegisterToRegister adds an instruction where source operand is the "left shifted register"
|
||||
// represented as `srcReg << shiftNum` and the destination is the register `dstReg`.
|
||||
CompileLeftShiftedRegisterToRegister(
|
||||
instruction asm.Instruction,
|
||||
shiftedSourceReg asm.Register,
|
||||
shiftNum asm.ConstantValue,
|
||||
srcReg, dstReg asm.Register,
|
||||
)
|
||||
|
||||
// CompileConditionalRegisterSet adds an instruction to set 1 on dstReg if the condition satisfies,
|
||||
// otherwise set 0.
|
||||
CompileConditionalRegisterSet(cond asm.ConditionalRegisterState, dstReg asm.Register)
|
||||
|
||||
// CompileMemoryToVectorRegister adds an instruction where source operands is the memory address specified by
|
||||
// `srcBaseReg+srcOffset` and the destination is `dstReg` vector register.
|
||||
CompileMemoryToVectorRegister(instruction asm.Instruction, srcBaseReg asm.Register, srcOffset asm.ConstantValue,
|
||||
dstReg asm.Register, arrangement VectorArrangement)
|
||||
|
||||
// CompileMemoryWithRegisterOffsetToVectorRegister is the same as CompileMemoryToVectorRegister except that the
|
||||
// offset is specified by the `srcOffsetRegister` register.
|
||||
CompileMemoryWithRegisterOffsetToVectorRegister(instruction asm.Instruction, srcBaseReg,
|
||||
srcOffsetRegister asm.Register, dstReg asm.Register, arrangement VectorArrangement)
|
||||
|
||||
// CompileVectorRegisterToMemory adds an instruction where source operand is `srcReg` vector register and the
|
||||
// destination is the memory address specified by `dstBaseReg+dstOffset`.
|
||||
CompileVectorRegisterToMemory(instruction asm.Instruction, srcReg, dstBaseReg asm.Register,
|
||||
dstOffset asm.ConstantValue, arrangement VectorArrangement)
|
||||
|
||||
// CompileVectorRegisterToMemoryWithRegisterOffset is the same as CompileVectorRegisterToMemory except that the
|
||||
// offset is specified by the `dstOffsetRegister` register.
|
||||
CompileVectorRegisterToMemoryWithRegisterOffset(instruction asm.Instruction, srcReg, dstBaseReg,
|
||||
dstOffsetRegister asm.Register, arrangement VectorArrangement)
|
||||
|
||||
// CompileRegisterToVectorRegister adds an instruction where source operand is `srcReg` general purpose register and
|
||||
// the destination is the `dstReg` vector register. The destination vector's arrangement and index of element can be
|
||||
// given by `arrangement` and `index`, but not all the instructions will use them.
|
||||
CompileRegisterToVectorRegister(instruction asm.Instruction, srcReg, dstReg asm.Register,
|
||||
arrangement VectorArrangement, index VectorIndex)
|
||||
|
||||
// CompileVectorRegisterToRegister adds an instruction where destination operand is `dstReg` general purpose register
|
||||
// and the source is the `srcReg` vector register. The source vector's arrangement and index of element can be
|
||||
// given by `arrangement` and `index`, but not all the instructions will use them.
|
||||
CompileVectorRegisterToRegister(instruction asm.Instruction, srcReg, dstReg asm.Register,
|
||||
arrangement VectorArrangement, index VectorIndex)
|
||||
|
||||
// CompileVectorRegisterToVectorRegister adds an instruction where both source and destination operands are vector
|
||||
// registers. The vector's arrangement can be specified `arrangement`, and the source and destination element's
|
||||
// index are given by `srcIndex` and `dstIndex` respectively, but not all the instructions will use them.
|
||||
CompileVectorRegisterToVectorRegister(instruction asm.Instruction, srcReg, dstReg asm.Register,
|
||||
arrangement VectorArrangement, srcIndex, dstIndex VectorIndex)
|
||||
|
||||
// CompileVectorRegisterToVectorRegisterWithConst is the same as CompileVectorRegisterToVectorRegister but the
|
||||
// additional constant can be provided.
|
||||
// For example, the const can be used to specify the shift amount for USHLL instruction.
|
||||
CompileVectorRegisterToVectorRegisterWithConst(instruction asm.Instruction, srcReg, dstReg asm.Register,
|
||||
arrangement VectorArrangement, c asm.ConstantValue)
|
||||
|
||||
// CompileStaticConstToRegister adds an instruction where the source operand is StaticConstant located in
|
||||
// the memory and the destination is the dstReg.
|
||||
CompileStaticConstToRegister(instruction asm.Instruction, c *asm.StaticConst, dstReg asm.Register)
|
||||
|
||||
// CompileStaticConstToVectorRegister adds an instruction where the source operand is StaticConstant located in
|
||||
// the memory and the destination is the dstReg.
|
||||
CompileStaticConstToVectorRegister(instruction asm.Instruction, c *asm.StaticConst, dstReg asm.Register,
|
||||
arrangement VectorArrangement)
|
||||
|
||||
// CompileTwoVectorRegistersToVectorRegister adds an instruction where source are two vectors and destination is one
|
||||
// vector. The vector's arrangement can be specified `arrangement`.
|
||||
CompileTwoVectorRegistersToVectorRegister(instruction asm.Instruction, srcReg, srcReg2, dstReg asm.Register,
|
||||
arrangement VectorArrangement)
|
||||
|
||||
// CompileTwoVectorRegistersToVectorRegisterWithConst is the same as CompileTwoVectorRegistersToVectorRegister except
|
||||
// that this also accept additional constant.
|
||||
// For example EXIT instruction needs the extraction target immediate as const.
|
||||
CompileTwoVectorRegistersToVectorRegisterWithConst(instruction asm.Instruction, srcReg, srcReg2, dstReg asm.Register,
|
||||
arrangement VectorArrangement, c asm.ConstantValue)
|
||||
}
|
||||
-1472
File diff suppressed because it is too large.
Load diff
-3898
File diff suppressed because it is too large.
Load diff
-209
@@ -1,209 +0,0 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Register represents architecture-specific registers.
|
||||
type Register byte
|
||||
|
||||
// NilRegister is the only architecture-independent register, and
|
||||
// can be used to indicate that no register is specified.
|
||||
const NilRegister Register = 0
|
||||
|
||||
// Instruction represents architecture-specific instructions.
|
||||
type Instruction uint16 // to accommodate the high cardinality of vector ops
|
||||
|
||||
// ConditionalRegisterState represents architecture-specific conditional
|
||||
// register's states.
|
||||
type ConditionalRegisterState byte
|
||||
|
||||
// ConditionalRegisterStateUnset is the only architecture-independent conditional state, and
|
||||
// can be used to indicate that no conditional state is specified.
|
||||
const ConditionalRegisterStateUnset ConditionalRegisterState = 0
|
||||
|
||||
// Node represents a node in the linked list of assembled operations.
|
||||
type Node interface {
|
||||
fmt.Stringer
|
||||
|
||||
// AssignJumpTarget assigns the given target node as the destination of
|
||||
// jump instruction for this Node.
|
||||
AssignJumpTarget(target Node)
|
||||
|
||||
// AssignDestinationConstant assigns the given constant as the destination
|
||||
// of the instruction for this node.
|
||||
AssignDestinationConstant(value ConstantValue)
|
||||
|
||||
// AssignSourceConstant assigns the given constant as the source
|
||||
// of the instruction for this node.
|
||||
AssignSourceConstant(value ConstantValue)
|
||||
|
||||
// OffsetInBinary returns the offset of this node in the assembled binary.
|
||||
OffsetInBinary() NodeOffsetInBinary
|
||||
}
|
||||
|
||||
// NodeOffsetInBinary represents an offset of this node in the final binary.
|
||||
type NodeOffsetInBinary = uint64
|
||||
|
||||
// ConstantValue represents a constant value used in an instruction.
|
||||
type ConstantValue = int64
|
||||
|
||||
// StaticConst represents an arbitrary constant bytes which are pooled and emitted by assembler into the binary.
|
||||
// These constants can be referenced by instructions.
|
||||
type StaticConst struct {
|
||||
// offsetFinalizedCallbacks holds callbacks which are called when .OffsetInBinary is finalized by assembler implementation.
|
||||
offsetFinalizedCallbacks []func(offsetOfConstInBinary uint64)
|
||||
|
||||
Raw []byte
|
||||
// OffsetInBinary is the offset of this static const in the result binary.
|
||||
OffsetInBinary uint64
|
||||
}
|
||||
|
||||
// NewStaticConst returns the pointer to the new NewStaticConst for given bytes.
|
||||
func NewStaticConst(raw []byte) *StaticConst {
|
||||
return &StaticConst{Raw: raw}
|
||||
}
|
||||
|
||||
// AddOffsetFinalizedCallback adds a callback into offsetFinalizedCallbacks.
|
||||
func (s *StaticConst) AddOffsetFinalizedCallback(cb func(offsetOfConstInBinary uint64)) {
|
||||
s.offsetFinalizedCallbacks = append(s.offsetFinalizedCallbacks, cb)
|
||||
}
|
||||
|
||||
// SetOffsetInBinary finalizes the offset of this StaticConst, and invokes callbacks.
|
||||
func (s *StaticConst) SetOffsetInBinary(offset uint64) {
|
||||
s.OffsetInBinary = offset
|
||||
for _, cb := range s.offsetFinalizedCallbacks {
|
||||
cb(offset)
|
||||
}
|
||||
}
|
||||
|
||||
// StaticConstPool holds a bulk of StaticConst which are yet to be emitted into the binary.
|
||||
type StaticConstPool struct {
|
||||
// addedConsts is used to deduplicate the consts to reduce the final size of binary.
|
||||
// Note: we can use map on .consts field and remove this field,
|
||||
// but we have the separate field for deduplication in order to have deterministic assembling behavior.
|
||||
addedConsts map[*StaticConst]struct{}
|
||||
|
||||
Consts []*StaticConst
|
||||
// FirstUseOffsetInBinary holds the offset of the first instruction which accesses this const pool .
|
||||
FirstUseOffsetInBinary NodeOffsetInBinary
|
||||
// PoolSizeInBytes is the current size of the pool in bytes.
|
||||
PoolSizeInBytes int
|
||||
}
|
||||
|
||||
func NewStaticConstPool() StaticConstPool {
|
||||
return StaticConstPool{addedConsts: map[*StaticConst]struct{}{}, FirstUseOffsetInBinary: math.MaxUint64}
|
||||
}
|
||||
|
||||
// Reset resets the *StaticConstPool for reuse.
|
||||
func (p *StaticConstPool) Reset() {
|
||||
for _, c := range p.Consts {
|
||||
delete(p.addedConsts, c)
|
||||
}
|
||||
// Reuse the slice to avoid re-allocations.
|
||||
p.Consts = p.Consts[:0]
|
||||
p.PoolSizeInBytes = 0
|
||||
p.FirstUseOffsetInBinary = math.MaxUint64
|
||||
}
|
||||
|
||||
// Empty returns true if StaticConstPool is empty.
|
||||
func (p *StaticConstPool) Empty() bool {
|
||||
return p.FirstUseOffsetInBinary == math.MaxUint64
|
||||
}
|
||||
|
||||
// AddConst adds a *StaticConst into the pool if it's not already added.
|
||||
func (p *StaticConstPool) AddConst(c *StaticConst, useOffset NodeOffsetInBinary) {
|
||||
if _, ok := p.addedConsts[c]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
if p.Empty() {
|
||||
p.FirstUseOffsetInBinary = useOffset
|
||||
}
|
||||
|
||||
c.offsetFinalizedCallbacks = c.offsetFinalizedCallbacks[:0]
|
||||
|
||||
p.Consts = append(p.Consts, c)
|
||||
p.PoolSizeInBytes += len(c.Raw)
|
||||
p.addedConsts[c] = struct{}{}
|
||||
}
|
||||
|
||||
// AssemblerBase is the common interface for assemblers among multiple architectures.
|
||||
//
|
||||
// Note: some of them can be implemented in an arch-independent way, but not all can be
|
||||
// implemented as such. However, we intentionally put such arch-dependant methods here
|
||||
// in order to provide the common documentation interface.
|
||||
type AssemblerBase interface {
|
||||
// Reset resets the state of Assembler implementation and mark it ready for
|
||||
// the compilation of the new function compilation.
|
||||
Reset()
|
||||
|
||||
// Assemble produces the final binary for the assembled operations.
|
||||
Assemble(Buffer) error
|
||||
|
||||
// SetJumpTargetOnNext instructs the assembler that the next node must be
|
||||
// assigned to the given node's jump destination.
|
||||
SetJumpTargetOnNext(node Node)
|
||||
|
||||
// BuildJumpTable calculates the offsets between the first instruction `initialInstructions[0]`
|
||||
// and others (e.g. initialInstructions[3]), and wrote the calculated offsets into pre-allocated
|
||||
// `table` StaticConst in little endian.
|
||||
BuildJumpTable(table *StaticConst, initialInstructions []Node)
|
||||
|
||||
// AllocateNOP allocates Node for NOP instruction.
|
||||
AllocateNOP() Node
|
||||
|
||||
// Add appends the given `Node` in the assembled linked list.
|
||||
Add(Node)
|
||||
|
||||
// CompileStandAlone adds an instruction to take no arguments.
|
||||
CompileStandAlone(instruction Instruction) Node
|
||||
|
||||
// CompileConstToRegister adds an instruction where source operand is `value` as constant and destination is `destinationReg` register.
|
||||
CompileConstToRegister(instruction Instruction, value ConstantValue, destinationReg Register) Node
|
||||
|
||||
// CompileRegisterToRegister adds an instruction where source and destination operands are registers.
|
||||
CompileRegisterToRegister(instruction Instruction, from, to Register)
|
||||
|
||||
// CompileMemoryToRegister adds an instruction where source operands is the memory address specified by `sourceBaseReg+sourceOffsetConst`
|
||||
// and the destination is `destinationReg` register.
|
||||
CompileMemoryToRegister(
|
||||
instruction Instruction,
|
||||
sourceBaseReg Register,
|
||||
sourceOffsetConst ConstantValue,
|
||||
destinationReg Register,
|
||||
)
|
||||
|
||||
// CompileRegisterToMemory adds an instruction where source operand is `sourceRegister` register and the destination is the
|
||||
// memory address specified by `destinationBaseRegister+destinationOffsetConst`.
|
||||
CompileRegisterToMemory(
|
||||
instruction Instruction,
|
||||
sourceRegister Register,
|
||||
destinationBaseRegister Register,
|
||||
destinationOffsetConst ConstantValue,
|
||||
)
|
||||
|
||||
// CompileJump adds jump-type instruction and returns the corresponding Node in the assembled linked list.
|
||||
CompileJump(jmpInstruction Instruction) Node
|
||||
|
||||
// CompileJumpToRegister adds jump-type instruction whose destination is the memory address specified by `reg` register.
|
||||
CompileJumpToRegister(jmpInstruction Instruction, reg Register)
|
||||
|
||||
// CompileReadInstructionAddress adds an ADR instruction to set the absolute address of "target instruction"
|
||||
// into destinationRegister. "target instruction" is specified by beforeTargetInst argument and
|
||||
// the target is determined by "the instruction right after beforeTargetInst type".
|
||||
//
|
||||
// For example, if `beforeTargetInst == RET` and we have the instruction sequence like
|
||||
// `ADR -> X -> Y -> ... -> RET -> MOV`, then the `ADR` instruction emitted by this function set the absolute
|
||||
// address of `MOV` instruction into the destination register.
|
||||
CompileReadInstructionAddress(destinationRegister Register, beforeAcquisitionTargetInstruction Instruction)
|
||||
}
|
||||
|
||||
// JumpTableMaximumOffset represents the limit on the size of jump table in bytes.
|
||||
// When users try loading an extremely large WebAssembly binary which contains a br_table
|
||||
// statement with approximately 4294967296 (2^32) targets. Realistically speaking, that kind of binary
|
||||
// could result in more than ten gigabytes of native compiled code where we have to care about
|
||||
// huge stacks whose height might exceed 32-bit range, and such huge stack doesn't work with the
|
||||
// current implementation.
|
||||
const JumpTableMaximumOffset = math.MaxUint32
|
||||
-235
@@ -1,235 +0,0 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
)
|
||||
|
||||
var zero [16]byte
|
||||
|
||||
// CodeSegment represents a memory mapped segment where native CPU instructions
|
||||
// are written.
|
||||
//
|
||||
// To construct code segments, the program must call Next to obtain a buffer
|
||||
// view capable of writing data at the end of the segment. Next must be called
|
||||
// before generating the code of a function because it aligns the next write on
|
||||
// 16 bytes.
|
||||
//
|
||||
// Instances of CodeSegment hold references to memory which is NOT managed by
|
||||
// the garbage collector and therefore must be released *manually* by calling
|
||||
// their Unmap method to prevent memory leaks.
|
||||
//
|
||||
// The zero value is a valid, empty code segment, equivalent to being
|
||||
// constructed by calling NewCodeSegment(nil).
|
||||
type CodeSegment struct {
|
||||
code []byte
|
||||
size int
|
||||
}
|
||||
|
||||
// NewCodeSegment constructs a CodeSegment value from a byte slice.
|
||||
//
|
||||
// No validation is made that the byte slice is a memory mapped region which can
|
||||
// be unmapped on Close.
|
||||
func NewCodeSegment(code []byte) *CodeSegment {
|
||||
return &CodeSegment{code: code, size: len(code)}
|
||||
}
|
||||
|
||||
// Map allocates a memory mapping of the given size to the code segment.
|
||||
//
|
||||
// Note that programs only need to use this method to initialize the code
|
||||
// segment to a specific content (e.g. when loading pre-compiled code from a
|
||||
// file), otherwise the backing memory mapping is allocated on demand when code
|
||||
// is written to the code segment via Buffers returned by calls to Next.
|
||||
//
|
||||
// The method errors is the segment is already backed by a memory mapping.
|
||||
func (seg *CodeSegment) Map(size int) error {
|
||||
if seg.code != nil {
|
||||
return fmt.Errorf("code segment already initialized to memory mapping of size %d", len(seg.code))
|
||||
}
|
||||
b, err := platform.MmapCodeSegment(size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seg.code = b
|
||||
seg.size = size
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close unmaps the underlying memory region held by the code segment, clearing
|
||||
// its state back to an empty code segment.
|
||||
//
|
||||
// The value is still usable after unmapping its memory, a new memory area can
|
||||
// be allocated by calling Map or writing to the segment.
|
||||
func (seg *CodeSegment) Unmap() error {
|
||||
if seg.code != nil {
|
||||
if err := platform.MunmapCodeSegment(seg.code[:cap(seg.code)]); err != nil {
|
||||
return err
|
||||
}
|
||||
seg.code = nil
|
||||
seg.size = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Addr returns the address of the beginning of the code segment as a uintptr.
|
||||
func (seg *CodeSegment) Addr() uintptr {
|
||||
if len(seg.code) > 0 {
|
||||
return uintptr(unsafe.Pointer(&seg.code[0]))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Size returns the size of code segment, which is less or equal to the length
|
||||
// of the byte slice returned by Len or Bytes.
|
||||
func (seg *CodeSegment) Size() uintptr {
|
||||
return uintptr(seg.size)
|
||||
}
|
||||
|
||||
// Len returns the length of the byte slice referencing the memory mapping of
|
||||
// the code segment.
|
||||
func (seg *CodeSegment) Len() int {
|
||||
return len(seg.code)
|
||||
}
|
||||
|
||||
// Bytes returns a byte slice to the memory mapping of the code segment.
|
||||
//
|
||||
// The returned slice remains valid until more bytes are written to a buffer
|
||||
// of the code segment, or Unmap is called.
|
||||
func (seg *CodeSegment) Bytes() []byte {
|
||||
return seg.code
|
||||
}
|
||||
|
||||
// Next returns a buffer pointed at the end of the code segment to support
|
||||
// writing more code instructions to it.
|
||||
//
|
||||
// Buffers are passed by value, but they hold a reference to the code segment
|
||||
// that they were created from.
|
||||
func (seg *CodeSegment) NextCodeSection() Buffer {
|
||||
// Align 16-bytes boundary.
|
||||
seg.AppendBytes(zero[:seg.size&15])
|
||||
return Buffer{CodeSegment: seg, off: seg.size}
|
||||
}
|
||||
|
||||
// Append appends n bytes to the code segment, returning a slice to the appended
|
||||
// memory region.
|
||||
//
|
||||
// The underlying code segment may be reallocated if it was too short to hold
|
||||
// n more bytes, which invalidates any addresses previously returned by calls
|
||||
// to Addr.
|
||||
func (seg *CodeSegment) Append(n int) []byte {
|
||||
seg.size += n
|
||||
if seg.size > len(seg.code) {
|
||||
seg.growToSize()
|
||||
}
|
||||
return seg.code[seg.size-n:]
|
||||
}
|
||||
|
||||
// AppendByte appends a single byte to the code segment.
|
||||
//
|
||||
// The underlying code segment may be reallocated if it was too short to hold
|
||||
// one more byte, which invalidates any addresses previously returned by calls
|
||||
// to Addr.
|
||||
func (seg *CodeSegment) AppendByte(b byte) {
|
||||
seg.size++
|
||||
if seg.size > len(seg.code) {
|
||||
seg.growToSize()
|
||||
}
|
||||
seg.code[seg.size-1] = b
|
||||
}
|
||||
|
||||
// AppendBytes appends a copy of b to the code segment.
|
||||
//
|
||||
// The underlying code segment may be reallocated if it was too short to hold
|
||||
// len(b) more bytes, which invalidates any addresses previously returned by
|
||||
// calls to Addr.
|
||||
func (seg *CodeSegment) AppendBytes(b []byte) {
|
||||
copy(seg.Append(len(b)), b)
|
||||
}
|
||||
|
||||
// AppendUint32 appends a 32 bits integer to the code segment.
|
||||
//
|
||||
// The underlying code segment may be reallocated if it was too short to hold
|
||||
// four more bytes, which invalidates any addresses previously returned by calls
|
||||
// to Addr.
|
||||
func (seg *CodeSegment) AppendUint32(u uint32) {
|
||||
seg.size += 4
|
||||
if seg.size > len(seg.code) {
|
||||
seg.growToSize()
|
||||
}
|
||||
// This can be replaced by an unsafe operation to assign the uint32, which
|
||||
// keeps the function cost below the inlining threshold. However, it did not
|
||||
// show any improvements in arm64 benchmarks so we retained this safer code.
|
||||
binary.LittleEndian.PutUint32(seg.code[seg.size-4:], u)
|
||||
}
|
||||
|
||||
// growMode grows the code segment so that another section can be added to it.
|
||||
//
|
||||
// The method is marked go:noinline so that it doesn't get inline in Append,
|
||||
// and AppendByte, which keeps the inlining score of those methods low enough
|
||||
// that they can be inlined at the call sites.
|
||||
//
|
||||
//go:noinline
|
||||
func (seg *CodeSegment) growToSize() {
|
||||
seg.Grow(0)
|
||||
}
|
||||
|
||||
// Grow ensure that the capacity of the code segment is large enough to hold n
|
||||
// more bytes.
|
||||
//
|
||||
// The underlying code segment may be reallocated if it was too short, which
|
||||
// invalidates any addresses previously returned by calls to Addr.
|
||||
func (seg *CodeSegment) Grow(n int) {
|
||||
size := len(seg.code)
|
||||
want := seg.size + n
|
||||
if size >= want {
|
||||
return
|
||||
}
|
||||
if size == 0 {
|
||||
size = 65536
|
||||
}
|
||||
for size < want {
|
||||
size *= 2
|
||||
}
|
||||
b, err := platform.RemapCodeSegment(seg.code, size)
|
||||
if err != nil {
|
||||
// The only reason for growing the buffer to error is if we run
|
||||
// out of memory, so panic for now as it greatly simplifies error
|
||||
// handling to assume writing to the buffer would never fail.
|
||||
panic(err)
|
||||
}
|
||||
seg.code = b
|
||||
}
|
||||
|
||||
// Buffer is a reference type representing a section beginning at the end of a
|
||||
// code segment where new instructions can be written.
|
||||
type Buffer struct {
|
||||
*CodeSegment
|
||||
off int
|
||||
}
|
||||
|
||||
func (buf Buffer) Cap() int {
|
||||
return len(buf.code) - buf.off
|
||||
}
|
||||
|
||||
func (buf Buffer) Len() int {
|
||||
return buf.size - buf.off
|
||||
}
|
||||
|
||||
func (buf Buffer) Bytes() []byte {
|
||||
return buf.code[buf.off:buf.size:buf.size]
|
||||
}
|
||||
|
||||
func (buf Buffer) Reset() {
|
||||
buf.size = buf.off
|
||||
}
|
||||
|
||||
func (buf Buffer) Truncate(n int) {
|
||||
buf.size = buf.off + n
|
||||
}
|
||||
|
||||
func (buf Buffer) Append4Bytes(a, b, c, d byte) {
|
||||
buf.AppendUint32(uint32(a) | uint32(b)<<8 | uint32(c)<<16 | uint32(d)<<24)
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// BaseAssemblerImpl includes code common to all architectures.
|
||||
//
|
||||
// Note: When possible, add code here instead of in architecture-specific files to reduce drift:
|
||||
// As this is internal, exporting symbols only to reduce duplication is ok.
|
||||
type BaseAssemblerImpl struct {
|
||||
// SetBranchTargetOnNextNodes holds branch kind instructions (BR, conditional BR, etc.)
|
||||
// where we want to set the next coming instruction as the destination of these BR instructions.
|
||||
SetBranchTargetOnNextNodes []Node
|
||||
|
||||
// JumpTableEntries holds the information to build jump tables.
|
||||
JumpTableEntries []JumpTableEntry
|
||||
}
|
||||
|
||||
// JumpTableEntry is the necessary data to build a jump table.
|
||||
// This is exported for testing purpose.
|
||||
type JumpTableEntry struct {
|
||||
t *StaticConst
|
||||
labelInitialInstructions []Node
|
||||
}
|
||||
|
||||
// SetJumpTargetOnNext implements AssemblerBase.SetJumpTargetOnNext
|
||||
func (a *BaseAssemblerImpl) SetJumpTargetOnNext(node Node) {
|
||||
a.SetBranchTargetOnNextNodes = append(a.SetBranchTargetOnNextNodes, node)
|
||||
}
|
||||
|
||||
// BuildJumpTable implements AssemblerBase.BuildJumpTable
|
||||
func (a *BaseAssemblerImpl) BuildJumpTable(table *StaticConst, labelInitialInstructions []Node) {
|
||||
a.JumpTableEntries = append(a.JumpTableEntries, JumpTableEntry{
|
||||
t: table,
|
||||
labelInitialInstructions: labelInitialInstructions,
|
||||
})
|
||||
}
|
||||
|
||||
// FinalizeJumpTableEntry finalizes the build tables inside the given code.
|
||||
func (a *BaseAssemblerImpl) FinalizeJumpTableEntry(code []byte) (err error) {
|
||||
for i := range a.JumpTableEntries {
|
||||
ent := &a.JumpTableEntries[i]
|
||||
labelInitialInstructions := ent.labelInitialInstructions
|
||||
table := ent.t
|
||||
// Compile the offset table for each target.
|
||||
base := labelInitialInstructions[0].OffsetInBinary()
|
||||
for i, nop := range labelInitialInstructions {
|
||||
if nop.OffsetInBinary()-base >= JumpTableMaximumOffset {
|
||||
return fmt.Errorf("too large br_table")
|
||||
}
|
||||
// We store the offset from the beginning of the L0's initial instruction.
|
||||
binary.LittleEndian.PutUint32(code[table.OffsetInBinary+uint64(i*4):table.OffsetInBinary+uint64((i+1)*4)],
|
||||
uint32(nop.OffsetInBinary())-uint32(base))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
-224
@@ -1,224 +0,0 @@
|
||||
package bitpack
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// OffsetArray is an interface representing read-only views of arrays of 64 bits
|
||||
// offsets.
|
||||
type OffsetArray interface {
|
||||
// Returns the value at index i.
|
||||
//
|
||||
// The method complexity may be anywhere between O(1) and O(N).
|
||||
Index(i int) uint64
|
||||
// Returns the number of offsets in the array.
|
||||
//
|
||||
// The method complexity must be O(1).
|
||||
Len() int
|
||||
}
|
||||
|
||||
// OffsetArrayLen is a helper function to access the length of an offset array.
|
||||
// It is similar to calling Len on the array but handles the special case where
|
||||
// the array is nil, in which case it returns zero.
|
||||
func OffsetArrayLen(array OffsetArray) int {
|
||||
if array != nil {
|
||||
return array.Len()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// NewOffsetArray constructs a new array of offsets from the slice of values
|
||||
// passed as argument. The slice is not retained, the returned array always
|
||||
// holds a copy of the values.
|
||||
//
|
||||
// The underlying implementation of the offset array applies a compression
|
||||
// mechanism derived from Frame-of-Reference and Delta Encoding to minimize
|
||||
// the memory footprint of the array. This compression model works best when
|
||||
// the input is made of ordered values, otherwise the deltas between values
|
||||
// are likely to be too large to benefit from delta encoding.
|
||||
//
|
||||
// See https://lemire.me/blog/2012/02/08/effective-compression-using-frame-of-reference-and-delta-coding/
|
||||
func NewOffsetArray(values []uint64) OffsetArray {
|
||||
if len(values) == 0 {
|
||||
return emptyOffsetArray{}
|
||||
}
|
||||
if len(values) <= smallOffsetArrayCapacity {
|
||||
return newSmallOffsetArray(values)
|
||||
}
|
||||
|
||||
maxDelta := uint64(0)
|
||||
lastValue := values[0]
|
||||
// TODO: the pre-processing we perform here can be optimized using SIMD
|
||||
// instructions.
|
||||
for _, value := range values[1:] {
|
||||
if delta := value - lastValue; delta > maxDelta {
|
||||
maxDelta = delta
|
||||
}
|
||||
lastValue = value
|
||||
}
|
||||
|
||||
switch {
|
||||
case maxDelta > math.MaxUint32:
|
||||
return newOffsetArray(values)
|
||||
case maxDelta > math.MaxUint16:
|
||||
return newDeltaArray[uint32](values)
|
||||
case maxDelta > math.MaxUint8:
|
||||
return newDeltaArray[uint16](values)
|
||||
case maxDelta > 15:
|
||||
return newDeltaArray[uint8](values)
|
||||
default:
|
||||
return newDeltaArrayUint4(values)
|
||||
}
|
||||
}
|
||||
|
||||
type offsetArray struct {
|
||||
values []uint64
|
||||
}
|
||||
|
||||
func newOffsetArray(values []uint64) *offsetArray {
|
||||
a := &offsetArray{
|
||||
values: make([]uint64, len(values)),
|
||||
}
|
||||
copy(a.values, values)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *offsetArray) Index(i int) uint64 {
|
||||
return a.values[i]
|
||||
}
|
||||
|
||||
func (a *offsetArray) Len() int {
|
||||
return len(a.values)
|
||||
}
|
||||
|
||||
type emptyOffsetArray struct{}
|
||||
|
||||
func (emptyOffsetArray) Index(int) uint64 {
|
||||
panic("index out of bounds")
|
||||
}
|
||||
|
||||
func (emptyOffsetArray) Len() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
const smallOffsetArrayCapacity = 7
|
||||
|
||||
type smallOffsetArray struct {
|
||||
length int
|
||||
values [smallOffsetArrayCapacity]uint64
|
||||
}
|
||||
|
||||
func newSmallOffsetArray(values []uint64) *smallOffsetArray {
|
||||
a := &smallOffsetArray{length: len(values)}
|
||||
copy(a.values[:], values)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *smallOffsetArray) Index(i int) uint64 {
|
||||
if i < 0 || i >= a.length {
|
||||
panic("index out of bounds")
|
||||
}
|
||||
return a.values[i]
|
||||
}
|
||||
|
||||
func (a *smallOffsetArray) Len() int {
|
||||
return a.length
|
||||
}
|
||||
|
||||
type uintType interface {
|
||||
uint8 | uint16 | uint32 | uint64
|
||||
}
|
||||
|
||||
type deltaArray[T uintType] struct {
|
||||
deltas []T
|
||||
firstValue uint64
|
||||
}
|
||||
|
||||
func newDeltaArray[T uintType](values []uint64) *deltaArray[T] {
|
||||
a := &deltaArray[T]{
|
||||
deltas: make([]T, len(values)-1),
|
||||
firstValue: values[0],
|
||||
}
|
||||
lastValue := values[0]
|
||||
for i, value := range values[1:] {
|
||||
a.deltas[i] = T(value - lastValue)
|
||||
lastValue = value
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *deltaArray[T]) Index(i int) uint64 {
|
||||
if i < 0 || i >= a.Len() {
|
||||
panic("index out of bounds")
|
||||
}
|
||||
value := a.firstValue
|
||||
// TODO: computing the prefix sum can be vectorized;
|
||||
// see https://en.algorithmica.org/hpc/algorithms/prefix/
|
||||
for _, delta := range a.deltas[:i] {
|
||||
value += uint64(delta)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (a *deltaArray[T]) Len() int {
|
||||
return len(a.deltas) + 1
|
||||
}
|
||||
|
||||
// deltaArrayUint4 is a specialization of deltaArray which packs 4 bits integers
|
||||
// to hold deltas between 0 and 15; based on the analysis of compiling Python,
|
||||
// it appeared that most source offset deltas were under 16, so using this
|
||||
// data structure cuts by 50% the memory needed compared to deltaArray[uint8].
|
||||
//
|
||||
// Here is the distribution of source offset deltas for Python 3.13:
|
||||
//
|
||||
// - <=15 : 10240
|
||||
// - <=255 : 9565
|
||||
// - <=65535 : 1163
|
||||
//
|
||||
// Memory profiles showed that using deltaArrayUint4 (compared to deltaArray[T])
|
||||
// dropped the memory footprint of source mappings for Python from 6MB to 4.5MB.
|
||||
type deltaArrayUint4 struct {
|
||||
deltas []byte
|
||||
numValues int
|
||||
firstValue uint64
|
||||
}
|
||||
|
||||
func newDeltaArrayUint4(values []uint64) *deltaArrayUint4 {
|
||||
a := &deltaArrayUint4{
|
||||
deltas: make([]byte, len(values)/2+1),
|
||||
numValues: len(values),
|
||||
firstValue: values[0],
|
||||
}
|
||||
lastValue := values[0]
|
||||
for i, value := range values[1:] {
|
||||
a.assign(i, value-lastValue)
|
||||
lastValue = value
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *deltaArrayUint4) assign(i int, v uint64) {
|
||||
index, shift := uint(i)>>1, 4*(uint(i)&1)
|
||||
a.deltas[index] &= ^(0xF << shift)
|
||||
a.deltas[index] |= byte(v) << shift
|
||||
}
|
||||
|
||||
func (a *deltaArrayUint4) index(i int) uint64 {
|
||||
index, shift := uint(i)>>1, 4*(uint(i)&1)
|
||||
return uint64((a.deltas[index] >> shift) & 0xF)
|
||||
}
|
||||
|
||||
func (a *deltaArrayUint4) Index(i int) uint64 {
|
||||
if i < 0 || i >= a.Len() {
|
||||
panic("index out of bounds")
|
||||
}
|
||||
value := a.firstValue
|
||||
for j := 0; j < i; j++ {
|
||||
value += a.index(j)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (a *deltaArrayUint4) Len() int {
|
||||
return a.numValues
|
||||
}
|
||||
Generated
Vendored
-112
@@ -1,112 +0,0 @@
|
||||
# Compiler engine
|
||||
|
||||
This package implements the Compiler engine for WebAssembly *purely written in Go*.
|
||||
In this README, we describe the background, technical difficulties and some design choices.
|
||||
|
||||
## General limitations on pure Go Compiler engines
|
||||
|
||||
In Go program, each Goroutine manages its own stack, and each item on Goroutine
|
||||
stack is managed by Go runtime for garbage collection, etc.
|
||||
|
||||
These impose some difficulties on compiler engine purely written in Go because
|
||||
we *cannot* use native push/pop instructions to save/restore temporary
|
||||
variables spilling from registers. This results in making it impossible for us
|
||||
to invoke Go functions from compiled native codes with the native `call`
|
||||
instruction since it involves stack manipulations.
|
||||
|
||||
*TODO: maybe it is possible to hack the runtime to make it possible to achieve
|
||||
function calls with `call`.*
|
||||
|
||||
## How to generate native codes
|
||||
|
||||
wazero uses its own assembler, implemented from scratch in the
|
||||
[`internal/asm`](../../asm/) package. The primary rationale are wazero's zero
|
||||
dependency policy, and to enable concurrent compilation (a feature the
|
||||
WebAssembly binary format optimizes for).
|
||||
|
||||
Before this, wazero used [`twitchyliquid64/golang-asm`](https://github.com/twitchyliquid64/golang-asm).
|
||||
However, this was not only a dependency (one of our goals is to have zero
|
||||
dependencies), but also a large one (several megabytes added to the binary).
|
||||
Moreover, any copy of golang-asm is not thread-safe, so can't be used for
|
||||
concurrent compilation (See [#233](https://github.com/tetratelabs/wazero/issues/233)).
|
||||
|
||||
The assembled native codes are represented as `[]byte` and the slice region is
|
||||
marked as executable via mmap system call.
|
||||
|
||||
## How to enter native codes
|
||||
|
||||
Assuming that we have a native code as `[]byte`, it is straightforward to enter
|
||||
the native code region via Go assembly code. In this package, we have the
|
||||
function without body called `nativecall`
|
||||
|
||||
```go
|
||||
func nativecall(codeSegment, engine, memory uintptr)
|
||||
```
|
||||
|
||||
where we pass `codeSegment uintptr` as a first argument. This pointer is to the
|
||||
first instruction to be executed. The pointer can be easily derived from
|
||||
`[]byte` via `unsafe.Pointer`:
|
||||
|
||||
```go
|
||||
code := []byte{}
|
||||
/* ...Compilation ...*/
|
||||
codeSegment := uintptr(unsafe.Pointer(&code[0]))
|
||||
nativecall(codeSegment, ...)
|
||||
```
|
||||
|
||||
And `nativecall` is actually implemented in [arch_amd64.s](./arch_amd64.s)
|
||||
as a convenience layer to comply with the Go's official calling convention.
|
||||
We delegate the task to jump into the code segment to the Go assembler code.
|
||||
|
||||
|
||||
## Why it's safe to execute runtime-generated machine codes against async Goroutine preemption
|
||||
|
||||
Goroutine preemption is the mechanism of the Go runtime to switch goroutines contexts on an OS thread.
|
||||
There are two types of preemption: cooperative preemption and async preemption. The former happens, for example,
|
||||
when making a function call, and it is not an issue for our runtime-generated functions as they do not make
|
||||
direct function calls to Go-implemented functions. On the other hand, the latter, async preemption, can be problematic
|
||||
since it tries to interrupt the execution of Goroutine at any point of function, and manipulates CPU register states.
|
||||
|
||||
Fortunately, our runtime-generated machine codes do not need to take the async preemption into account.
|
||||
All the assembly codes are entered via the trampoline implemented as Go Assembler Function (e.g. [arch_amd64.s](./arch_amd64.s)),
|
||||
and as of Go 1.20, these assembler functions are considered as _unsafe_ for async preemption:
|
||||
- https://github.com/golang/go/blob/go1.20rc1/src/runtime/preempt.go#L406-L407
|
||||
- https://github.com/golang/go/blob/9f0234214473dfb785a5ad84a8fc62a6a395cbc3/src/runtime/traceback.go#L227
|
||||
|
||||
From the Go runtime point of view, the execution of runtime-generated machine codes is considered as a part of
|
||||
that trampoline function. Therefore, runtime-generated machine code is also correctly considered unsafe for async preemption.
|
||||
|
||||
## Why context cancellation is handled in Go code rather than native code
|
||||
|
||||
Since [wazero v1.0.0-pre.9](https://github.com/tetratelabs/wazero/releases/tag/v1.0.0-pre.9), the runtime
|
||||
supports integration with Go contexts to interrupt execution after a timeout, or in response to explicit cancellation.
|
||||
This support is internally implemented as a special opcode `builtinFunctionCheckExitCode` that triggers the execution of
|
||||
a Go function (`ModuleInstance.FailIfClosed`) that atomically checks a sentinel value at strategic points in the code
|
||||
(e.g. [within loops][checkexitcode_loop]).
|
||||
|
||||
[It _is indeed_ possible to check the sentinel value directly, without leaving the native world][native_check], thus sparing some cycles;
|
||||
however, because native code never preempts (see section above), this may lead to a state where the other goroutines
|
||||
never get the chance to run, and thus never get the chance to set the sentinel value; effectively preventing
|
||||
cancellation from taking place.
|
||||
|
||||
[checkexitcode_loop]: https://github.com/tetratelabs/wazero/blob/86444c67a37dbf9e693ae5b365901f64968d9025/internal/wazeroir/compiler.go#L467-L476
|
||||
[native_check]: https://github.com/tetratelabs/wazero/issues/1409
|
||||
|
||||
## Source Offset Mapping
|
||||
|
||||
When translating code from WebAssembly to the wazero IR, and compiling to native
|
||||
binary, wazero keeps track of two indexes to correlate native program counters
|
||||
to the original source offset that they were generated from.
|
||||
|
||||
Source offset maps are useful for debugging, but holding indexes in memory for
|
||||
all instructions can have a significant overhead. To reduce the memory footprint
|
||||
of the compiled modules, wazero uses data structures inspired by
|
||||
[frame-of-reference and delta encoding][FOR].
|
||||
|
||||
Because wazero does not reorder instructions, the source offsets are naturally
|
||||
sorted during compilation, and the distance between two consecutive offsets is
|
||||
usually small. Encoding deltas instead of the absolute values allows most of
|
||||
the indexes to store offsets with an overhead of 8 bits per instruction, instead
|
||||
of recording 64 bits integers for absolute code positions.
|
||||
|
||||
[FOR]: https://lemire.me/blog/2012/02/08/effective-compression-using-frame-of-reference-and-delta-coding/
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/tetratelabs/wazero/internal/asm"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// newArchContext returns a new archContext which is architecture-specific type to be embedded in callEngine.
|
||||
// This must be initialized in init() function in architecture-specific arch_*.go file which is guarded by build tag.
|
||||
var newArchContext func() archContext
|
||||
|
||||
// nativecall is used by callEngine.execWasmFunction and the entrypoint to enter the compiled native code.
|
||||
// codeSegment is the pointer to the initial instruction of the compiled native code.
|
||||
//
|
||||
// Note: this is implemented in per-arch Go assembler file. For example, arch_amd64.s implements this for amd64.
|
||||
func nativecall(codeSegment uintptr, ce *callEngine, moduleInstanceAddress *wasm.ModuleInstance)
|
||||
|
||||
// registerNameFn is used for debugging purpose to have register symbols in the string of runtimeValueLocation.
|
||||
var registerNameFn func(register asm.Register) string
|
||||
Generated
Vendored
-35
@@ -1,35 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/tetratelabs/wazero/internal/asm"
|
||||
"github.com/tetratelabs/wazero/internal/asm/amd64"
|
||||
)
|
||||
|
||||
// init initializes variables for the amd64 architecture
|
||||
func init() {
|
||||
newArchContext = newArchContextImpl
|
||||
registerNameFn = amd64.RegisterName
|
||||
unreservedGeneralPurposeRegisters = amd64UnreservedGeneralPurposeRegisters
|
||||
unreservedVectorRegisters = amd64UnreservedVectorRegisters
|
||||
}
|
||||
|
||||
// archContext is embedded in callEngine in order to store architecture-specific data.
|
||||
// For amd64, this is empty.
|
||||
type archContext struct{}
|
||||
|
||||
// newArchContextImpl implements newArchContext for amd64 architecture.
|
||||
func newArchContextImpl() (ret archContext) { return }
|
||||
|
||||
// newCompiler returns a new compiler interface which can be used to compile the given function instance.
|
||||
// Note: ir param can be nil for host functions.
|
||||
func newCompiler() compiler {
|
||||
return newAmd64Compiler()
|
||||
}
|
||||
|
||||
func registerMaskShift(r asm.Register) int {
|
||||
return int(r - amd64.RegAX)
|
||||
}
|
||||
|
||||
func registerFromMaskShift(s int) asm.Register {
|
||||
return amd64.RegAX + asm.Register(s)
|
||||
}
|
||||
Generated
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
#include "funcdata.h"
|
||||
#include "textflag.h"
|
||||
|
||||
// nativecall(codeSegment, ce, moduleInstanceAddress)
|
||||
TEXT ·nativecall(SB), NOSPLIT|NOFRAME, $0-24
|
||||
MOVQ ce+8(FP), R13 // Load the address of *callEngine. into amd64ReservedRegisterForCallEngine.
|
||||
MOVQ moduleInstanceAddress+16(FP), R12 // Load the address of *wasm.ModuleInstance into amd64CallingConventionModuleInstanceAddressRegister.
|
||||
MOVQ codeSegment+0(FP), AX // Load the address of native code.
|
||||
JMP AX // Jump to native code.
|
||||
Generated
Vendored
-70
@@ -1,70 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/asm"
|
||||
"github.com/tetratelabs/wazero/internal/asm/arm64"
|
||||
)
|
||||
|
||||
// init initializes variables for the arm64 architecture
|
||||
func init() {
|
||||
newArchContext = newArchContextImpl
|
||||
registerNameFn = arm64.RegisterName
|
||||
unreservedGeneralPurposeRegisters = arm64UnreservedGeneralPurposeRegisters
|
||||
unreservedVectorRegisters = arm64UnreservedVectorRegisters
|
||||
}
|
||||
|
||||
// archContext is embedded in callEngine in order to store architecture-specific data.
|
||||
type archContext struct {
|
||||
// compilerCallReturnAddress holds the absolute return address for nativecall.
|
||||
// The value is set whenever nativecall is executed and done in compiler_arm64.s
|
||||
// Native code can return to the ce.execWasmFunction's main loop back by
|
||||
// executing "ret" instruction with this value. See arm64Compiler.exit.
|
||||
// Note: this is only used by Compiler code so mark this as nolint.
|
||||
compilerCallReturnAddress uint64 //nolint
|
||||
|
||||
// Loading large constants in arm64 is a bit costly, so we place the following
|
||||
// consts on callEngine struct so that we can quickly access them during various operations.
|
||||
|
||||
// minimum32BitSignedInt is used for overflow check for 32-bit signed division.
|
||||
// Note: this can be obtained by moving $1 and doing left-shift with 31, but it is
|
||||
// slower than directly loading from this location.
|
||||
minimum32BitSignedInt int32
|
||||
// Note: this can be obtained by moving $1 and doing left-shift with 63, but it is
|
||||
// slower than directly loading from this location.
|
||||
// minimum64BitSignedInt is used for overflow check for 64-bit signed division.
|
||||
minimum64BitSignedInt int64
|
||||
}
|
||||
|
||||
// newArchContextImpl implements newArchContext for amd64 architecture.
|
||||
func newArchContextImpl() archContext {
|
||||
return archContext{
|
||||
minimum32BitSignedInt: math.MinInt32,
|
||||
minimum64BitSignedInt: math.MinInt64,
|
||||
}
|
||||
}
|
||||
|
||||
// newCompiler returns a new compiler interface which can be used to compile the given function instance.
|
||||
// Note: ir param can be nil for host functions.
|
||||
func newCompiler() compiler {
|
||||
return newArm64Compiler()
|
||||
}
|
||||
|
||||
func registerMaskShift(r asm.Register) (ret int) {
|
||||
ret = int(r - arm64.RegR0)
|
||||
if r > arm64.RegSP {
|
||||
// Skips arm64.RegSP which is not a real register.
|
||||
ret--
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func registerFromMaskShift(s int) asm.Register {
|
||||
if s < 32 {
|
||||
return arm64.RegR0 + asm.Register(s)
|
||||
} else {
|
||||
// Skips arm64.RegSP which is not a real register.
|
||||
return arm64.RegR0 + asm.Register(s) + 1
|
||||
}
|
||||
}
|
||||
Generated
Vendored
-21
@@ -1,21 +0,0 @@
|
||||
#include "funcdata.h"
|
||||
#include "textflag.h"
|
||||
|
||||
// nativecall(codeSegment, ce, moduleInstanceAddress)
|
||||
TEXT ·nativecall(SB), NOSPLIT|NOFRAME, $0-24
|
||||
// Load the address of *callEngine into arm64ReservedRegisterForCallEngine.
|
||||
MOVD ce+8(FP), R0
|
||||
|
||||
// In arm64, return address is stored in R30 after jumping into the code.
|
||||
// We save the return address value into archContext.compilerReturnAddress in Engine.
|
||||
// Note that the const 144 drifts after editting Engine or archContext struct. See TestArchContextOffsetInEngine.
|
||||
MOVD R30, 144(R0)
|
||||
|
||||
// Load the address of *wasm.ModuleInstance into arm64CallingConventionModuleInstanceAddressRegister.
|
||||
MOVD moduleInstanceAddress+16(FP), R29
|
||||
|
||||
// Load the address of native code.
|
||||
MOVD codeSegment+0(FP), R1
|
||||
|
||||
// Jump to native code.
|
||||
JMP (R1)
|
||||
Generated
Vendored
-26
@@ -1,26 +0,0 @@
|
||||
//go:build !amd64 && !arm64
|
||||
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/asm"
|
||||
)
|
||||
|
||||
// archContext is empty on an unsupported architecture.
|
||||
type archContext struct{}
|
||||
|
||||
// newCompiler panics with an unsupported error.
|
||||
func newCompiler() compiler {
|
||||
panic(fmt.Sprintf("unsupported GOARCH %s", runtime.GOARCH))
|
||||
}
|
||||
|
||||
func registerMaskShift(r asm.Register) (ret int) {
|
||||
panic(fmt.Sprintf("unsupported GOARCH %s", runtime.GOARCH))
|
||||
}
|
||||
|
||||
func registerFromMaskShift(s int) asm.Register {
|
||||
panic(fmt.Sprintf("unsupported GOARCH %s", runtime.GOARCH))
|
||||
}
|
||||
Generated
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
//go:build !arm64 && !amd64
|
||||
|
||||
TEXT ·nativecall(SB), $0-16
|
||||
Generated
Vendored
-335
@@ -1,335 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/tetratelabs/wazero/internal/asm"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
"github.com/tetratelabs/wazero/internal/wazeroir"
|
||||
)
|
||||
|
||||
// compiler is the interface of architecture-specific native code compiler,
|
||||
// and this is responsible for compiling native code for all wazeroir operations.
|
||||
type compiler interface {
|
||||
Init(functionType *wasm.FunctionType, ir *wazeroir.CompilationResult, withListener bool)
|
||||
|
||||
// String is for debugging purpose.
|
||||
String() string
|
||||
// compilePreamble is called before compiling any wazeroir operation.
|
||||
// This is used, for example, to initialize the reserved registers, etc.
|
||||
compilePreamble() error
|
||||
// compile generates the native code into buf.
|
||||
// stackPointerCeil is the max stack pointer that the target function would reach.
|
||||
compile(buf asm.Buffer) (stackPointerCeil uint64, err error)
|
||||
// compileGoHostFunction adds the trampoline code from which native code can jump into the Go-defined host function.
|
||||
// TODO: maybe we wouldn't need to have trampoline for host functions.
|
||||
compileGoDefinedHostFunction() error
|
||||
// compileLabel notify compilers of the beginning of a label.
|
||||
// Return true if the compiler decided to skip the entire label.
|
||||
// See wazeroir.NewOperationLabel
|
||||
compileLabel(o *wazeroir.UnionOperation) (skipThisLabel bool)
|
||||
// compileUnreachable adds instruction to perform wazeroir.OperationUnreachable.
|
||||
compileUnreachable() error
|
||||
// compileSet adds instruction to perform wazeroir.OperationSet.
|
||||
compileSet(o *wazeroir.UnionOperation) error
|
||||
// compileGlobalGet adds instructions to perform wazeroir.OperationGlobalGet.
|
||||
compileGlobalGet(o *wazeroir.UnionOperation) error
|
||||
// compileGlobalSet adds instructions to perform wazeroir.OperationGlobalSet.
|
||||
compileGlobalSet(o *wazeroir.UnionOperation) error
|
||||
// compileBr adds instructions to perform wazeroir.NewOperationBr.
|
||||
compileBr(o *wazeroir.UnionOperation) error
|
||||
// compileBrIf adds instructions to perform wazeroir.NewOperationBrIf.
|
||||
compileBrIf(o *wazeroir.UnionOperation) error
|
||||
// compileBrTable adds instructions to perform wazeroir.NewOperationBrTable.
|
||||
compileBrTable(o *wazeroir.UnionOperation) error
|
||||
// compileCall adds instructions to perform wazeroir.OperationCall.
|
||||
compileCall(o *wazeroir.UnionOperation) error
|
||||
// compileCallIndirect adds instructions to perform wazeroir.OperationCallIndirect.
|
||||
compileCallIndirect(o *wazeroir.UnionOperation) error
|
||||
// compileDrop adds instructions to perform wazeroir.NewOperationDrop.
|
||||
compileDrop(o *wazeroir.UnionOperation) error
|
||||
// compileSelect adds instructions to perform wazeroir.OperationSelect.
|
||||
compileSelect(o *wazeroir.UnionOperation) error
|
||||
// compilePick adds instructions to perform wazeroir.OperationPick.
|
||||
compilePick(o *wazeroir.UnionOperation) error
|
||||
// compileAdd adds instructions to perform wazeroir.OperationAdd.
|
||||
compileAdd(o *wazeroir.UnionOperation) error
|
||||
// compileSub adds instructions to perform wazeroir.OperationSub.
|
||||
compileSub(o *wazeroir.UnionOperation) error
|
||||
// compileMul adds instructions to perform wazeroir.OperationMul.
|
||||
compileMul(o *wazeroir.UnionOperation) error
|
||||
// compileClz adds instructions to perform wazeroir.OperationClz.
|
||||
compileClz(o *wazeroir.UnionOperation) error
|
||||
// compileCtz adds instructions to perform wazeroir.OperationCtz.
|
||||
compileCtz(o *wazeroir.UnionOperation) error
|
||||
// compilePopcnt adds instructions to perform wazeroir.OperationPopcnt.
|
||||
compilePopcnt(o *wazeroir.UnionOperation) error
|
||||
// compileDiv adds instructions to perform wazeroir.OperationDiv.
|
||||
compileDiv(o *wazeroir.UnionOperation) error
|
||||
// compileRem adds instructions to perform wazeroir.OperationRem.
|
||||
compileRem(o *wazeroir.UnionOperation) error
|
||||
// compileAnd adds instructions to perform wazeroir.OperationAnd.
|
||||
compileAnd(o *wazeroir.UnionOperation) error
|
||||
// compileOr adds instructions to perform wazeroir.OperationOr.
|
||||
compileOr(o *wazeroir.UnionOperation) error
|
||||
// compileXor adds instructions to perform wazeroir.OperationXor.
|
||||
compileXor(o *wazeroir.UnionOperation) error
|
||||
// compileShl adds instructions to perform wazeroir.OperationShl.
|
||||
compileShl(o *wazeroir.UnionOperation) error
|
||||
// compileShr adds instructions to perform wazeroir.OperationShr.
|
||||
compileShr(o *wazeroir.UnionOperation) error
|
||||
// compileRotl adds instructions to perform wazeroir.OperationRotl.
|
||||
compileRotl(o *wazeroir.UnionOperation) error
|
||||
// compileRotr adds instructions to perform wazeroir.OperationRotr.
|
||||
compileRotr(o *wazeroir.UnionOperation) error
|
||||
// compileNeg adds instructions to perform wazeroir.OperationAbs.
|
||||
compileAbs(o *wazeroir.UnionOperation) error
|
||||
// compileNeg adds instructions to perform wazeroir.OperationNeg.
|
||||
compileNeg(o *wazeroir.UnionOperation) error
|
||||
// compileCeil adds instructions to perform wazeroir.OperationCeil.
|
||||
compileCeil(o *wazeroir.UnionOperation) error
|
||||
// compileFloor adds instructions to perform wazeroir.OperationFloor.
|
||||
compileFloor(o *wazeroir.UnionOperation) error
|
||||
// compileTrunc adds instructions to perform wazeroir.OperationTrunc.
|
||||
compileTrunc(o *wazeroir.UnionOperation) error
|
||||
// compileNearest adds instructions to perform wazeroir.OperationNearest.
|
||||
compileNearest(o *wazeroir.UnionOperation) error
|
||||
// compileSqrt adds instructions perform wazeroir.OperationSqrt.
|
||||
compileSqrt(o *wazeroir.UnionOperation) error
|
||||
// compileMin adds instructions perform wazeroir.OperationMin.
|
||||
compileMin(o *wazeroir.UnionOperation) error
|
||||
// compileMax adds instructions perform wazeroir.OperationMax.
|
||||
compileMax(o *wazeroir.UnionOperation) error
|
||||
// compileCopysign adds instructions to perform wazeroir.OperationCopysign.
|
||||
compileCopysign(o *wazeroir.UnionOperation) error
|
||||
// compileI32WrapFromI64 adds instructions to perform wazeroir.OperationI32WrapFromI64.
|
||||
compileI32WrapFromI64() error
|
||||
// compileITruncFromF adds instructions to perform wazeroir.NewOperationITruncFromF.
|
||||
compileITruncFromF(o *wazeroir.UnionOperation) error
|
||||
// compileFConvertFromI adds instructions to perform wazeroir.NewOperationFConvertFromI.
|
||||
compileFConvertFromI(o *wazeroir.UnionOperation) error
|
||||
// compileF32DemoteFromF64 adds instructions to perform wazeroir.OperationF32DemoteFromF64.
|
||||
compileF32DemoteFromF64() error
|
||||
// compileF64PromoteFromF32 adds instructions to perform wazeroir.OperationF64PromoteFromF32.
|
||||
compileF64PromoteFromF32() error
|
||||
// compileI32ReinterpretFromF32 adds instructions to perform wazeroir.OperationI32ReinterpretFromF32.
|
||||
compileI32ReinterpretFromF32() error
|
||||
// compileI64ReinterpretFromF64 adds instructions to perform wazeroir.OperationI64ReinterpretFromF64.
|
||||
compileI64ReinterpretFromF64() error
|
||||
// compileF32ReinterpretFromI32 adds instructions to perform wazeroir.OperationF32ReinterpretFromI32.
|
||||
compileF32ReinterpretFromI32() error
|
||||
// compileF64ReinterpretFromI64 adds instructions to perform wazeroir.OperationF64ReinterpretFromI64.
|
||||
compileF64ReinterpretFromI64() error
|
||||
// compileExtend adds instructions to perform wazeroir.NewOperationExtend.
|
||||
compileExtend(o *wazeroir.UnionOperation) error
|
||||
// compileEq adds instructions to perform wazeroir.OperationEq.
|
||||
compileEq(o *wazeroir.UnionOperation) error
|
||||
// compileEq adds instructions to perform wazeroir.OperationNe.
|
||||
compileNe(o *wazeroir.UnionOperation) error
|
||||
// compileEq adds instructions to perform wazeroir.OperationEqz.
|
||||
compileEqz(o *wazeroir.UnionOperation) error
|
||||
// compileLt adds instructions to perform wazeroir.OperationLt.
|
||||
compileLt(o *wazeroir.UnionOperation) error
|
||||
// compileGt adds instructions to perform wazeroir.OperationGt.
|
||||
compileGt(o *wazeroir.UnionOperation) error
|
||||
// compileLe adds instructions to perform wazeroir.OperationLe.
|
||||
compileLe(o *wazeroir.UnionOperation) error
|
||||
// compileLe adds instructions to perform wazeroir.OperationGe.
|
||||
compileGe(o *wazeroir.UnionOperation) error
|
||||
// compileLoad adds instructions to perform wazeroir.OperationLoad.
|
||||
compileLoad(o *wazeroir.UnionOperation) error
|
||||
// compileLoad8 adds instructions to perform wazeroir.OperationLoad8.
|
||||
compileLoad8(o *wazeroir.UnionOperation) error
|
||||
// compileLoad16 adds instructions to perform wazeroir.OperationLoad16.
|
||||
compileLoad16(o *wazeroir.UnionOperation) error
|
||||
// compileLoad32 adds instructions to perform wazeroir.OperationLoad32.
|
||||
compileLoad32(o *wazeroir.UnionOperation) error
|
||||
// compileStore adds instructions to perform wazeroir.OperationStore.
|
||||
compileStore(o *wazeroir.UnionOperation) error
|
||||
// compileStore8 adds instructions to perform wazeroir.OperationStore8.
|
||||
compileStore8(o *wazeroir.UnionOperation) error
|
||||
// compileStore16 adds instructions to perform wazeroir.OperationStore16.
|
||||
compileStore16(o *wazeroir.UnionOperation) error
|
||||
// compileStore32 adds instructions to perform wazeroir.OperationStore32.
|
||||
compileStore32(o *wazeroir.UnionOperation) error
|
||||
// compileMemorySize adds instruction to perform wazeroir.OperationMemoryGrow.
|
||||
compileMemoryGrow() error
|
||||
// compileMemorySize adds instruction to perform wazeroir.OperationMemorySize.
|
||||
compileMemorySize() error
|
||||
// compileConstI32 adds instruction to perform wazeroir.NewOperationConstI32.
|
||||
compileConstI32(o *wazeroir.UnionOperation) error
|
||||
// compileConstI64 adds instruction to perform wazeroir.NewOperationConstI64.
|
||||
compileConstI64(o *wazeroir.UnionOperation) error
|
||||
// compileConstF32 adds instruction to perform wazeroir.NewOperationConstF32.
|
||||
compileConstF32(o *wazeroir.UnionOperation) error
|
||||
// compileConstF64 adds instruction to perform wazeroir.NewOperationConstF64.
|
||||
compileConstF64(o *wazeroir.UnionOperation) error
|
||||
// compileSignExtend32From8 adds instructions to perform wazeroir.OperationSignExtend32From8.
|
||||
compileSignExtend32From8() error
|
||||
// compileSignExtend32From16 adds instructions to perform wazeroir.OperationSignExtend32From16.
|
||||
compileSignExtend32From16() error
|
||||
// compileSignExtend64From8 adds instructions to perform wazeroir.OperationSignExtend64From8.
|
||||
compileSignExtend64From8() error
|
||||
// compileSignExtend64From16 adds instructions to perform wazeroir.OperationSignExtend64From16.
|
||||
compileSignExtend64From16() error
|
||||
// compileSignExtend64From32 adds instructions to perform wazeroir.OperationSignExtend64From32.
|
||||
compileSignExtend64From32() error
|
||||
// compileMemoryInit adds instructions to perform wazeroir.NewOperationMemoryInit.
|
||||
compileMemoryInit(*wazeroir.UnionOperation) error
|
||||
// compileDataDrop adds instructions to perform wazeroir.NewOperationDataDrop.
|
||||
compileDataDrop(*wazeroir.UnionOperation) error
|
||||
// compileMemoryCopy adds instructions to perform wazeroir.OperationMemoryCopy.
|
||||
compileMemoryCopy() error
|
||||
// compileMemoryFill adds instructions to perform wazeroir.OperationMemoryFill.
|
||||
compileMemoryFill() error
|
||||
// compileTableInit adds instructions to perform wazeroir.NewOperationTableInit.
|
||||
compileTableInit(*wazeroir.UnionOperation) error
|
||||
// compileTableCopy adds instructions to perform wazeroir.NewOperationTableCopy.
|
||||
compileTableCopy(*wazeroir.UnionOperation) error
|
||||
// compileElemDrop adds instructions to perform wazeroir.NewOperationElemDrop.
|
||||
compileElemDrop(*wazeroir.UnionOperation) error
|
||||
// compileRefFunc adds instructions to perform wazeroir.NewOperationRefFunc.
|
||||
compileRefFunc(*wazeroir.UnionOperation) error
|
||||
// compileTableGet adds instructions to perform wazeroir.NewOperationTableGet.
|
||||
compileTableGet(*wazeroir.UnionOperation) error
|
||||
// compileTableSet adds instructions to perform wazeroir.NewOperationTableSet.
|
||||
compileTableSet(*wazeroir.UnionOperation) error
|
||||
// compileTableGrow adds instructions to perform wazeroir.NewOperationTableGrow.
|
||||
compileTableGrow(*wazeroir.UnionOperation) error
|
||||
// compileTableSize adds instructions to perform wazeroir.NewOperationTableSize.
|
||||
compileTableSize(*wazeroir.UnionOperation) error
|
||||
// compileTableFill adds instructions to perform wazeroir.NewOperationTableFill.
|
||||
compileTableFill(*wazeroir.UnionOperation) error
|
||||
// compileV128Const adds instructions to perform wazeroir.NewOperationV128Const.
|
||||
compileV128Const(*wazeroir.UnionOperation) error
|
||||
// compileV128Add adds instructions to perform wazeroir.OperationV128Add.
|
||||
compileV128Add(o *wazeroir.UnionOperation) error
|
||||
// compileV128Sub adds instructions to perform wazeroir.OperationV128Sub.
|
||||
compileV128Sub(o *wazeroir.UnionOperation) error
|
||||
// compileV128Load adds instructions to perform wazeroir.OperationV128Load.
|
||||
compileV128Load(o *wazeroir.UnionOperation) error
|
||||
// compileV128LoadLane adds instructions to perform wazeroir.OperationV128LoadLane.
|
||||
compileV128LoadLane(o *wazeroir.UnionOperation) error
|
||||
// compileV128Store adds instructions to perform wazeroir.NewOperationV128Store.
|
||||
compileV128Store(o *wazeroir.UnionOperation) error
|
||||
// compileV128StoreLane adds instructions to perform wazeroir.NewOperationV128StoreLane.
|
||||
compileV128StoreLane(o *wazeroir.UnionOperation) error
|
||||
// compileV128ExtractLane adds instructions to perform wazeroir.NewOperationV128ExtractLane.
|
||||
compileV128ExtractLane(o *wazeroir.UnionOperation) error
|
||||
// compileV128ReplaceLane adds instructions to perform wazeroir.NewOperationV128ReplaceLane.
|
||||
compileV128ReplaceLane(o *wazeroir.UnionOperation) error
|
||||
// compileV128Splat adds instructions to perform wazeroir.NewOperationV128Splat.
|
||||
compileV128Splat(o *wazeroir.UnionOperation) error
|
||||
// compileV128Shuffle adds instructions to perform wazeroir.NewOperationV128Shuffle.
|
||||
compileV128Shuffle(o *wazeroir.UnionOperation) error
|
||||
// compileV128Swizzle adds instructions to perform wazeroir.OperationV128Swizzle.
|
||||
compileV128Swizzle(o *wazeroir.UnionOperation) error
|
||||
// compileV128AnyTrue adds instructions to perform wazeroir.OperationV128AnyTrue.
|
||||
compileV128AnyTrue(o *wazeroir.UnionOperation) error
|
||||
// compileV128AllTrue adds instructions to perform wazeroir.NewOperationV128AllTrue.
|
||||
compileV128AllTrue(o *wazeroir.UnionOperation) error
|
||||
// compileV128BitMask adds instructions to perform wazeroir.NewOperationV128BitMask.
|
||||
compileV128BitMask(*wazeroir.UnionOperation) error
|
||||
// compileV128And adds instructions to perform wazeroir.OperationV128And.
|
||||
compileV128And(*wazeroir.UnionOperation) error
|
||||
// compileV128Not adds instructions to perform wazeroir.OperationV128Not.
|
||||
compileV128Not(*wazeroir.UnionOperation) error
|
||||
// compileV128Or adds instructions to perform wazeroir.OperationV128Or.
|
||||
compileV128Or(*wazeroir.UnionOperation) error
|
||||
// compileV128Xor adds instructions to perform wazeroir.OperationV128Xor.
|
||||
compileV128Xor(*wazeroir.UnionOperation) error
|
||||
// compileV128Bitselect adds instructions to perform wazeroir.OperationV128Bitselect.
|
||||
compileV128Bitselect(*wazeroir.UnionOperation) error
|
||||
// compileV128AndNot adds instructions to perform wazeroir.OperationV128AndNot.
|
||||
compileV128AndNot(*wazeroir.UnionOperation) error
|
||||
// compileV128Shr adds instructions to perform wazeroir.NewOperationV128Shr.
|
||||
compileV128Shr(*wazeroir.UnionOperation) error
|
||||
// compileV128Shl adds instructions to perform wazeroir.NewOperationV128Shl.
|
||||
compileV128Shl(*wazeroir.UnionOperation) error
|
||||
// compileV128Cmp adds instructions to perform wazeroir.NewOperationV128Cmp.
|
||||
compileV128Cmp(*wazeroir.UnionOperation) error
|
||||
// compileV128AddSat adds instructions to perform wazeroir.NewOperationV128AddSat.
|
||||
compileV128AddSat(*wazeroir.UnionOperation) error
|
||||
// compileV128SubSat adds instructions to perform wazeroir.NewOperationV128SubSat.
|
||||
compileV128SubSat(*wazeroir.UnionOperation) error
|
||||
// compileV128Mul adds instructions to perform wazeroir.NewOperationV128Mul.
|
||||
compileV128Mul(*wazeroir.UnionOperation) error
|
||||
// compileV128Div adds instructions to perform wazeroir.NewOperationV128Div.
|
||||
compileV128Div(*wazeroir.UnionOperation) error
|
||||
// compileV128Neg adds instructions to perform wazeroir.NewOperationV128Neg.
|
||||
compileV128Neg(*wazeroir.UnionOperation) error
|
||||
// compileV128Sqrt adds instructions to perform wazeroir.NewOperationV128Sqrt.
|
||||
compileV128Sqrt(*wazeroir.UnionOperation) error
|
||||
// compileV128Abs adds instructions to perform wazeroir.NewOperationV128Abs.
|
||||
compileV128Abs(*wazeroir.UnionOperation) error
|
||||
// compileV128Popcnt adds instructions to perform wazeroir.NewOperationV128Popcnt.
|
||||
compileV128Popcnt(*wazeroir.UnionOperation) error
|
||||
// compileV128Min adds instructions to perform wazeroir.NewOperationV128Min.
|
||||
compileV128Min(*wazeroir.UnionOperation) error
|
||||
// compileV128Max adds instructions to perform wazeroir.NewOperationV128Max.
|
||||
compileV128Max(*wazeroir.UnionOperation) error
|
||||
// compileV128AvgrU adds instructions to perform wazeroir.NewOperationV128AvgrU.
|
||||
compileV128AvgrU(*wazeroir.UnionOperation) error
|
||||
// compileV128Pmin adds instructions to perform wazeroir.NewOperationV128Pmin.
|
||||
compileV128Pmin(*wazeroir.UnionOperation) error
|
||||
// compileV128Pmax adds instructions to perform wazeroir.NewOperationV128Pmax.
|
||||
compileV128Pmax(*wazeroir.UnionOperation) error
|
||||
// compileV128Ceil adds instructions to perform wazeroir.NewOperationV128Ceil.
|
||||
compileV128Ceil(*wazeroir.UnionOperation) error
|
||||
// compileV128Floor adds instructions to perform wazeroir.NewOperationV128Floor.
|
||||
compileV128Floor(*wazeroir.UnionOperation) error
|
||||
// compileV128Trunc adds instructions to perform wazeroir.NewOperationV128Trunc.
|
||||
compileV128Trunc(*wazeroir.UnionOperation) error
|
||||
// compileV128Nearest adds instructions to perform wazeroir.NewOperationV128Nearest.
|
||||
compileV128Nearest(*wazeroir.UnionOperation) error
|
||||
// compileV128Extend adds instructions to perform wazeroir.NewOperationV128Extend.
|
||||
compileV128Extend(*wazeroir.UnionOperation) error
|
||||
// compileV128ExtMul adds instructions to perform wazeroir.NewOperationV128ExtMul.
|
||||
compileV128ExtMul(*wazeroir.UnionOperation) error
|
||||
// compileV128Q15mulrSatS adds instructions to perform wazeroir.OperationV128Q15mulrSatS.
|
||||
compileV128Q15mulrSatS(*wazeroir.UnionOperation) error
|
||||
// compileV128ExtAddPairwise adds instructions to perform wazeroir.NewOperationV128ExtAddPairwise.
|
||||
compileV128ExtAddPairwise(o *wazeroir.UnionOperation) error
|
||||
// compileV128FloatPromote adds instructions to perform wazeroir.OperationV128FloatPromote.
|
||||
compileV128FloatPromote(o *wazeroir.UnionOperation) error
|
||||
// compileV128FloatDemote adds instructions to perform wazeroir.OperationV128FloatDemote.
|
||||
compileV128FloatDemote(o *wazeroir.UnionOperation) error
|
||||
// compileV128FConvertFromI adds instructions to perform wazeroir.NewOperationV128FConvertFromI.
|
||||
compileV128FConvertFromI(o *wazeroir.UnionOperation) error
|
||||
// compileV128Dot adds instructions to perform wazeroir.OperationV128Dot.
|
||||
compileV128Dot(o *wazeroir.UnionOperation) error
|
||||
// compileV128Narrow adds instructions to perform wazeroir.NewOperationV128Narrow.
|
||||
compileV128Narrow(o *wazeroir.UnionOperation) error
|
||||
// compileV128ITruncSatFromF adds instructions to perform wazeroir.NewOperationV128ITruncSatFromF.
|
||||
compileV128ITruncSatFromF(o *wazeroir.UnionOperation) error
|
||||
|
||||
// compileBuiltinFunctionCheckExitCode adds instructions to perform wazeroir.OperationBuiltinFunctionCheckExitCode.
|
||||
compileBuiltinFunctionCheckExitCode() error
|
||||
|
||||
// compileReleaseRegisterToStack adds instructions to write the value on a register back to memory stack region.
|
||||
compileReleaseRegisterToStack(loc *runtimeValueLocation)
|
||||
// compileLoadValueOnStackToRegister adds instructions to load the value located on the stack to the assigned register.
|
||||
compileLoadValueOnStackToRegister(loc *runtimeValueLocation)
|
||||
|
||||
// maybeCompileMoveTopConditionalToGeneralPurposeRegister moves the top value on the stack
|
||||
// if the value is located on a conditional register.
|
||||
//
|
||||
// This is usually called at the beginning of methods on compiler interface where we possibly
|
||||
// compile instructions without saving the conditional register value.
|
||||
// The compileXXX functions without calling this function is saving the conditional
|
||||
// value to the stack or register by invoking compileEnsureOnRegister for the top.
|
||||
maybeCompileMoveTopConditionalToGeneralPurposeRegister() error
|
||||
// allocateRegister returns an unused register of the given type. The register will be taken
|
||||
// either from the free register pool or by stealing a used register.
|
||||
//
|
||||
// Note: resulting registers will not be marked as used so the call site should
|
||||
// mark it used if necessary.
|
||||
allocateRegister(t registerType) (reg asm.Register, err error)
|
||||
// runtimeValueLocationStack returns the current runtimeValueLocationStack of the compiler implementation.
|
||||
runtimeValueLocationStack() *runtimeValueLocationStack
|
||||
// pushRuntimeValueLocationOnRegister pushes a new runtimeValueLocation on a register `reg` and of the type `vt`.
|
||||
pushRuntimeValueLocationOnRegister(reg asm.Register, vt runtimeValueType) (ret *runtimeValueLocation)
|
||||
// pushRuntimeValueLocationOnRegister pushes a new vector value's runtimeValueLocation on a register `reg`.
|
||||
pushVectorRuntimeValueLocationOnRegister(reg asm.Register) (lowerBitsLocation *runtimeValueLocation)
|
||||
// compileNOP compiles NOP instruction and returns the corresponding asm.Node in the assembled native code.
|
||||
// This is used to emit DWARF based stack traces.
|
||||
compileNOP() asm.Node
|
||||
}
|
||||
Generated
Vendored
-126
@@ -1,126 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/tetratelabs/wazero/internal/asm"
|
||||
"github.com/tetratelabs/wazero/internal/wazeroir"
|
||||
)
|
||||
|
||||
// compileDropRange adds instruction to drop the values on the target range
|
||||
// in an architecture independent way.
|
||||
func compileDropRange(c compiler, raw uint64) (err error) {
|
||||
r := wazeroir.InclusiveRangeFromU64(raw)
|
||||
locationStack := c.runtimeValueLocationStack()
|
||||
if r.Start < 0 {
|
||||
return
|
||||
} else if r.Start == 0 {
|
||||
for i := 0; i <= int(r.End); i++ {
|
||||
if loc := locationStack.pop(); loc.onRegister() {
|
||||
locationStack.releaseRegister(loc)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// If the top value is alive, we must ensure that it is not located as a conditional.
|
||||
// Otherwise, the conditional flag might end up modified by the following operation.
|
||||
if err = c.maybeCompileMoveTopConditionalToGeneralPurposeRegister(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dropValues, liveValues := locationStack.dropsLivesForInclusiveRange(r)
|
||||
|
||||
// Frees all the registers used by drop target values.
|
||||
for i := range dropValues {
|
||||
dv := &dropValues[i]
|
||||
if dv.onRegister() {
|
||||
locationStack.releaseRegister(dv)
|
||||
}
|
||||
}
|
||||
|
||||
// These registers are not nil if a live value of that type is currently located on the memory stack.
|
||||
// In order to migrate these values, we can use them below.
|
||||
gpTmp, vecTmp, err := getTemporariesForStackedLiveValues(c, liveValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reset the stack pointer below the end.
|
||||
locationStack.sp -= uint64(len(liveValues) + len(dropValues))
|
||||
|
||||
// Push back the live values again.
|
||||
for i := range liveValues {
|
||||
live := &liveValues[i]
|
||||
migrateLiveValue(c, live, gpTmp, vecTmp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// migrateLiveValue migrates the live value `live` into the top of the stack. It might be located on the stack
|
||||
// and in that case, we have to load it into either `generalPurposeTmpReg` or `vectorTmpReg` temporarily, and
|
||||
// write it back into the *new* stack location.
|
||||
func migrateLiveValue(c compiler, live *runtimeValueLocation, generalPurposeTmpReg, vectorTmpReg asm.Register) {
|
||||
if live.valueType == runtimeValueTypeV128Hi {
|
||||
// Higher bits of vector was already handled together with the lower bits.
|
||||
return
|
||||
}
|
||||
|
||||
previouslyOnStack := live.onStack()
|
||||
if previouslyOnStack {
|
||||
// If the value is on the stack, load the value on the old location into the temporary value,
|
||||
// and then write it back to the new memory location below.
|
||||
switch live.getRegisterType() {
|
||||
case registerTypeGeneralPurpose:
|
||||
live.setRegister(generalPurposeTmpReg)
|
||||
case registerTypeVector:
|
||||
live.setRegister(vectorTmpReg)
|
||||
}
|
||||
// Load the value into tmp.
|
||||
c.compileLoadValueOnStackToRegister(live)
|
||||
}
|
||||
|
||||
var newLocation *runtimeValueLocation
|
||||
if live.valueType == runtimeValueTypeV128Lo {
|
||||
newLocation = c.pushVectorRuntimeValueLocationOnRegister(live.register)
|
||||
} else {
|
||||
newLocation = c.pushRuntimeValueLocationOnRegister(live.register, live.valueType)
|
||||
}
|
||||
|
||||
if previouslyOnStack {
|
||||
// This case, the location is on the temporary register. Therefore,
|
||||
// we have to release the value there into the *new* memory location
|
||||
// so that the tmp can be used for subsequent live value migrations.
|
||||
c.compileReleaseRegisterToStack(newLocation)
|
||||
}
|
||||
}
|
||||
|
||||
func getTemporariesForStackedLiveValues(c compiler, liveValues []runtimeValueLocation) (gpTmp, vecTmp asm.Register, err error) {
|
||||
gpTmp, vecTmp = asm.NilRegister, asm.NilRegister
|
||||
for i := range liveValues {
|
||||
l := &liveValues[i]
|
||||
if l.onStack() {
|
||||
if rt := l.getRegisterType(); rt == registerTypeGeneralPurpose && gpTmp == asm.NilRegister {
|
||||
gpTmp, err = c.allocateRegister(registerTypeGeneralPurpose)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if rt == registerTypeVector && vecTmp == asm.NilRegister {
|
||||
vecTmp, err = c.allocateRegister(registerTypeVector)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// dropsLivesForInclusiveRange returns the live and drop target values for the given wazeroir.InclusiveRange.
|
||||
func (v *runtimeValueLocationStack) dropsLivesForInclusiveRange(
|
||||
r wazeroir.InclusiveRange,
|
||||
) (dropValues, liveValues []runtimeValueLocation) {
|
||||
// liveValues are must be pushed backed after dropping the target range.
|
||||
liveValues = v.stack[v.sp-uint64(r.Start) : v.sp]
|
||||
// dropValues are the values on the drop target range.
|
||||
dropValues = v.stack[v.sp-uint64(r.End)-1 : v.sp-uint64(r.Start)]
|
||||
return
|
||||
}
|
||||
Generated
Vendored
-425
@@ -1,425 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/asm"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
var (
|
||||
// unreservedGeneralPurposeRegisters contains unreserved general purpose registers of integer type.
|
||||
unreservedGeneralPurposeRegisters []asm.Register
|
||||
|
||||
// unreservedVectorRegisters contains unreserved vector registers.
|
||||
unreservedVectorRegisters []asm.Register
|
||||
)
|
||||
|
||||
func isGeneralPurposeRegister(r asm.Register) bool {
|
||||
return unreservedGeneralPurposeRegisters[0] <= r && r <= unreservedGeneralPurposeRegisters[len(unreservedGeneralPurposeRegisters)-1]
|
||||
}
|
||||
|
||||
func isVectorRegister(r asm.Register) bool {
|
||||
return unreservedVectorRegisters[0] <= r && r <= unreservedVectorRegisters[len(unreservedVectorRegisters)-1]
|
||||
}
|
||||
|
||||
// runtimeValueLocation corresponds to each variable pushed onto the wazeroir (virtual) stack,
|
||||
// and it has the information about where it exists in the physical machine.
|
||||
// It might exist in registers, or maybe on in the non-virtual physical stack allocated in memory.
|
||||
type runtimeValueLocation struct {
|
||||
valueType runtimeValueType
|
||||
// register is set to asm.NilRegister if the value is stored in the memory stack.
|
||||
register asm.Register
|
||||
// conditionalRegister is set to conditionalRegisterStateUnset if the value is not on the conditional register.
|
||||
conditionalRegister asm.ConditionalRegisterState
|
||||
// stackPointer is the location of this value in the memory stack at runtime,
|
||||
stackPointer uint64
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocation) getRegisterType() (ret registerType) {
|
||||
switch v.valueType {
|
||||
case runtimeValueTypeI32, runtimeValueTypeI64:
|
||||
ret = registerTypeGeneralPurpose
|
||||
case runtimeValueTypeF32, runtimeValueTypeF64,
|
||||
runtimeValueTypeV128Lo, runtimeValueTypeV128Hi:
|
||||
ret = registerTypeVector
|
||||
default:
|
||||
panic("BUG")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type runtimeValueType byte
|
||||
|
||||
const (
|
||||
runtimeValueTypeNone runtimeValueType = iota
|
||||
runtimeValueTypeI32
|
||||
runtimeValueTypeI64
|
||||
runtimeValueTypeF32
|
||||
runtimeValueTypeF64
|
||||
runtimeValueTypeV128Lo
|
||||
runtimeValueTypeV128Hi
|
||||
)
|
||||
|
||||
func (r runtimeValueType) String() (ret string) {
|
||||
switch r {
|
||||
case runtimeValueTypeI32:
|
||||
ret = "i32"
|
||||
case runtimeValueTypeI64:
|
||||
ret = "i64"
|
||||
case runtimeValueTypeF32:
|
||||
ret = "f32"
|
||||
case runtimeValueTypeF64:
|
||||
ret = "f64"
|
||||
case runtimeValueTypeV128Lo:
|
||||
ret = "v128.lo"
|
||||
case runtimeValueTypeV128Hi:
|
||||
ret = "v128.hi"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocation) setRegister(reg asm.Register) {
|
||||
v.register = reg
|
||||
v.conditionalRegister = asm.ConditionalRegisterStateUnset
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocation) onRegister() bool {
|
||||
return v.register != asm.NilRegister && v.conditionalRegister == asm.ConditionalRegisterStateUnset
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocation) onStack() bool {
|
||||
return v.register == asm.NilRegister && v.conditionalRegister == asm.ConditionalRegisterStateUnset
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocation) onConditionalRegister() bool {
|
||||
return v.conditionalRegister != asm.ConditionalRegisterStateUnset
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocation) String() string {
|
||||
var location string
|
||||
if v.onStack() {
|
||||
location = fmt.Sprintf("stack(%d)", v.stackPointer)
|
||||
} else if v.onConditionalRegister() {
|
||||
location = fmt.Sprintf("conditional(%d)", v.conditionalRegister)
|
||||
} else if v.onRegister() {
|
||||
location = fmt.Sprintf("register(%s)", registerNameFn(v.register))
|
||||
}
|
||||
return fmt.Sprintf("{type=%s,location=%s}", v.valueType, location)
|
||||
}
|
||||
|
||||
func newRuntimeValueLocationStack() runtimeValueLocationStack {
|
||||
return runtimeValueLocationStack{
|
||||
unreservedVectorRegisters: unreservedVectorRegisters,
|
||||
unreservedGeneralPurposeRegisters: unreservedGeneralPurposeRegisters,
|
||||
}
|
||||
}
|
||||
|
||||
// runtimeValueLocationStack represents the wazeroir virtual stack
|
||||
// where each item holds the location information about where it exists
|
||||
// on the physical machine at runtime.
|
||||
//
|
||||
// Notably this is only used in the compilation phase, not runtime,
|
||||
// and we change the state of this struct at every wazeroir operation we compile.
|
||||
// In this way, we can see where the operands of an operation (for example,
|
||||
// two variables for wazeroir add operation.) exist and check the necessity for
|
||||
// moving the variable to registers to perform actual CPU instruction
|
||||
// to achieve wazeroir's add operation.
|
||||
type runtimeValueLocationStack struct {
|
||||
// stack holds all the variables.
|
||||
stack []runtimeValueLocation
|
||||
// sp is the current stack pointer.
|
||||
sp uint64
|
||||
// usedRegisters is the bit map to track the used registers.
|
||||
usedRegisters usedRegistersMask
|
||||
// stackPointerCeil tracks max(.sp) across the lifespan of this struct.
|
||||
stackPointerCeil uint64
|
||||
// unreservedGeneralPurposeRegisters and unreservedVectorRegisters hold
|
||||
// architecture dependent unreserved register list.
|
||||
unreservedGeneralPurposeRegisters, unreservedVectorRegisters []asm.Register
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocationStack) reset() {
|
||||
stack := v.stack[:0]
|
||||
*v = runtimeValueLocationStack{
|
||||
unreservedVectorRegisters: unreservedVectorRegisters,
|
||||
unreservedGeneralPurposeRegisters: unreservedGeneralPurposeRegisters,
|
||||
stack: stack,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocationStack) String() string {
|
||||
var stackStr []string
|
||||
for i := uint64(0); i < v.sp; i++ {
|
||||
stackStr = append(stackStr, v.stack[i].String())
|
||||
}
|
||||
usedRegisters := v.usedRegisters.list()
|
||||
return fmt.Sprintf("sp=%d, stack=[%s], used_registers=[%s]", v.sp, strings.Join(stackStr, ","), strings.Join(usedRegisters, ","))
|
||||
}
|
||||
|
||||
// cloneFrom clones the values on `from` into self except for the slice of .stack field.
|
||||
// The content on .stack will be copied from the origin to self, and grow the underlying slice
|
||||
// if necessary.
|
||||
func (v *runtimeValueLocationStack) cloneFrom(from runtimeValueLocationStack) {
|
||||
// Assigns the same values for fields except for the stack which we want to reuse.
|
||||
prev := v.stack
|
||||
*v = from
|
||||
v.stack = prev[:cap(prev)] // Expand the length to the capacity so that we can minimize "diff" below.
|
||||
// Copy the content in the stack.
|
||||
if diff := int(from.sp) - len(v.stack); diff > 0 {
|
||||
v.stack = append(v.stack, make([]runtimeValueLocation, diff)...)
|
||||
}
|
||||
copy(v.stack, from.stack[:from.sp])
|
||||
}
|
||||
|
||||
// pushRuntimeValueLocationOnRegister creates a new runtimeValueLocation with a given register and pushes onto
|
||||
// the location stack.
|
||||
func (v *runtimeValueLocationStack) pushRuntimeValueLocationOnRegister(reg asm.Register, vt runtimeValueType) (loc *runtimeValueLocation) {
|
||||
loc = v.push(reg, asm.ConditionalRegisterStateUnset)
|
||||
loc.valueType = vt
|
||||
return
|
||||
}
|
||||
|
||||
// pushRuntimeValueLocationOnRegister creates a new runtimeValueLocation and pushes onto the location stack.
|
||||
func (v *runtimeValueLocationStack) pushRuntimeValueLocationOnStack() (loc *runtimeValueLocation) {
|
||||
loc = v.push(asm.NilRegister, asm.ConditionalRegisterStateUnset)
|
||||
loc.valueType = runtimeValueTypeNone
|
||||
return
|
||||
}
|
||||
|
||||
// pushRuntimeValueLocationOnRegister creates a new runtimeValueLocation with a given conditional register state
|
||||
// and pushes onto the location stack.
|
||||
func (v *runtimeValueLocationStack) pushRuntimeValueLocationOnConditionalRegister(state asm.ConditionalRegisterState) (loc *runtimeValueLocation) {
|
||||
loc = v.push(asm.NilRegister, state)
|
||||
loc.valueType = runtimeValueTypeI32
|
||||
return
|
||||
}
|
||||
|
||||
// push a runtimeValueLocation onto the stack.
|
||||
func (v *runtimeValueLocationStack) push(reg asm.Register, conditionalRegister asm.ConditionalRegisterState) (ret *runtimeValueLocation) {
|
||||
if v.sp >= uint64(len(v.stack)) {
|
||||
// This case we need to grow the stack capacity by appending the item,
|
||||
// rather than indexing.
|
||||
v.stack = append(v.stack, runtimeValueLocation{})
|
||||
}
|
||||
ret = &v.stack[v.sp]
|
||||
ret.register, ret.conditionalRegister, ret.stackPointer = reg, conditionalRegister, v.sp
|
||||
v.sp++
|
||||
// stackPointerCeil must be set after sp is incremented since
|
||||
// we skip the stack grow if len(stack) >= basePointer+stackPointerCeil.
|
||||
if v.sp > v.stackPointerCeil {
|
||||
v.stackPointerCeil = v.sp
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocationStack) pop() (loc *runtimeValueLocation) {
|
||||
v.sp--
|
||||
loc = &v.stack[v.sp]
|
||||
return
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocationStack) popV128() (loc *runtimeValueLocation) {
|
||||
v.sp -= 2
|
||||
loc = &v.stack[v.sp]
|
||||
return
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocationStack) peek() (loc *runtimeValueLocation) {
|
||||
loc = &v.stack[v.sp-1]
|
||||
return
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocationStack) releaseRegister(loc *runtimeValueLocation) {
|
||||
v.markRegisterUnused(loc.register)
|
||||
loc.register = asm.NilRegister
|
||||
loc.conditionalRegister = asm.ConditionalRegisterStateUnset
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocationStack) markRegisterUnused(regs ...asm.Register) {
|
||||
for _, reg := range regs {
|
||||
v.usedRegisters.remove(reg)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *runtimeValueLocationStack) markRegisterUsed(regs ...asm.Register) {
|
||||
for _, reg := range regs {
|
||||
v.usedRegisters.add(reg)
|
||||
}
|
||||
}
|
||||
|
||||
type registerType byte
|
||||
|
||||
const (
|
||||
registerTypeGeneralPurpose registerType = iota
|
||||
// registerTypeVector represents a vector register which can be used for either scalar float
|
||||
// operation or SIMD vector operation depending on the instruction by which the register is used.
|
||||
//
|
||||
// Note: In normal assembly language, scalar float and vector register have different notations as
|
||||
// Vn is for vectors and Qn is for scalar floats on arm64 for example. But on physical hardware,
|
||||
// they are placed on the same locations. (Qn means the lower 64-bit of Vn vector register on arm64).
|
||||
//
|
||||
// In wazero, for the sake of simplicity in the register allocation, we intentionally conflate these two types
|
||||
// and delegate the decision to the assembler which is aware of the instruction types for which these registers are used.
|
||||
registerTypeVector
|
||||
)
|
||||
|
||||
func (tp registerType) String() (ret string) {
|
||||
switch tp {
|
||||
case registerTypeGeneralPurpose:
|
||||
ret = "int"
|
||||
case registerTypeVector:
|
||||
ret = "vector"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// takeFreeRegister searches for unused registers. Any found are marked used and returned.
|
||||
func (v *runtimeValueLocationStack) takeFreeRegister(tp registerType) (reg asm.Register, found bool) {
|
||||
var targetRegs []asm.Register
|
||||
switch tp {
|
||||
case registerTypeVector:
|
||||
targetRegs = v.unreservedVectorRegisters
|
||||
case registerTypeGeneralPurpose:
|
||||
targetRegs = v.unreservedGeneralPurposeRegisters
|
||||
}
|
||||
for _, candidate := range targetRegs {
|
||||
if v.usedRegisters.exist(candidate) {
|
||||
continue
|
||||
}
|
||||
return candidate, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Search through the stack, and steal the register from the last used
|
||||
// variable on the stack.
|
||||
func (v *runtimeValueLocationStack) takeStealTargetFromUsedRegister(tp registerType) (*runtimeValueLocation, bool) {
|
||||
for i := uint64(0); i < v.sp; i++ {
|
||||
loc := &v.stack[i]
|
||||
if loc.onRegister() {
|
||||
switch tp {
|
||||
case registerTypeVector:
|
||||
if loc.valueType == runtimeValueTypeV128Hi {
|
||||
panic("BUG: V128Hi must be above the corresponding V128Lo")
|
||||
}
|
||||
if isVectorRegister(loc.register) {
|
||||
return loc, true
|
||||
}
|
||||
case registerTypeGeneralPurpose:
|
||||
if isGeneralPurposeRegister(loc.register) {
|
||||
return loc, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// init sets up the runtimeValueLocationStack which reflects the state of
|
||||
// the stack at the beginning of the function.
|
||||
//
|
||||
// See the diagram in callEngine.stack.
|
||||
func (v *runtimeValueLocationStack) init(sig *wasm.FunctionType) {
|
||||
for _, t := range sig.Params {
|
||||
loc := v.pushRuntimeValueLocationOnStack()
|
||||
switch t {
|
||||
case wasm.ValueTypeI32:
|
||||
loc.valueType = runtimeValueTypeI32
|
||||
case wasm.ValueTypeI64, wasm.ValueTypeFuncref, wasm.ValueTypeExternref:
|
||||
loc.valueType = runtimeValueTypeI64
|
||||
case wasm.ValueTypeF32:
|
||||
loc.valueType = runtimeValueTypeF32
|
||||
case wasm.ValueTypeF64:
|
||||
loc.valueType = runtimeValueTypeF64
|
||||
case wasm.ValueTypeV128:
|
||||
loc.valueType = runtimeValueTypeV128Lo
|
||||
hi := v.pushRuntimeValueLocationOnStack()
|
||||
hi.valueType = runtimeValueTypeV128Hi
|
||||
default:
|
||||
panic("BUG")
|
||||
}
|
||||
}
|
||||
|
||||
// If the len(results) > len(args), the slots for all results are reserved after
|
||||
// arguments, so we reflect that into the location stack.
|
||||
for i := 0; i < sig.ResultNumInUint64-sig.ParamNumInUint64; i++ {
|
||||
_ = v.pushRuntimeValueLocationOnStack()
|
||||
}
|
||||
|
||||
// Then push the control frame fields.
|
||||
for i := 0; i < callFrameDataSizeInUint64; i++ {
|
||||
loc := v.pushRuntimeValueLocationOnStack()
|
||||
loc.valueType = runtimeValueTypeI64
|
||||
}
|
||||
}
|
||||
|
||||
// getCallFrameLocations returns each field of callFrame's runtime location.
|
||||
//
|
||||
// See the diagram in callEngine.stack.
|
||||
func (v *runtimeValueLocationStack) getCallFrameLocations(sig *wasm.FunctionType) (
|
||||
returnAddress, callerStackBasePointerInBytes, callerFunction *runtimeValueLocation,
|
||||
) {
|
||||
offset := callFrameOffset(sig)
|
||||
return &v.stack[offset], &v.stack[offset+1], &v.stack[offset+2]
|
||||
}
|
||||
|
||||
// pushCallFrame pushes a call frame's runtime locations onto the stack assuming that
|
||||
// the function call parameters are already pushed there.
|
||||
//
|
||||
// See the diagram in callEngine.stack.
|
||||
func (v *runtimeValueLocationStack) pushCallFrame(callTargetFunctionType *wasm.FunctionType) (
|
||||
returnAddress, callerStackBasePointerInBytes, callerFunction *runtimeValueLocation,
|
||||
) {
|
||||
// If len(results) > len(args), we reserve the slots for the results below the call frame.
|
||||
reservedSlotsBeforeCallFrame := callTargetFunctionType.ResultNumInUint64 - callTargetFunctionType.ParamNumInUint64
|
||||
for i := 0; i < reservedSlotsBeforeCallFrame; i++ {
|
||||
v.pushRuntimeValueLocationOnStack()
|
||||
}
|
||||
|
||||
// Push the runtime location for each field of callFrame struct. Note that each of them has
|
||||
// uint64 type, and therefore must be treated as runtimeValueTypeI64.
|
||||
|
||||
// callFrame.returnAddress
|
||||
returnAddress = v.pushRuntimeValueLocationOnStack()
|
||||
returnAddress.valueType = runtimeValueTypeI64
|
||||
// callFrame.returnStackBasePointerInBytes
|
||||
callerStackBasePointerInBytes = v.pushRuntimeValueLocationOnStack()
|
||||
callerStackBasePointerInBytes.valueType = runtimeValueTypeI64
|
||||
// callFrame.function
|
||||
callerFunction = v.pushRuntimeValueLocationOnStack()
|
||||
callerFunction.valueType = runtimeValueTypeI64
|
||||
return
|
||||
}
|
||||
|
||||
// usedRegistersMask tracks the used registers in its bits.
|
||||
type usedRegistersMask uint64
|
||||
|
||||
// add adds the given `r` to the mask.
|
||||
func (u *usedRegistersMask) add(r asm.Register) {
|
||||
*u = *u | (1 << registerMaskShift(r))
|
||||
}
|
||||
|
||||
// remove drops the given `r` from the mask.
|
||||
func (u *usedRegistersMask) remove(r asm.Register) {
|
||||
*u = *u & ^(1 << registerMaskShift(r))
|
||||
}
|
||||
|
||||
// exist returns true if the given `r` is used.
|
||||
func (u *usedRegistersMask) exist(r asm.Register) bool {
|
||||
shift := registerMaskShift(r)
|
||||
return (*u & (1 << shift)) > 0
|
||||
}
|
||||
|
||||
// list returns the list of debug string of used registers.
|
||||
// Only used for debugging and testing.
|
||||
func (u *usedRegistersMask) list() (ret []string) {
|
||||
mask := *u
|
||||
for i := 0; i < 64; i++ {
|
||||
if mask&(1<<i) > 0 {
|
||||
ret = append(ret, registerNameFn(registerFromMaskShift(i)))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
-1563
File diff suppressed because it is too large.
Load diff
Generated
Vendored
-233
@@ -1,233 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/internal/u32"
|
||||
"github.com/tetratelabs/wazero/internal/u64"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func (e *engine) deleteCompiledModule(module *wasm.Module) {
|
||||
e.mux.Lock()
|
||||
defer e.mux.Unlock()
|
||||
delete(e.codes, module.ID)
|
||||
|
||||
// Note: we do not call e.Cache.Delete, as the lifetime of
|
||||
// the content is up to the implementation of extencache.Cache interface.
|
||||
}
|
||||
|
||||
func (e *engine) addCompiledModule(module *wasm.Module, cm *compiledModule, withGoFunc bool) (err error) {
|
||||
e.addCompiledModuleToMemory(module, cm)
|
||||
if !withGoFunc {
|
||||
err = e.addCompiledModuleToCache(module, cm)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e *engine) getCompiledModule(module *wasm.Module, listeners []experimental.FunctionListener) (cm *compiledModule, ok bool, err error) {
|
||||
cm, ok = e.getCompiledModuleFromMemory(module)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
cm, ok, err = e.getCompiledModuleFromCache(module)
|
||||
if ok {
|
||||
e.addCompiledModuleToMemory(module, cm)
|
||||
if len(listeners) > 0 {
|
||||
// Files do not contain the actual listener instances (it's impossible to cache them as files!), so assign each here.
|
||||
for i := range cm.functions {
|
||||
cm.functions[i].listener = listeners[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e *engine) addCompiledModuleToMemory(module *wasm.Module, cm *compiledModule) {
|
||||
e.mux.Lock()
|
||||
defer e.mux.Unlock()
|
||||
e.codes[module.ID] = cm
|
||||
}
|
||||
|
||||
func (e *engine) getCompiledModuleFromMemory(module *wasm.Module) (cm *compiledModule, ok bool) {
|
||||
e.mux.RLock()
|
||||
defer e.mux.RUnlock()
|
||||
cm, ok = e.codes[module.ID]
|
||||
return
|
||||
}
|
||||
|
||||
func (e *engine) addCompiledModuleToCache(module *wasm.Module, cm *compiledModule) (err error) {
|
||||
if e.fileCache == nil || module.IsHostModule {
|
||||
return
|
||||
}
|
||||
err = e.fileCache.Add(module.ID, serializeCompiledModule(e.wazeroVersion, cm))
|
||||
return
|
||||
}
|
||||
|
||||
func (e *engine) getCompiledModuleFromCache(module *wasm.Module) (cm *compiledModule, hit bool, err error) {
|
||||
if e.fileCache == nil || module.IsHostModule {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the entries exist in the external cache.
|
||||
var cached io.ReadCloser
|
||||
cached, hit, err = e.fileCache.Get(module.ID)
|
||||
if !hit || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we hit the cache on external cache.
|
||||
// We retrieve *code structures from `cached`.
|
||||
var staleCache bool
|
||||
// Note: cached.Close is ensured to be called in deserializeCodes.
|
||||
cm, staleCache, err = deserializeCompiledModule(e.wazeroVersion, cached, module)
|
||||
if err != nil {
|
||||
hit = false
|
||||
return
|
||||
} else if staleCache {
|
||||
return nil, false, e.fileCache.Delete(module.ID)
|
||||
}
|
||||
|
||||
cm.source = module
|
||||
return
|
||||
}
|
||||
|
||||
var wazeroMagic = "WAZERO" // version must be synced with the tag of the wazero library.
|
||||
|
||||
func serializeCompiledModule(wazeroVersion string, cm *compiledModule) io.Reader {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
// First 6 byte: WAZERO header.
|
||||
buf.WriteString(wazeroMagic)
|
||||
// Next 1 byte: length of version:
|
||||
buf.WriteByte(byte(len(wazeroVersion)))
|
||||
// Version of wazero.
|
||||
buf.WriteString(wazeroVersion)
|
||||
if cm.ensureTermination {
|
||||
buf.WriteByte(1)
|
||||
} else {
|
||||
buf.WriteByte(0)
|
||||
}
|
||||
// Number of *code (== locally defined functions in the module): 4 bytes.
|
||||
buf.Write(u32.LeBytes(uint32(len(cm.functions))))
|
||||
for i := 0; i < len(cm.functions); i++ {
|
||||
f := &cm.functions[i]
|
||||
// The stack pointer ceil (8 bytes).
|
||||
buf.Write(u64.LeBytes(f.stackPointerCeil))
|
||||
// The offset of this function in the executable (8 bytes).
|
||||
buf.Write(u64.LeBytes(uint64(f.executableOffset)))
|
||||
}
|
||||
// The length of code segment (8 bytes).
|
||||
buf.Write(u64.LeBytes(uint64(cm.executable.Len())))
|
||||
// Append the native code.
|
||||
buf.Write(cm.executable.Bytes())
|
||||
return bytes.NewReader(buf.Bytes())
|
||||
}
|
||||
|
||||
func deserializeCompiledModule(wazeroVersion string, reader io.ReadCloser, module *wasm.Module) (cm *compiledModule, staleCache bool, err error) {
|
||||
defer reader.Close()
|
||||
cacheHeaderSize := len(wazeroMagic) + 1 /* version size */ + len(wazeroVersion) + 1 /* ensure termination */ + 4 /* number of functions */
|
||||
|
||||
// Read the header before the native code.
|
||||
header := make([]byte, cacheHeaderSize)
|
||||
n, err := reader.Read(header)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("compilationcache: error reading header: %v", err)
|
||||
}
|
||||
|
||||
if n != cacheHeaderSize {
|
||||
return nil, false, fmt.Errorf("compilationcache: invalid header length: %d", n)
|
||||
}
|
||||
|
||||
// Check the version compatibility.
|
||||
versionSize := int(header[len(wazeroMagic)])
|
||||
|
||||
cachedVersionBegin, cachedVersionEnd := len(wazeroMagic)+1, len(wazeroMagic)+1+versionSize
|
||||
if cachedVersionEnd >= len(header) {
|
||||
staleCache = true
|
||||
return
|
||||
} else if cachedVersion := string(header[cachedVersionBegin:cachedVersionEnd]); cachedVersion != wazeroVersion {
|
||||
staleCache = true
|
||||
return
|
||||
}
|
||||
|
||||
ensureTermination := header[cachedVersionEnd] != 0
|
||||
functionsNum := binary.LittleEndian.Uint32(header[len(header)-4:])
|
||||
cm = &compiledModule{functions: make([]compiledFunction, functionsNum), ensureTermination: ensureTermination}
|
||||
|
||||
imported := module.ImportFunctionCount
|
||||
|
||||
var eightBytes [8]byte
|
||||
for i := uint32(0); i < functionsNum; i++ {
|
||||
f := &cm.functions[i]
|
||||
f.parent = cm
|
||||
|
||||
// Read the stack pointer ceil.
|
||||
if f.stackPointerCeil, err = readUint64(reader, &eightBytes); err != nil {
|
||||
err = fmt.Errorf("compilationcache: error reading func[%d] stack pointer ceil: %v", i, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Read the offset of each function in the executable.
|
||||
var offset uint64
|
||||
if offset, err = readUint64(reader, &eightBytes); err != nil {
|
||||
err = fmt.Errorf("compilationcache: error reading func[%d] executable offset: %v", i, err)
|
||||
return
|
||||
}
|
||||
f.executableOffset = uintptr(offset)
|
||||
f.index = imported + i
|
||||
}
|
||||
|
||||
executableLen, err := readUint64(reader, &eightBytes)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("compilationcache: error reading executable size: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if executableLen > 0 {
|
||||
if err = cm.executable.Map(int(executableLen)); err != nil {
|
||||
err = fmt.Errorf("compilationcache: error mmapping executable (len=%d): %v", executableLen, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(reader, cm.executable.Bytes())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("compilationcache: error reading executable (len=%d): %v", executableLen, err)
|
||||
return
|
||||
}
|
||||
|
||||
if runtime.GOARCH == "arm64" {
|
||||
// On arm64, we cannot give all of rwx at the same time, so we change it to exec.
|
||||
if err = platform.MprotectRX(cm.executable.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// readUint64 strictly reads an uint64 in little-endian byte order, using the
|
||||
// given array as a buffer. This returns io.EOF if less than 8 bytes were read.
|
||||
func readUint64(reader io.Reader, b *[8]byte) (uint64, error) {
|
||||
s := b[0:8]
|
||||
n, err := reader.Read(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if n < 8 { // more strict than reader.Read
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
// Read the u64 from the underlying buffer.
|
||||
ret := binary.LittleEndian.Uint64(s)
|
||||
|
||||
// Clear the underlying array.
|
||||
for i := 0; i < 8; i++ {
|
||||
b[i] = 0
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
Generated
Vendored
-5239
File diff suppressed because it is too large.
Load diff
Generated
Vendored
-4353
File diff suppressed because it is too large.
Load diff
Generated
Vendored
-2812
File diff suppressed because it is too large.
Load diff
Generated
Vendored
-1592
File diff suppressed because it is too large.
Load diff
Generated
Vendored
+469
-27
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/expctxkeys"
|
||||
"github.com/tetratelabs/wazero/internal/filecache"
|
||||
"github.com/tetratelabs/wazero/internal/internalapi"
|
||||
"github.com/tetratelabs/wazero/internal/moremath"
|
||||
@@ -87,6 +88,19 @@ type moduleEngine struct {
|
||||
parentEngine *engine
|
||||
}
|
||||
|
||||
// GetGlobalValue implements the same method as documented on wasm.ModuleEngine.
|
||||
func (e *moduleEngine) GetGlobalValue(wasm.Index) (lo, hi uint64) {
|
||||
panic("BUG: GetGlobalValue should never be called on interpreter mode")
|
||||
}
|
||||
|
||||
// SetGlobalValue implements the same method as documented on wasm.ModuleEngine.
|
||||
func (e *moduleEngine) SetGlobalValue(idx wasm.Index, lo, hi uint64) {
|
||||
panic("BUG: SetGlobalValue should never be called on interpreter mode")
|
||||
}
|
||||
|
||||
// OwnsGlobals implements the same method as documented on wasm.ModuleEngine.
|
||||
func (e *moduleEngine) OwnsGlobals() bool { return false }
|
||||
|
||||
// callEngine holds context per moduleEngine.Call, and shared across all the
|
||||
// function calls originating from the same moduleEngine.Call execution.
|
||||
//
|
||||
@@ -216,6 +230,53 @@ func functionFromUintptr(ptr uintptr) *function {
|
||||
return *(**function)(unsafe.Pointer(wrapped))
|
||||
}
|
||||
|
||||
type snapshot struct {
|
||||
stack []uint64
|
||||
frames []*callFrame
|
||||
pc uint64
|
||||
|
||||
ret []uint64
|
||||
|
||||
ce *callEngine
|
||||
}
|
||||
|
||||
// Snapshot implements the same method as documented on experimental.Snapshotter.
|
||||
func (ce *callEngine) Snapshot() experimental.Snapshot {
|
||||
stack := make([]uint64, len(ce.stack))
|
||||
copy(stack, ce.stack)
|
||||
|
||||
frames := make([]*callFrame, len(ce.frames))
|
||||
copy(frames, ce.frames)
|
||||
|
||||
return &snapshot{
|
||||
stack: stack,
|
||||
frames: frames,
|
||||
ce: ce,
|
||||
}
|
||||
}
|
||||
|
||||
// Restore implements the same method as documented on experimental.Snapshot.
|
||||
func (s *snapshot) Restore(ret []uint64) {
|
||||
s.ret = ret
|
||||
panic(s)
|
||||
}
|
||||
|
||||
func (s *snapshot) doRestore() {
|
||||
ce := s.ce
|
||||
|
||||
ce.stack = s.stack
|
||||
ce.frames = s.frames
|
||||
ce.frames[len(ce.frames)-1].pc = s.pc
|
||||
|
||||
copy(ce.stack[len(ce.stack)-len(s.ret):], s.ret)
|
||||
}
|
||||
|
||||
// Error implements the same method on error.
|
||||
func (s *snapshot) Error() string {
|
||||
return "unhandled snapshot restore, this generally indicates restore was called from a different " +
|
||||
"exported function invocation than snapshot"
|
||||
}
|
||||
|
||||
// stackIterator implements experimental.StackIterator.
|
||||
type stackIterator struct {
|
||||
stack []uint64
|
||||
@@ -271,14 +332,6 @@ func (si *stackIterator) ProgramCounter() experimental.ProgramCounter {
|
||||
return experimental.ProgramCounter(si.pc)
|
||||
}
|
||||
|
||||
// Parameters implements the same method as documented on
|
||||
// experimental.StackIterator.
|
||||
func (si *stackIterator) Parameters() []uint64 {
|
||||
paramsCount := si.fn.funcType.ParamNumInUint64
|
||||
top := len(si.stack)
|
||||
return si.stack[top-paramsCount:]
|
||||
}
|
||||
|
||||
// internalFunction implements experimental.InternalFunction.
|
||||
type internalFunction struct{ *function }
|
||||
|
||||
@@ -442,6 +495,12 @@ func (e *moduleEngine) ResolveImportedFunction(index, indexInImportedModule wasm
|
||||
e.functions[index] = imported.functions[indexInImportedModule]
|
||||
}
|
||||
|
||||
// ResolveImportedMemory implements wasm.ModuleEngine.
|
||||
func (e *moduleEngine) ResolveImportedMemory(wasm.ModuleEngine) {}
|
||||
|
||||
// DoneInstantiation implements wasm.ModuleEngine.
|
||||
func (e *moduleEngine) DoneInstantiation() {}
|
||||
|
||||
// FunctionInstanceReference implements the same method as documented on wasm.ModuleEngine.
|
||||
func (e *moduleEngine) FunctionInstanceReference(funcIndex wasm.Index) wasm.Reference {
|
||||
return uintptr(unsafe.Pointer(&e.functions[funcIndex]))
|
||||
@@ -456,25 +515,20 @@ func (e *moduleEngine) NewFunction(index wasm.Index) (ce api.Function) {
|
||||
}
|
||||
|
||||
// LookupFunction implements the same method as documented on wasm.ModuleEngine.
|
||||
func (e *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (f api.Function, err error) {
|
||||
func (e *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (*wasm.ModuleInstance, wasm.Index) {
|
||||
if tableOffset >= uint32(len(t.References)) {
|
||||
err = wasmruntime.ErrRuntimeInvalidTableAccess
|
||||
return
|
||||
panic(wasmruntime.ErrRuntimeInvalidTableAccess)
|
||||
}
|
||||
rawPtr := t.References[tableOffset]
|
||||
if rawPtr == 0 {
|
||||
err = wasmruntime.ErrRuntimeInvalidTableAccess
|
||||
return
|
||||
panic(wasmruntime.ErrRuntimeInvalidTableAccess)
|
||||
}
|
||||
|
||||
tf := functionFromUintptr(rawPtr)
|
||||
if tf.typeID != typeId {
|
||||
err = wasmruntime.ErrRuntimeIndirectCallTypeMismatch
|
||||
return
|
||||
panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch)
|
||||
}
|
||||
|
||||
f = e.newCallEngine(tf)
|
||||
return
|
||||
return tf.moduleInstance, tf.parent.index
|
||||
}
|
||||
|
||||
// Definition implements the same method as documented on api.Function.
|
||||
@@ -519,6 +573,10 @@ func (ce *callEngine) call(ctx context.Context, params, results []uint64) (_ []u
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Value(expctxkeys.EnableSnapshotterKey{}) != nil {
|
||||
ctx = context.WithValue(ctx, expctxkeys.SnapshotterKey{}, ce)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// If the module closed during the call, and the call didn't err for another reason, set an ExitError.
|
||||
if err == nil {
|
||||
@@ -562,10 +620,19 @@ type functionListenerInvocation struct {
|
||||
// with the call frame stack traces. Also, reset the state of callEngine
|
||||
// so that it can be used for the subsequent calls.
|
||||
func (ce *callEngine) recoverOnCall(ctx context.Context, m *wasm.ModuleInstance, v interface{}) (err error) {
|
||||
if s, ok := v.(*snapshot); ok {
|
||||
// A snapshot that wasn't handled was created by a different call engine possibly from a nested wasm invocation,
|
||||
// let it propagate up to be handled by the caller.
|
||||
panic(s)
|
||||
}
|
||||
|
||||
builder := wasmdebug.NewErrorBuilder()
|
||||
frameCount := len(ce.frames)
|
||||
functionListeners := make([]functionListenerInvocation, 0, 16)
|
||||
|
||||
if frameCount > wasmdebug.MaxFrames {
|
||||
frameCount = wasmdebug.MaxFrames
|
||||
}
|
||||
for i := 0; i < frameCount; i++ {
|
||||
frame := ce.popFrame()
|
||||
f := frame.f
|
||||
@@ -676,7 +743,23 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
ce.drop(op.Us[v+1])
|
||||
frame.pc = op.Us[v]
|
||||
case wazeroir.OperationKindCall:
|
||||
ce.callFunction(ctx, f.moduleInstance, &functions[op.U1])
|
||||
func() {
|
||||
if ctx.Value(expctxkeys.EnableSnapshotterKey{}) != nil {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if s, ok := r.(*snapshot); ok && s.ce == ce {
|
||||
s.doRestore()
|
||||
frame = ce.frames[len(ce.frames)-1]
|
||||
body = frame.f.parent.body
|
||||
bodyLen = uint64(len(body))
|
||||
} else {
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
ce.callFunction(ctx, f.moduleInstance, &functions[op.U1])
|
||||
}()
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindCallIndirect:
|
||||
offset := ce.popValue()
|
||||
@@ -844,7 +927,7 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
}
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindMemorySize:
|
||||
ce.pushValue(uint64(memoryInst.PageSize()))
|
||||
ce.pushValue(uint64(memoryInst.Pages()))
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindMemoryGrow:
|
||||
n := ce.popValue()
|
||||
@@ -1660,15 +1743,15 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
inElementOffset := ce.popValue()
|
||||
inTableOffset := ce.popValue()
|
||||
table := tables[op.U2]
|
||||
if inElementOffset+copySize > uint64(len(elementInstance.References)) ||
|
||||
if inElementOffset+copySize > uint64(len(elementInstance)) ||
|
||||
inTableOffset+copySize > uint64(len(table.References)) {
|
||||
panic(wasmruntime.ErrRuntimeInvalidTableAccess)
|
||||
} else if copySize != 0 {
|
||||
copy(table.References[inTableOffset:inTableOffset+copySize], elementInstance.References[inElementOffset:])
|
||||
copy(table.References[inTableOffset:inTableOffset+copySize], elementInstance[inElementOffset:])
|
||||
}
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindElemDrop:
|
||||
elementInstances[op.U1].References = nil
|
||||
elementInstances[op.U1] = nil
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindTableCopy:
|
||||
srcTable, dstTable := tables[op.U1].References, tables[op.U2].References
|
||||
@@ -2004,12 +2087,17 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
case wazeroir.OperationKindV128Store:
|
||||
hi, lo := ce.popValue(), ce.popValue()
|
||||
offset := ce.popMemoryOffset(op)
|
||||
if ok := memoryInst.WriteUint64Le(offset, lo); !ok {
|
||||
// Write the upper bytes first to trigger an early error if the memory access is out of bounds.
|
||||
// Otherwise, the lower bytes might be written to memory, but the upper bytes might not.
|
||||
if uint64(offset)+8 > math.MaxUint32 {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
if ok := memoryInst.WriteUint64Le(offset+8, hi); !ok {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
if ok := memoryInst.WriteUint64Le(offset, lo); !ok {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindV128StoreLane:
|
||||
hi, lo := ce.popValue(), ce.popValue()
|
||||
@@ -3896,6 +3984,361 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
ce.pushValue(retLo)
|
||||
ce.pushValue(retHi)
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicMemoryWait:
|
||||
timeout := int64(ce.popValue())
|
||||
exp := ce.popValue()
|
||||
offset := ce.popMemoryOffset(op)
|
||||
// Runtime instead of validation error because the spec intends to allow binaries to include
|
||||
// such instructions as long as they are not executed.
|
||||
if !memoryInst.Shared {
|
||||
panic(wasmruntime.ErrRuntimeExpectedSharedMemory)
|
||||
}
|
||||
|
||||
switch wazeroir.UnsignedType(op.B1) {
|
||||
case wazeroir.UnsignedTypeI32:
|
||||
if offset%4 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
if int(offset) > len(memoryInst.Buffer)-4 {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
ce.pushValue(memoryInst.Wait32(offset, uint32(exp), timeout, func(mem *wasm.MemoryInstance, offset uint32) uint32 {
|
||||
mem.Mux.Lock()
|
||||
defer mem.Mux.Unlock()
|
||||
value, _ := mem.ReadUint32Le(offset)
|
||||
return value
|
||||
}))
|
||||
case wazeroir.UnsignedTypeI64:
|
||||
if offset%8 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
if int(offset) > len(memoryInst.Buffer)-8 {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
ce.pushValue(memoryInst.Wait64(offset, exp, timeout, func(mem *wasm.MemoryInstance, offset uint32) uint64 {
|
||||
mem.Mux.Lock()
|
||||
defer mem.Mux.Unlock()
|
||||
value, _ := mem.ReadUint64Le(offset)
|
||||
return value
|
||||
}))
|
||||
}
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicMemoryNotify:
|
||||
count := ce.popValue()
|
||||
offset := ce.popMemoryOffset(op)
|
||||
if offset%4 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
// Just a bounds check
|
||||
if offset >= memoryInst.Size() {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
res := memoryInst.Notify(offset, uint32(count))
|
||||
ce.pushValue(uint64(res))
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicFence:
|
||||
// Memory not required for fence only
|
||||
if memoryInst != nil {
|
||||
// An empty critical section can be used as a synchronization primitive, which is what
|
||||
// fence is. Probably, there are no spectests or defined behavior to confirm this yet.
|
||||
memoryInst.Mux.Lock()
|
||||
memoryInst.Mux.Unlock() //nolint:staticcheck
|
||||
}
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicLoad:
|
||||
offset := ce.popMemoryOffset(op)
|
||||
switch wazeroir.UnsignedType(op.B1) {
|
||||
case wazeroir.UnsignedTypeI32:
|
||||
if offset%4 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
memoryInst.Mux.Lock()
|
||||
val, ok := memoryInst.ReadUint32Le(offset)
|
||||
memoryInst.Mux.Unlock()
|
||||
if !ok {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
ce.pushValue(uint64(val))
|
||||
case wazeroir.UnsignedTypeI64:
|
||||
if offset%8 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
memoryInst.Mux.Lock()
|
||||
val, ok := memoryInst.ReadUint64Le(offset)
|
||||
memoryInst.Mux.Unlock()
|
||||
if !ok {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
ce.pushValue(val)
|
||||
}
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicLoad8:
|
||||
offset := ce.popMemoryOffset(op)
|
||||
memoryInst.Mux.Lock()
|
||||
val, ok := memoryInst.ReadByte(offset)
|
||||
memoryInst.Mux.Unlock()
|
||||
if !ok {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
ce.pushValue(uint64(val))
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicLoad16:
|
||||
offset := ce.popMemoryOffset(op)
|
||||
if offset%2 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
memoryInst.Mux.Lock()
|
||||
val, ok := memoryInst.ReadUint16Le(offset)
|
||||
memoryInst.Mux.Unlock()
|
||||
if !ok {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
ce.pushValue(uint64(val))
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicStore:
|
||||
val := ce.popValue()
|
||||
offset := ce.popMemoryOffset(op)
|
||||
switch wazeroir.UnsignedType(op.B1) {
|
||||
case wazeroir.UnsignedTypeI32:
|
||||
if offset%4 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
memoryInst.Mux.Lock()
|
||||
ok := memoryInst.WriteUint32Le(offset, uint32(val))
|
||||
memoryInst.Mux.Unlock()
|
||||
if !ok {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
case wazeroir.UnsignedTypeI64:
|
||||
if offset%8 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
memoryInst.Mux.Lock()
|
||||
ok := memoryInst.WriteUint64Le(offset, val)
|
||||
memoryInst.Mux.Unlock()
|
||||
if !ok {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
}
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicStore8:
|
||||
val := byte(ce.popValue())
|
||||
offset := ce.popMemoryOffset(op)
|
||||
memoryInst.Mux.Lock()
|
||||
ok := memoryInst.WriteByte(offset, val)
|
||||
memoryInst.Mux.Unlock()
|
||||
if !ok {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicStore16:
|
||||
val := uint16(ce.popValue())
|
||||
offset := ce.popMemoryOffset(op)
|
||||
if offset%2 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
memoryInst.Mux.Lock()
|
||||
ok := memoryInst.WriteUint16Le(offset, val)
|
||||
memoryInst.Mux.Unlock()
|
||||
if !ok {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicRMW:
|
||||
val := ce.popValue()
|
||||
offset := ce.popMemoryOffset(op)
|
||||
switch wazeroir.UnsignedType(op.B1) {
|
||||
case wazeroir.UnsignedTypeI32:
|
||||
if offset%4 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
memoryInst.Mux.Lock()
|
||||
old, ok := memoryInst.ReadUint32Le(offset)
|
||||
if !ok {
|
||||
memoryInst.Mux.Unlock()
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
var newVal uint32
|
||||
switch wazeroir.AtomicArithmeticOp(op.B2) {
|
||||
case wazeroir.AtomicArithmeticOpAdd:
|
||||
newVal = old + uint32(val)
|
||||
case wazeroir.AtomicArithmeticOpSub:
|
||||
newVal = old - uint32(val)
|
||||
case wazeroir.AtomicArithmeticOpAnd:
|
||||
newVal = old & uint32(val)
|
||||
case wazeroir.AtomicArithmeticOpOr:
|
||||
newVal = old | uint32(val)
|
||||
case wazeroir.AtomicArithmeticOpXor:
|
||||
newVal = old ^ uint32(val)
|
||||
case wazeroir.AtomicArithmeticOpNop:
|
||||
newVal = uint32(val)
|
||||
}
|
||||
memoryInst.WriteUint32Le(offset, newVal)
|
||||
memoryInst.Mux.Unlock()
|
||||
ce.pushValue(uint64(old))
|
||||
case wazeroir.UnsignedTypeI64:
|
||||
if offset%8 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
memoryInst.Mux.Lock()
|
||||
old, ok := memoryInst.ReadUint64Le(offset)
|
||||
if !ok {
|
||||
memoryInst.Mux.Unlock()
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
var newVal uint64
|
||||
switch wazeroir.AtomicArithmeticOp(op.B2) {
|
||||
case wazeroir.AtomicArithmeticOpAdd:
|
||||
newVal = old + val
|
||||
case wazeroir.AtomicArithmeticOpSub:
|
||||
newVal = old - val
|
||||
case wazeroir.AtomicArithmeticOpAnd:
|
||||
newVal = old & val
|
||||
case wazeroir.AtomicArithmeticOpOr:
|
||||
newVal = old | val
|
||||
case wazeroir.AtomicArithmeticOpXor:
|
||||
newVal = old ^ val
|
||||
case wazeroir.AtomicArithmeticOpNop:
|
||||
newVal = val
|
||||
}
|
||||
memoryInst.WriteUint64Le(offset, newVal)
|
||||
memoryInst.Mux.Unlock()
|
||||
ce.pushValue(old)
|
||||
}
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicRMW8:
|
||||
val := ce.popValue()
|
||||
offset := ce.popMemoryOffset(op)
|
||||
memoryInst.Mux.Lock()
|
||||
old, ok := memoryInst.ReadByte(offset)
|
||||
if !ok {
|
||||
memoryInst.Mux.Unlock()
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
arg := byte(val)
|
||||
var newVal byte
|
||||
switch wazeroir.AtomicArithmeticOp(op.B2) {
|
||||
case wazeroir.AtomicArithmeticOpAdd:
|
||||
newVal = old + arg
|
||||
case wazeroir.AtomicArithmeticOpSub:
|
||||
newVal = old - arg
|
||||
case wazeroir.AtomicArithmeticOpAnd:
|
||||
newVal = old & arg
|
||||
case wazeroir.AtomicArithmeticOpOr:
|
||||
newVal = old | arg
|
||||
case wazeroir.AtomicArithmeticOpXor:
|
||||
newVal = old ^ arg
|
||||
case wazeroir.AtomicArithmeticOpNop:
|
||||
newVal = arg
|
||||
}
|
||||
memoryInst.WriteByte(offset, newVal)
|
||||
memoryInst.Mux.Unlock()
|
||||
ce.pushValue(uint64(old))
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicRMW16:
|
||||
val := ce.popValue()
|
||||
offset := ce.popMemoryOffset(op)
|
||||
if offset%2 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
memoryInst.Mux.Lock()
|
||||
old, ok := memoryInst.ReadUint16Le(offset)
|
||||
if !ok {
|
||||
memoryInst.Mux.Unlock()
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
arg := uint16(val)
|
||||
var newVal uint16
|
||||
switch wazeroir.AtomicArithmeticOp(op.B2) {
|
||||
case wazeroir.AtomicArithmeticOpAdd:
|
||||
newVal = old + arg
|
||||
case wazeroir.AtomicArithmeticOpSub:
|
||||
newVal = old - arg
|
||||
case wazeroir.AtomicArithmeticOpAnd:
|
||||
newVal = old & arg
|
||||
case wazeroir.AtomicArithmeticOpOr:
|
||||
newVal = old | arg
|
||||
case wazeroir.AtomicArithmeticOpXor:
|
||||
newVal = old ^ arg
|
||||
case wazeroir.AtomicArithmeticOpNop:
|
||||
newVal = arg
|
||||
}
|
||||
memoryInst.WriteUint16Le(offset, newVal)
|
||||
memoryInst.Mux.Unlock()
|
||||
ce.pushValue(uint64(old))
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicRMWCmpxchg:
|
||||
rep := ce.popValue()
|
||||
exp := ce.popValue()
|
||||
offset := ce.popMemoryOffset(op)
|
||||
switch wazeroir.UnsignedType(op.B1) {
|
||||
case wazeroir.UnsignedTypeI32:
|
||||
if offset%4 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
memoryInst.Mux.Lock()
|
||||
old, ok := memoryInst.ReadUint32Le(offset)
|
||||
if !ok {
|
||||
memoryInst.Mux.Unlock()
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
if old == uint32(exp) {
|
||||
memoryInst.WriteUint32Le(offset, uint32(rep))
|
||||
}
|
||||
memoryInst.Mux.Unlock()
|
||||
ce.pushValue(uint64(old))
|
||||
case wazeroir.UnsignedTypeI64:
|
||||
if offset%8 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
memoryInst.Mux.Lock()
|
||||
old, ok := memoryInst.ReadUint64Le(offset)
|
||||
if !ok {
|
||||
memoryInst.Mux.Unlock()
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
if old == exp {
|
||||
memoryInst.WriteUint64Le(offset, rep)
|
||||
}
|
||||
memoryInst.Mux.Unlock()
|
||||
ce.pushValue(old)
|
||||
}
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicRMW8Cmpxchg:
|
||||
rep := byte(ce.popValue())
|
||||
exp := byte(ce.popValue())
|
||||
offset := ce.popMemoryOffset(op)
|
||||
memoryInst.Mux.Lock()
|
||||
old, ok := memoryInst.ReadByte(offset)
|
||||
if !ok {
|
||||
memoryInst.Mux.Unlock()
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
if old == exp {
|
||||
memoryInst.WriteByte(offset, rep)
|
||||
}
|
||||
memoryInst.Mux.Unlock()
|
||||
ce.pushValue(uint64(old))
|
||||
frame.pc++
|
||||
case wazeroir.OperationKindAtomicRMW16Cmpxchg:
|
||||
rep := uint16(ce.popValue())
|
||||
exp := uint16(ce.popValue())
|
||||
offset := ce.popMemoryOffset(op)
|
||||
if offset%2 != 0 {
|
||||
panic(wasmruntime.ErrRuntimeUnalignedAtomic)
|
||||
}
|
||||
memoryInst.Mux.Lock()
|
||||
old, ok := memoryInst.ReadUint16Le(offset)
|
||||
if !ok {
|
||||
memoryInst.Mux.Unlock()
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
if old == exp {
|
||||
memoryInst.WriteUint16Le(offset, rep)
|
||||
}
|
||||
memoryInst.Mux.Unlock()
|
||||
ce.pushValue(uint64(old))
|
||||
frame.pc++
|
||||
default:
|
||||
frame.pc++
|
||||
}
|
||||
@@ -4103,17 +4546,16 @@ func (ce *callEngine) callNativeFuncWithListener(ctx context.Context, m *wasm.Mo
|
||||
def, typ := f.definition(), f.funcType
|
||||
|
||||
ce.stackIterator.reset(ce.stack, ce.frames, f)
|
||||
fnl.Before(ctx, m, def, ce.peekValues(len(typ.Params)), &ce.stackIterator)
|
||||
fnl.Before(ctx, m, def, ce.peekValues(typ.ParamNumInUint64), &ce.stackIterator)
|
||||
ce.stackIterator.clear()
|
||||
ce.callNativeFunc(ctx, m, f)
|
||||
fnl.After(ctx, m, def, ce.peekValues(len(typ.Results)))
|
||||
fnl.After(ctx, m, def, ce.peekValues(typ.ResultNumInUint64))
|
||||
return ctx
|
||||
}
|
||||
|
||||
// popMemoryOffset takes a memory offset off the stack for use in load and store instructions.
|
||||
// As the top of stack value is 64-bit, this ensures it is in range before returning it.
|
||||
func (ce *callEngine) popMemoryOffset(op *wazeroir.UnionOperation) uint32 {
|
||||
// TODO: Document what 'us' is and why we expect to look at value 1.
|
||||
offset := op.U2 + ce.popValue()
|
||||
if offset > math.MaxUint32 {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
wazevo is the new optimizing compiler engine which only supports arm64. AMD64 is under active development.
|
||||
Loaded 100 of 2507 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user