Skip to content

akitlance/v8go

Folders and files

NameName
Last commit message
Last commit date
Oct 25, 2021
Oct 28, 2021
Feb 2, 2021
Oct 19, 2021
Aug 29, 2019
Oct 28, 2021
Sep 4, 2019
Feb 8, 2021
Oct 22, 2021
Oct 20, 2021
Oct 15, 2021
Sep 30, 2021
Sep 30, 2021
Oct 25, 2021
Oct 25, 2021
Oct 25, 2021
Oct 25, 2021
Oct 25, 2021
Oct 25, 2021
Sep 24, 2021
Sep 19, 2021
Sep 30, 2021
Sep 30, 2021
Sep 27, 2021
Sep 19, 2021
Sep 30, 2021
Aug 29, 2019
Dec 24, 2020
Sep 3, 2019
Oct 18, 2021
Sep 17, 2021
Sep 19, 2021
Sep 24, 2021
Sep 19, 2021
Oct 18, 2021
Oct 18, 2021
Sep 19, 2021
Oct 18, 2021
Sep 30, 2021
Oct 14, 2021
Sep 27, 2021
Oct 28, 2021
Mar 23, 2021
Oct 25, 2021
Sep 19, 2021
Sep 24, 2021
Sep 30, 2021

Repository files navigation

Execute JavaScript from Go

Github release Go Report Card Go Reference CI V8 Build codecov FOSSA Status #v8go Slack Channel

V8 Gopher based on original artwork from the amazing Renee French

Usage

import v8 "rogchap.com/v8go"

Running a script

ctx := v8.NewContext() // creates a new V8 context with a new Isolate aka VM
ctx.RunScript("const add = (a, b) => a + b", "math.js") // executes a script on the global context
ctx.RunScript("const result = add(3, 4)", "main.js") // any functions previously added to the context can be called
val, _ := ctx.RunScript("result", "value.js") // return a value in JavaScript back to Go
fmt.Printf("addition result: %s", val)

One VM, many contexts

iso := v8.NewIsolate() // creates a new JavaScript VM
ctx1 := v8.NewContext(iso) // new context within the VM
ctx1.RunScript("const multiply = (a, b) => a * b", "math.js")

ctx2 := v8.NewContext(iso) // another context on the same VM
if _, err := ctx2.RunScript("multiply(3, 4)", "main.js"); err != nil {
  // this will error as multiply is not defined in this context
}

JavaScript function with Go callback

iso := v8.NewIsolate() // create a new VM
// a template that represents a JS function
printfn := v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value {
    fmt.Printf("%v", info.Args()) // when the JS function is called this Go callback will execute
    return nil // you can return a value back to the JS caller if required
})
global := v8.NewObjectTemplate(iso) // a template that represents a JS Object
global.Set("print", printfn) // sets the "print" property of the Object to our function
ctx := v8.NewContext(iso, global) // new Context with the global Object set to our object template
ctx.RunScript("print('foo')", "print.js") // will execute the Go callback with a single argunent 'foo'

Update a JavaScript object from Go

ctx := v8.NewContext() // new context with a default VM
obj := ctx.Global() // get the global object from the context
obj.Set("version", "v1.0.0") // set the property "version" on the object
val, _ := ctx.RunScript("version", "version.js") // global object will have the property set within the JS VM
fmt.Printf("version: %s", val)

if obj.Has("version") { // check if a property exists on the object
    obj.Delete("version") // remove the property from the object
}

JavaScript errors

val, err := ctx.RunScript(src, filename)
if err != nil {
  e := err.(*v8.JSError) // JavaScript errors will be returned as the JSError struct
  fmt.Println(e.Message) // the message of the exception thrown
  fmt.Println(e.Location) // the filename, line number and the column where the error occured
  fmt.Println(e.StackTrace) // the full stack trace of the error, if available

  fmt.Printf("javascript error: %v", e) // will format the standard error message
  fmt.Printf("javascript stack trace: %+v", e) // will format the full error stack trace
}

Terminate long running scripts

vals := make(chan *v8.Value, 1)
errs := make(chan error, 1)

go func() {
    val, err := ctx.RunScript(script, "forever.js") // exec a long running script
    if err != nil {
        errs <- err
        return
    }
    vals <- val
}()

select {
case val := <- vals:
    // success
case err := <- errs:
    // javascript error
case <- time.After(200 * time.Milliseconds):
    vm := ctx.Isolate() // get the Isolate from the context
    vm.TerminateExecution() // terminate the execution
    err := <- errs // will get a termination error back from the running script
}

CPU Profiler

func createProfile() {
	iso := v8.NewIsolate()
	ctx := v8.NewContext(iso)
	cpuProfiler := v8.NewCPUProfiler(iso)

	cpuProfiler.StartProfiling("my-profile")

	ctx.RunScript(profileScript, "script.js") # this script is defined in cpuprofiler_test.go
	val, _ := ctx.Global().Get("start")
	fn, _ := val.AsFunction()
	fn.Call(ctx.Global())

	cpuProfile := cpuProfiler.StopProfiling("my-profile")

	printTree("", cpuProfile.GetTopDownRoot()) # helper function to print the profile
}

