Files
wazero/internal/gojs/state.go
Crypt Keeper 8464474e21 gojs: adds support for uid and gid (#1245)
This adds `gojs.WithOSUser` which passes through current user IDs so
that GOOS=js compiled wasm can read them. This also adds support for
reading back the uid and gid on files. In summary, this passes
`os.TestChown` except on windows where it will not work due to lack of
support.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2023-03-16 11:07:27 +08:00

244 lines
6.6 KiB
Go

package gojs
import (
"context"
"fmt"
"math"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/gojs/config"
"github.com/tetratelabs/wazero/internal/gojs/goos"
"github.com/tetratelabs/wazero/internal/gojs/values"
)
func NewState(config *config.Config) *State {
return &State{
values: values.NewValues(),
valueGlobal: newJsGlobal(config),
_nextCallbackTimeoutID: 1,
_scheduledTimeouts: map[uint32]chan bool{},
}
}
// StateKey is a context.Context Value key. The value must be a state pointer.
type StateKey struct{}
func getState(ctx context.Context) *State {
return ctx.Value(StateKey{}).(*State)
}
// GetLastEventArgs implements goos.GetLastEventArgs
func GetLastEventArgs(ctx context.Context) []interface{} {
if ls := ctx.Value(StateKey{}).(*State)._lastEvent; ls != nil {
if args := ls.args; args != nil {
return args.slice
}
}
return nil
}
type event struct {
// id is the funcWrapper.id
id uint32
this goos.Ref
args *objectArray
result interface{}
}
// Get implements the same method as documented on goos.GetFunction
func (e *event) Get(propertyKey string) interface{} {
switch propertyKey {
case "id":
return e.id
case "this": // ex fs
return e.this
case "args":
return e.args
}
panic(fmt.Sprintf("TODO: event.%s", propertyKey))
}
var NaN = math.NaN()
// LoadValue reads up to 8 bytes at the memory offset `addr` to return the
// value written by storeValue.
//
// See https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L122-L133
func LoadValue(ctx context.Context, ref goos.Ref) interface{} { //nolint
switch ref {
case 0:
return goos.Undefined
case goos.RefValueNaN:
return NaN
case goos.RefValueZero:
return float64(0)
case goos.RefValueNull:
return nil
case goos.RefValueTrue:
return true
case goos.RefValueFalse:
return false
case goos.RefValueGlobal:
return getState(ctx).valueGlobal
case goos.RefJsGo:
return getState(ctx)
case goos.RefObjectConstructor:
return objectConstructor
case goos.RefArrayConstructor:
return arrayConstructor
case goos.RefJsProcess:
return getState(ctx).valueGlobal.Get("process")
case goos.RefJsfs:
return getState(ctx).valueGlobal.Get("fs")
case goos.RefJsfsConstants:
return jsfsConstants
case goos.RefUint8ArrayConstructor:
return uint8ArrayConstructor
case goos.RefJsCrypto:
return jsCrypto
case goos.RefJsDateConstructor:
return jsDateConstructor
case goos.RefJsDate:
return jsDate
case goos.RefHttpHeadersConstructor:
return headersConstructor
default:
if f, ok := ref.ParseFloat(); ok { // numbers are passed through as a Ref
return f
}
return getState(ctx).values.Get(uint32(ref))
}
}
// storeValue stores a value prior to returning to wasm from a host function.
// This returns 8 bytes to represent either the value or a reference to it.
// Any side effects besides memory must be cleaned up on wasmExit.
//
// See https://github.com/golang/go/blob/de4748c47c67392a57f250714509f590f68ad395/misc/wasm/wasm_exec.js#L135-L183
func storeValue(ctx context.Context, v interface{}) goos.Ref { //nolint
// allow-list because we control all implementations
if v == goos.Undefined {
return goos.RefValueUndefined
} else if v == nil {
return goos.RefValueNull
} else if r, ok := v.(goos.Ref); ok {
return r
} else if b, ok := v.(bool); ok {
if b {
return goos.RefValueTrue
} else {
return goos.RefValueFalse
}
} else if c, ok := v.(*jsVal); ok {
return c.ref // already stored
} else if _, ok := v.(*event); ok {
id := getState(ctx).values.Increment(v)
return goos.ValueRef(id, goos.TypeFlagFunction)
} else if _, ok := v.(funcWrapper); ok {
id := getState(ctx).values.Increment(v)
return goos.ValueRef(id, goos.TypeFlagFunction)
} else if _, ok := v.(jsFn); ok {
id := getState(ctx).values.Increment(v)
return goos.ValueRef(id, goos.TypeFlagFunction)
} else if _, ok := v.(string); ok {
id := getState(ctx).values.Increment(v)
return goos.ValueRef(id, goos.TypeFlagString)
} else if i32, ok := v.(int32); ok {
return toFloatRef(float64(i32))
} else if u32, ok := v.(uint32); ok {
return toFloatRef(float64(u32))
} else if i64, ok := v.(int64); ok {
return toFloatRef(float64(i64))
} else if u64, ok := v.(uint64); ok {
return toFloatRef(float64(u64))
} else if f64, ok := v.(float64); ok {
return toFloatRef(f64)
}
id := getState(ctx).values.Increment(v)
return goos.ValueRef(id, goos.TypeFlagObject)
}
func toFloatRef(f float64) goos.Ref {
if f == 0 {
return goos.RefValueZero
}
// numbers are encoded as float and passed through as a Ref
return goos.Ref(api.EncodeF64(f))
}
// State holds state used by the "go" imports used by gojs.
// Note: This is module-scoped.
type State struct {
values *values.Values
_pendingEvent *event
// _lastEvent was the last _pendingEvent value
_lastEvent *event
valueGlobal *jsVal
_nextCallbackTimeoutID uint32
_scheduledTimeouts map[uint32]chan bool
}
// Get implements the same method as documented on goos.GetFunction
func (s *State) Get(propertyKey string) interface{} {
switch propertyKey {
case "_pendingEvent":
return s._pendingEvent
}
panic(fmt.Sprintf("TODO: state.%s", propertyKey))
}
// call implements jsCall.call
func (s *State) call(_ context.Context, _ api.Module, _ goos.Ref, method string, args ...interface{}) (interface{}, error) {
switch method {
case "_makeFuncWrapper":
return funcWrapper(args[0].(float64)), nil
}
panic(fmt.Sprintf("TODO: state.%s", method))
}
// close releases any state including values and underlying slices for garbage
// collection.
func (s *State) close() {
// _scheduledTimeouts may have in-flight goroutines, so cancel them.
for k, cancel := range s._scheduledTimeouts {
delete(s._scheduledTimeouts, k)
cancel <- true
}
// Reset all state recursively to their initial values. This allows our
// unit tests to check we closed everything.
s._scheduledTimeouts = map[uint32]chan bool{}
s.values.Reset()
s._pendingEvent = nil
s._lastEvent = nil
s._nextCallbackTimeoutID = 1
}
func toInt64(arg interface{}) int64 {
if arg == goos.RefValueZero || arg == goos.Undefined {
return 0
} else if u, ok := arg.(int64); ok {
return u
}
return int64(arg.(float64))
}
func toUint64(arg interface{}) uint64 {
if arg == goos.RefValueZero || arg == goos.Undefined {
return 0
} else if u, ok := arg.(uint64); ok {
return u
}
return uint64(arg.(float64))
}
// valueString returns the string form of JavaScript string, boolean and number types.
func valueString(v interface{}) string { //nolint
if s, ok := v.(string); ok {
return s
} else {
return fmt.Sprintf("%v", v)
}
}