Files
wazero/internal/gojs/goos/goos.go
Crypt Keeper 40341af448 fs: returns EBADF on negative file descriptor (#1391)
This changes file descriptors from uint32 to int32 and the
corresponding file table to reject negative values. This ensures
invalid values aren't mistaken for very large descriptor entries, which
can use a lot of memory as the table implementation isn't designed to
be sparse.

See https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html#tag_16_90

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2023-04-21 16:08:35 +02:00

312 lines
8.6 KiB
Go

// Package goos isolates code from runtime.GOOS=js in a way that avoids cyclic
// dependencies when re-used from other packages.
package goos
import (
"context"
"encoding/binary"
"fmt"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/gojs/goarch"
"github.com/tetratelabs/wazero/internal/gojs/util"
"github.com/tetratelabs/wazero/internal/wasm"
)
// Ref is used to identify a JavaScript value, since the value itself cannot
// be passed to WebAssembly.
//
// The JavaScript value "undefined" is represented by the value 0.
//
// A JavaScript number (64-bit float, except 0 and NaN) is represented by its
// IEEE 754 binary representation.
//
// All other values are represented as an IEEE 754 binary representation of NaN
// with bits 0-31 used as an ID and bits 32-34 used to differentiate between
// string, symbol, function and object.
type Ref uint64
const (
// predefined
IdValueNaN uint32 = iota
IdValueZero
IdValueNull
IdValueTrue
IdValueFalse
IdValueGlobal
IdJsGo
// The below are derived from analyzing `*_js.go` source.
IdObjectConstructor
IdArrayConstructor
IdJsProcess
IdJsfs
IdJsfsConstants
IdUint8ArrayConstructor
IdJsCrypto
IdJsDateConstructor
IdJsDate
IdHttpFetch
IdHttpHeaders
NextID
)
const (
RefValueUndefined = Ref(0)
RefValueNaN = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueNaN)
RefValueZero = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueZero)
RefValueNull = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueNull)
RefValueTrue = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueTrue)
RefValueFalse = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueFalse)
RefValueGlobal = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdValueGlobal)
RefJsGo = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsGo)
RefObjectConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdObjectConstructor)
RefArrayConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdArrayConstructor)
RefJsProcess = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsProcess)
RefJsfs = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsfs)
RefJsfsConstants = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsfsConstants)
RefUint8ArrayConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdUint8ArrayConstructor)
RefJsCrypto = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdJsCrypto)
RefJsDateConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdJsDateConstructor)
RefJsDate = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsDate)
RefHttpFetch = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdHttpFetch)
RefHttpHeadersConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdHttpHeaders)
)
type TypeFlag byte
// the type flags need to be in sync with gojs.js
const (
TypeFlagNone TypeFlag = iota
TypeFlagObject
TypeFlagString
TypeFlagSymbol //nolint
TypeFlagFunction
)
func ValueRef(id uint32, typeFlag TypeFlag) Ref {
return (NanHead|Ref(typeFlag))<<32 | Ref(id)
}
var le = binary.LittleEndian
// NanHead are the upper 32 bits of a Ref which are set if the value is not encoded as an IEEE 754 number (see above).
const NanHead = 0x7FF80000
func (ref Ref) ParseFloat() (v float64, ok bool) {
if (ref>>32)&NanHead != NanHead {
v = api.DecodeF64(uint64(ref))
ok = true
}
return
}
// GetLastEventArgs returns the arguments to the last event created by
// custom.NameSyscallValueCall.
type GetLastEventArgs func(context.Context) []interface{}
type ValLoader func(context.Context, Ref) interface{}
type Stack interface {
goarch.Stack
ParamRef(i int) Ref
ParamRefs(mem api.Memory, i int) []Ref
ParamVal(ctx context.Context, i int, loader ValLoader) interface{}
// ParamVals is used by functions whose final parameter is an arg array.
ParamVals(ctx context.Context, mem api.Memory, i int, loader ValLoader) []interface{}
SetResultRef(i int, v Ref)
}
type stack struct {
s goarch.Stack
}
// Name implements the same method as documented on goarch.Stack
func (s *stack) Name() string {
return s.s.Name()
}
// Param implements the same method as documented on goarch.Stack
func (s *stack) Param(i int) uint64 {
return s.s.Param(i)
}
// ParamBytes implements the same method as documented on goarch.Stack
func (s *stack) ParamBytes(mem api.Memory, i int) []byte {
return s.s.ParamBytes(mem, i)
}
// ParamRef implements Stack.ParamRef
func (s *stack) ParamRef(i int) Ref {
return Ref(s.s.Param(i))
}
// ParamRefs implements Stack.ParamRefs
func (s *stack) ParamRefs(mem api.Memory, i int) []Ref {
offset := s.s.ParamUint32(i)
size := s.s.ParamUint32(i + 1)
byteCount := size << 3 // size * 8
result := make([]Ref, 0, size)
buf := util.MustRead(mem, s.Name(), i, offset, byteCount)
for pos := uint32(0); pos < byteCount; pos += 8 {
ref := Ref(le.Uint64(buf[pos:]))
result = append(result, ref)
}
return result
}
// ParamString implements the same method as documented on goarch.Stack
func (s *stack) ParamString(mem api.Memory, i int) string {
return s.s.ParamString(mem, i)
}
// ParamInt32 implements the same method as documented on goarch.Stack
func (s *stack) ParamInt32(i int) int32 {
return s.s.ParamInt32(i)
}
// ParamUint32 implements the same method as documented on goarch.Stack
func (s *stack) ParamUint32(i int) uint32 {
return s.s.ParamUint32(i)
}
// ParamVal implements Stack.ParamVal
func (s *stack) ParamVal(ctx context.Context, i int, loader ValLoader) interface{} {
ref := s.ParamRef(i)
return loader(ctx, ref)
}
// ParamVals implements Stack.ParamVals
func (s *stack) ParamVals(ctx context.Context, mem api.Memory, i int, loader ValLoader) []interface{} {
offset := s.s.ParamUint32(i)
size := s.s.ParamUint32(i + 1)
byteCount := size << 3 // size * 8
result := make([]interface{}, 0, size)
buf := util.MustRead(mem, s.Name(), i, offset, byteCount)
for pos := uint32(0); pos < byteCount; pos += 8 {
ref := Ref(le.Uint64(buf[pos:]))
result = append(result, loader(ctx, ref))
}
return result
}
// Refresh implements the same method as documented on goarch.Stack
func (s *stack) Refresh(mod api.Module) {
s.s.Refresh(mod)
}
// SetResult implements the same method as documented on goarch.Stack
func (s *stack) SetResult(i int, v uint64) {
s.s.SetResult(i, v)
}
// SetResultBool implements the same method as documented on goarch.Stack
func (s *stack) SetResultBool(i int, v bool) {
s.s.SetResultBool(i, v)
}
// SetResultI32 implements the same method as documented on goarch.Stack
func (s *stack) SetResultI32(i int, v int32) {
s.s.SetResultI32(i, v)
}
// SetResultI64 implements the same method as documented on goarch.Stack
func (s *stack) SetResultI64(i int, v int64) {
s.s.SetResultI64(i, v)
}
// SetResultRef implements Stack.SetResultRef
func (s *stack) SetResultRef(i int, v Ref) {
s.s.SetResult(i, uint64(v))
}
// SetResultUint32 implements the same method as documented on goarch.Stack
func (s *stack) SetResultUint32(i int, v uint32) {
s.s.SetResultUint32(i, v)
}
func NewFunc(name string, goFunc Func) *wasm.HostFunc {
sf := &stackFunc{name: name, f: goFunc}
return util.NewFunc(name, sf.Call)
}
type Func func(context.Context, api.Module, Stack)
type stackFunc struct {
name string
f Func
}
// Call implements the same method as defined on api.GoModuleFunction.
func (f *stackFunc) Call(ctx context.Context, mod api.Module, wasmStack []uint64) {
s := NewStack(f.name, mod.Memory(), uint32(wasmStack[0]))
f.f(ctx, mod, s)
}
func NewStack(name string, mem api.Memory, sp uint32) *stack {
return &stack{goarch.NewStack(name, mem, sp)}
}
var Undefined = struct{ name string }{name: "undefined"}
func ValueToUint32(arg interface{}) uint32 {
if arg == RefValueZero || arg == Undefined {
return 0
} else if u, ok := arg.(uint32); ok {
return u
}
return uint32(arg.(float64))
}
func ValueToInt32(arg interface{}) int32 {
if arg == RefValueZero || arg == Undefined {
return 0
} else if u, ok := arg.(int); ok {
return int32(u)
}
return int32(uint32(arg.(float64)))
}
// GetFunction allows getting a JavaScript property by name.
type GetFunction interface {
Get(propertyKey string) interface{}
}
// ByteArray is a result of uint8ArrayConstructor which temporarily stores
// binary data outside linear memory.
//
// Note: This is a wrapper because a slice is not hashable.
type ByteArray struct {
slice []byte
}
func WrapByteArray(buf []byte) *ByteArray {
return &ByteArray{buf}
}
// Unwrap returns the underlying byte slice
func (a *ByteArray) Unwrap() []byte {
return a.slice
}
// Get implements GetFunction
func (a *ByteArray) Get(propertyKey string) interface{} {
switch propertyKey {
case "byteLength":
return uint32(len(a.slice))
}
panic(fmt.Sprintf("TODO: get byteArray.%s", propertyKey))
}