Skip to content

Commit

Permalink
Contain main module within it's own scope as well
Browse files Browse the repository at this point in the history
  • Loading branch information
mstoykov committed Jun 30, 2022
1 parent 8c58d4f commit b9bb3d0
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 86 deletions.
52 changes: 38 additions & 14 deletions js/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type BundleInstance struct {
env map[string]string

exports map[string]goja.Callable
pgm programWithSource
}

// NewBundle creates a new bundle from a source file and a filesystem.
Expand All @@ -89,7 +90,7 @@ func NewBundle(
Strict: true,
SourceMapLoader: generateSourceMapLoader(logger, filesystems),
}
pgm, _, err := c.Compile(code, src.URL.String(), true)
pgm, _, err := c.Compile(code, src.URL.String(), false)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -142,7 +143,7 @@ func NewBundleFromArchive(
CompatibilityMode: compatMode,
SourceMapLoader: generateSourceMapLoader(logger, arc.Filesystems),
}
pgm, _, err := c.Compile(string(arc.Data), arc.FilenameURL.String(), true)
pgm, _, err := c.Compile(string(arc.Data), arc.FilenameURL.String(), false)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -209,7 +210,8 @@ func (b *Bundle) makeArchive() *lib.Archive {

// getExports validates and extracts exported objects
func (b *Bundle) getExports(logger logrus.FieldLogger, rt *goja.Runtime, options bool) error {
exportsV := rt.Get("exports")
pgm := b.BaseInitContext.programs[b.Filename.String()]
exportsV := pgm.module.Get("exports")
if goja.IsNull(exportsV) || goja.IsUndefined(exportsV) {
return errors.New("exports must be an object")
}
Expand Down Expand Up @@ -265,26 +267,31 @@ func (b *Bundle) Instantiate(
}

rt := vuImpl.runtime
pgm := init.programs[b.Filename.String()]
bi = &BundleInstance{
Runtime: rt,
Context: &vuImpl.ctx,
exports: make(map[string]goja.Callable),
env: b.RuntimeOptions.Env,
pgm: pgm,
}

// Grab any exported functions that could be executed. These were
// already pre-validated in cmd.validateScenarioConfig(), just get them here.
exports := rt.Get("exports").ToObject(rt)
exports := pgm.module.Get("exports").ToObject(rt)
for k := range b.exports {
fn, _ := goja.AssertFunction(exports.Get(k))
bi.exports[k] = fn
}

jsOptions := rt.Get("options")
jsOptions := exports.Get("options")
var jsOptionsObj *goja.Object
if jsOptions == nil || goja.IsNull(jsOptions) || goja.IsUndefined(jsOptions) {
jsOptionsObj = rt.NewObject()
rt.Set("options", jsOptionsObj)
err := exports.Set("options", jsOptionsObj)
if err != nil {
return nil, fmt.Errorf("Couldn't set options back on exports: %w", err)
}
} else {
jsOptionsObj = jsOptions.ToObject(rt)
}
Expand All @@ -303,12 +310,6 @@ func (b *Bundle) instantiate(logger logrus.FieldLogger, rt *goja.Runtime, init *
rt.SetFieldNameMapper(common.FieldNameMapper{})
rt.SetRandSource(common.NewRandSource())

exports := rt.NewObject()
rt.Set("exports", exports)
module := rt.NewObject()
_ = module.Set("exports", exports)
rt.Set("module", module)

env := make(map[string]string, len(b.RuntimeOptions.Env))
for key, value := range b.RuntimeOptions.Env {
env[key] = value
Expand All @@ -333,10 +334,27 @@ func (b *Bundle) instantiate(logger logrus.FieldLogger, rt *goja.Runtime, init *
init.moduleVUImpl.ctx = context.Background()
unbindInit := b.setInitGlobals(rt, init)
init.moduleVUImpl.eventLoop = eventloop.New(init.moduleVUImpl)
pgm := init.programs[b.Filename.String()] // this just initializes the program
pgm.pgm = b.Program
pgm.src = b.Source
exports := rt.NewObject()
pgm.module = rt.NewObject()
_ = pgm.module.Set("exports", exports)
init.programs[b.Filename.String()] = pgm

err = common.RunWithPanicCatching(logger, rt, func() error {
return init.moduleVUImpl.eventLoop.Start(func() error {
_, errRun := rt.RunProgram(b.Program)
return errRun
f, errRun := rt.RunProgram(b.Program)
if errRun != nil {
return errRun
}
if call, ok := goja.AssertFunction(f); ok {
if _, errRun = call(exports, pgm.module, exports); errRun != nil {
return errRun
}
return nil
}
panic("we shouldn't be able to get here")
})
})

Expand All @@ -347,6 +365,12 @@ func (b *Bundle) instantiate(logger logrus.FieldLogger, rt *goja.Runtime, init *
}
return err
}
exportsV := pgm.module.Get("exports")
if goja.IsNull(exportsV) {
return errors.New("exports must be an object")
}
pgm.exports = exportsV.ToObject(rt)
init.programs[b.Filename.String()] = pgm
unbindInit()
init.moduleVUImpl.ctx = nil
init.moduleVUImpl.initEnv = nil
Expand Down
13 changes: 7 additions & 6 deletions js/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ func TestNewBundle(t *testing.T) {
_, err := getSimpleBundle(t, "/script.js", `throw new Error("aaaa");`)
exception := new(scriptException)
require.ErrorAs(t, err, &exception)
require.EqualError(t, err, "Error: aaaa\n\tat file:///script.js:1:7(2)\n")
require.EqualError(t, err, "Error: aaaa\n\tat file:///script.js:2:7(3)\n\tat native\n")
})
t.Run("InvalidExports", func(t *testing.T) {
t.Parallel()
_, err := getSimpleBundle(t, "/script.js", `exports = null`)
_, err := getSimpleBundle(t, "/script.js", `module.exports = null`)
require.EqualError(t, err, "exports must be an object")
})
t.Run("DefaultUndefined", func(t *testing.T) {
Expand Down Expand Up @@ -169,13 +169,13 @@ func TestNewBundle(t *testing.T) {
// ES2015 modules are not supported
{
"Modules", "base", `export default function() {};`,
"file:///script.js: Line 1:1 Unexpected reserved word",
"file:///script.js: Line 2:1 Unexpected reserved word (and 2 more errors)",
},
// BigInt is not supported
{
"BigInt", "base",
`module.exports.default = function() {}; BigInt(1231412444)`,
"ReferenceError: BigInt is not defined\n\tat file:///script.js:1:47(6)\n",
"ReferenceError: BigInt is not defined\n\tat file:///script.js:2:47(7)\n\tat native\n",
},
}

Expand Down Expand Up @@ -763,6 +763,7 @@ func TestBundleInstantiate(t *testing.T) {

t.Run("SetAndRun", func(t *testing.T) {
t.Parallel()
t.Skip("This makes no sense for a test we are basically testing that we can reset global")
b, err := getSimpleBundle(t, "/script.js", `
export let options = {
vus: 5,
Expand Down Expand Up @@ -798,7 +799,7 @@ func TestBundleInstantiate(t *testing.T) {
bi, err := b.Instantiate(logger, 0, newModuleVUImpl())
require.NoError(t, err)
// Ensure `options` properties are correctly marshalled
jsOptions := bi.Runtime.Get("options").ToObject(bi.Runtime)
jsOptions := bi.pgm.exports.Get("options").ToObject(bi.Runtime)
vus := jsOptions.Get("vus").Export()
require.Equal(t, int64(5), vus)
tdt := jsOptions.Get("teardownTimeout").Export()
Expand All @@ -809,7 +810,7 @@ func TestBundleInstantiate(t *testing.T) {
b.Options.VUs = null.IntFrom(10)
bi2, err := b.Instantiate(logger, 0, newModuleVUImpl())
require.NoError(t, err)
jsOptions = bi2.Runtime.Get("options").ToObject(bi2.Runtime)
jsOptions = bi2.pgm.exports.Get("options").ToObject(bi2.Runtime)
vus = jsOptions.Get("vus").Export()
require.Equal(t, int64(10), vus)
b.Options.VUs = optOrig
Expand Down
10 changes: 2 additions & 8 deletions js/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,7 @@ func (c *Compiler) Compile(src, filename string, main bool) (*goja.Program, stri
// additioanlly it fixes off by one error in commonjs dependencies due to having to wrap them in a function.
func (c *compilationState) sourceMapLoader(path string) ([]byte, error) {
if path == sourceMapURLFromBabel {
if !c.main {
return c.increaseMappingsByOne(c.srcMap)
}
return c.srcMap, nil
return c.increaseMappingsByOne(c.srcMap)
}
c.srcMap, c.srcMapError = c.compiler.Options.SourceMapLoader(path)
if c.srcMapError != nil {
Expand All @@ -214,10 +211,7 @@ func (c *compilationState) sourceMapLoader(path string) ([]byte, error) {
c.srcMap = nil
return nil, c.srcMapError
}
if !c.main {
return c.increaseMappingsByOne(c.srcMap)
}
return c.srcMap, nil
return c.increaseMappingsByOne(c.srcMap)
}

func (c *Compiler) compileImpl(
Expand Down
7 changes: 4 additions & 3 deletions js/initcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ import (
)

type programWithSource struct {
pgm *goja.Program
src string
module *goja.Object
pgm *goja.Program
src string
module *goja.Object
exports *goja.Object
}

const openCantBeUsedOutsideInitContextMsg = `The "open()" function is only available in the init stage ` +
Expand Down
12 changes: 6 additions & 6 deletions js/initcontext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ func TestInitContextRequire(t *testing.T) {
bi, err := b.Instantiate(logger, 0, newModuleVUImpl())
assert.NoError(t, err, "instance error")

exports := bi.Runtime.Get("exports").ToObject(bi.Runtime)
exports := bi.pgm.exports
require.NotNil(t, exports)
_, defaultOk := goja.AssertFunction(exports.Get("default"))
assert.True(t, defaultOk, "default export is not a function")
assert.Equal(t, "abc123", exports.Get("dummy").String())

k6 := bi.Runtime.Get("_k6").ToObject(bi.Runtime)
k6 := exports.Get("_k6").ToObject(bi.Runtime)
require.NotNil(t, k6)
_, groupOk := goja.AssertFunction(k6.Get("group"))
assert.True(t, groupOk, "k6.group is not a function")
Expand All @@ -96,7 +96,7 @@ func TestInitContextRequire(t *testing.T) {
bi, err := b.Instantiate(logger, 0, newModuleVUImpl())
require.NoError(t, err)

exports := bi.Runtime.Get("exports").ToObject(bi.Runtime)
exports := bi.pgm.exports
require.NotNil(t, exports)
_, defaultOk := goja.AssertFunction(exports.Get("default"))
assert.True(t, defaultOk, "default export is not a function")
Expand Down Expand Up @@ -130,7 +130,7 @@ func TestInitContextRequire(t *testing.T) {
require.NoError(t, afero.WriteFile(fs, "/file.js", []byte(`throw new Error("aaaa")`), 0o755))
_, err := getSimpleBundle(t, "/script.js", `import "/file.js"; export default function() {}`, fs)
assert.EqualError(t, err,
"Error: aaaa\n\tat file:///file.js:2:7(3)\n\tat go.k6.io/k6/js.(*InitContext).Require-fm (native)\n\tat file:///script.js:1:0(14)\n")
"Error: aaaa\n\tat file:///file.js:2:7(3)\n\tat go.k6.io/k6/js.(*InitContext).Require-fm (native)\n\tat file:///script.js:1:0(15)\n\tat native\n")
})

imports := map[string]struct {
Expand Down Expand Up @@ -282,7 +282,7 @@ func TestInitContextOpen(t *testing.T) {
t.Parallel()
bi, err := createAndReadFile(t, tc.file, tc.content, tc.length, "")
require.NoError(t, err)
assert.Equal(t, string(tc.content), bi.Runtime.Get("data").Export())
assert.Equal(t, string(tc.content), bi.pgm.exports.Get("data").Export())
})
}

Expand All @@ -291,7 +291,7 @@ func TestInitContextOpen(t *testing.T) {
bi, err := createAndReadFile(t, "/path/to/file.bin", []byte("hi!\x0f\xff\x01"), 6, "b")
require.NoError(t, err)
buf := bi.Runtime.NewArrayBuffer([]byte{104, 105, 33, 15, 255, 1})
assert.Equal(t, buf, bi.Runtime.Get("data").Export())
assert.Equal(t, buf, bi.pgm.exports.Get("data").Export())
})

testdata := map[string]string{
Expand Down
73 changes: 46 additions & 27 deletions js/module_loading_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,10 @@ func TestLoadCycle(t *testing.T) {
// This is mostly the example from https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/
fs := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fs, "/counter.js", []byte(`
let message = require("./main.js").message;
let main = require("./main.js");
exports.count = 5;
export function a() {
return message;
return main.message;
}
`), os.ModePerm))

Expand Down Expand Up @@ -608,27 +608,36 @@ func TestLoadingSourceMapsDoesntErrorOut(t *testing.T) {
}
}

func TestShowcasingHowOptionsAreGlobalReadable(t *testing.T) {
func TestOptionsAreGloballyReadable(t *testing.T) {
t.Parallel()
fs := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fs, "/A.js", []byte(`
export function A() {
export function A() {
// we can technically get a field set from outside of js this way
return options.someField;
}
`), os.ModePerm))
return options.someField;
}`), os.ModePerm))
r1, err := getSimpleRunner(t, "/script.js", `
import { A } from "./A.js";
export let options = {
someField: "here is an option",
}
export default function(data) {
if (A() != "here is an option") {
throw "oops"
}
}
`, fs, lib.RuntimeOptions{CompatibilityMode: null.StringFrom("extended")})
import { A } from "./A.js";
export let options = {
someField: "here is an option",
}
export default function(data) {
var caught = false;
try{
if (A() == "here is an option") {
throw "oops"
}
} catch(e) {
if (e.message != "options is not defined") {
throw e;
}
caught = true;
}
if (!caught) {
throw "expected exception"
}
} `, fs, lib.RuntimeOptions{CompatibilityMode: null.StringFrom("extended")})
require.NoError(t, err)

arc := r1.MakeArchive()
Expand Down Expand Up @@ -660,26 +669,36 @@ func TestShowcasingHowOptionsAreGlobalReadable(t *testing.T) {
}
}

func TestShowcasingHowOptionsAreGlobalWritable(t *testing.T) {
func TestOptionsAreNotGloballyWritable(t *testing.T) {
t.Parallel()
fs := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fs, "/A.js", []byte(`
export function A() {
// this requires that this is defined
options.minIterationDuration = "1h"
}
`), os.ModePerm))
// this requires that this is defined
options.minIterationDuration = "1h"
}`), os.ModePerm))
r1, err := getSimpleRunner(t, "/script.js", `
import {A} from "/A.js"
export let options = {minIterationDuration: "5m"}
export default () =>{}
A()
`, fs, lib.RuntimeOptions{CompatibilityMode: null.StringFrom("extended")})
var caught = false;
try{
A()
} catch(e) {
if (e.message != "options is not defined") {
throw e;
}
caught = true;
}
if (!caught) {
throw "expected exception"
}`, fs, lib.RuntimeOptions{CompatibilityMode: null.StringFrom("extended")})
require.NoError(t, err)

// here it exists
require.EqualValues(t, time.Hour, r1.GetOptions().MinIterationDuration.Duration)
require.EqualValues(t, time.Minute*5, r1.GetOptions().MinIterationDuration.Duration)
arc := r1.MakeArchive()
registry := metrics.NewRegistry()
builtinMetrics := metrics.RegisterBuiltinMetrics(registry)
Expand All @@ -690,5 +709,5 @@ func TestShowcasingHowOptionsAreGlobalWritable(t *testing.T) {
}, arc)
require.NoError(t, err)

require.EqualValues(t, time.Hour, r2.GetOptions().MinIterationDuration.Duration)
require.EqualValues(t, time.Minute*5, r2.GetOptions().MinIterationDuration.Duration)
}
Loading

0 comments on commit b9bb3d0

Please sign in to comment.