func printTree(nest string, node *v8.CPUProfileNode) {
	fmt.Printf("%s%s %s:%d:%d\n", nest, node.GetFunctionName(), node.GetScriptResourceName(), node.GetLineNumber(), node.GetColumnNumber())
	count := node.GetChildrenCount()
	if count == 0 {
		return
	}
	nest = fmt.Sprintf("%s  ", nest)
	for i := 0; i < count; i++ {
		printTree(nest, node.GetChild(i))
	}
}

// Output
// (root) :0:0
//   (program) :0:0
//   start script.js:23:15
//     foo script.js:15:13
//       delay script.js:12:15
//         loop script.js:1:14
//       bar script.js:13:13
//         delay script.js:12:15
//           loop script.js:1:14
//       baz script.js:14:13
//         delay script.js:12:15
//           loop script.js:1:14
//   (garbage collector) :0:0

Documentation

Go Reference & more examples: https://pkg.go.dev/rogchap.com/v8go

Support

If you would like to ask questions about this library or want to keep up-to-date with the latest changes and releases, please join the #v8go channel on Gophers Slack. Click here to join the Gophers Slack community!

Windows

In order to build a project using v8go on Windows, Go requires a gcc compiler to be installed.

To set this up:

  1. Install MSYS2 (https://www.msys2.org/)
  2. Add the Mingw-w64 bin to your PATH environment variable (C:\msys64\mingw64\bin by default)
  3. Open MSYS2 MSYS and execute pacman -S mingw-w64-x86_64-toolchain

V8 requires 64-bit on Windows, therefore it will not work on 32-bit systems.

V8 dependency

V8 version: 9.0.257.18 (April 2021)

In order to make v8go usable as a standard Go package, prebuilt static libraries of V8 are included for Linux, macOS and Windows ie. you should not require to build V8 yourself.

Due to security concerns of binary blobs hiding malicious code, the V8 binary is built via CI ONLY.

Project Goals

To provide a high quality, idiomatic, Go binding to the V8 C++ API.

The API should match the original API as closely as possible, but with an API that Gophers (Go enthusiasts) expect. For example: using multiple return values to return both result and error from a function, rather than throwing an exception.

This project also aims to keep up-to-date with the latest (stable) release of V8.

License

FOSSA Status

Development

Recompile V8 with debug info and debug checks

Aside from data races, Go should be memory-safe and v8go should preserve this property by adding the necessary checks to return an error or panic on these unsupported code paths. Release builds of v8go don't include debugging information for the V8 library since it significantly adds to the binary size, slows down compilation and shouldn't be needed by users of v8go. However, if a v8go bug causes a crash (e.g. during new feature development) then it can be helpful to build V8 with debugging information to get a C++ backtrace with line numbers. The following steps will not only do that, but also enable V8 debug checking, which can help with catching misuse of the V8 API.

  1. Make sure to clone the projects submodules (ie. the V8's depot_tools project): git submodule update --init --recursive
  2. Build the V8 binary for your OS: deps/build.py --debug. V8 is a large project, and building the binary can take up to 30 minutes.
  3. Build the executable to debug, using go build for commands or go test -c for tests. You may need to add the -ldflags=-compressdwarf=false option to disable debug information compression so this information can be read by the debugger (e.g. lldb that comes with Xcode v12.5.1, the latest Xcode released at the time of writing)
  4. Run the executable with a debugger (e.g. lldb -- ./v8go.test -test.run TestThatIsCrashing, run to start execution then use bt to print a bracktrace after it breaks on a crash), since backtraces printed by Go or V8 don't currently include line number information.

Upgrading the V8 binaries

We have the upgradev8 workflow. The workflow is triggered every day or manually.

If the current v8_version is different from the latest stable version, the workflow takes care of fetching the latest stable v8 files and copying them into deps/include. The last step of the workflow opens a new PR with the branch name v8_upgrade/<v8-version> with all the changes.

The next steps are:

  1. The build is not yet triggered automatically. To trigger it manually, go to the V8 Build Github Action, Select "Run workflow", and select your pushed branch eg. v8_upgrade/<v8-version>.
  2. Once built, this should open 3 PRs against your branch to add the libv8.a for Linux, macOS and Windows; merge these PRs into your branch. You are now ready to raise the PR against master with the latest version of V8.

Flushing after C/C++ standard library printing for debugging

When using the C/C++ standard library functions for printing (e.g. printf), then the output will be buffered by default. This can cause some confusion, especially because the test binary (created through go test) does not flush the buffer at exit (at the time of writing). When standard output is the terminal, then it will use line buffering and flush when a new line is printed, otherwise (e.g. if the output is redirected to a pipe or file) it will be fully buffered and not even flush at the end of a line. When the test binary is executed through go test . (e.g. instead of separately compiled with go test -c and run with ./v8go.test) Go may redirect standard output internally, resulting in standard output being fully buffered.

A simple way to avoid this problem is to flush the standard output stream after printing with the fflush(stdout); statement. Not relying on the flushing at exit can also help ensure the output is printed before a crash.

Formatting

Go has go fmt, C has clang-format. Any changes to the v8go.h|cc should be formated with clang-format with the "Chromium" Coding style. This can be done easily by running the go generate command.

brew install clang-format to install on macOS.


V8 Gopher image based on original artwork from the amazing Renee French.

About

Execute JavaScript from Go

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Go 69.4%
  • C++ 24.5%
  • C 6.1%