diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a2b7b4de..1899ce048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for setting and getting internal fields for template object instances - Support for CPU profiling - Add V8 build for Apple Silicon +- Add support for throwing an exception directly via the isolate's ThrowException function. - Support for compiling a context-dependent UnboundScript which can be run in any context of the isolate it was compiled in. - Support for creating a code cache from an UnboundScript which can be used to create an UnboundScript in other isolates to run a pre-compiled script in new contexts. diff --git a/isolate.go b/isolate.go index 2966d10b8..661fbec05 100644 --- a/isolate.go +++ b/isolate.go @@ -149,6 +149,19 @@ func (i *Isolate) Dispose() { i.ptr = nil } +// ThrowException schedules an exception to be thrown when returning to +// JavaScript. When an exception has been scheduled it is illegal to invoke +// any JavaScript operation; the caller must return immediately and only after +// the exception has been handled does it become legal to invoke JavaScript operations. +func (i *Isolate) ThrowException(value *Value) *Value { + if i.ptr == nil { + panic("Isolate has been disposed") + } + return &Value{ + ptr: C.IsolateThrowException(i.ptr, value.ptr), + } +} + // Deprecated: use `iso.Dispose()`. func (i *Isolate) Close() { i.Dispose() diff --git a/isolate_test.go b/isolate_test.go index bb25fea76..3e389bdc6 100644 --- a/isolate_test.go +++ b/isolate_test.go @@ -223,6 +223,62 @@ func TestIsolateGarbageCollection(t *testing.T) { time.Sleep(time.Second) } +func TestIsolateThrowException(t *testing.T) { + t.Parallel() + iso := v8.NewIsolate() + + strErr, _ := v8.NewValue(iso, "some type error") + + throwError := func(val *v8.Value) { + v := iso.ThrowException(val) + + if !v.IsNullOrUndefined() { + t.Error("expected result to be null or undefined") + } + } + + // Function that throws a simple string error from within the function. It is meant + // to emulate when an error is returned within Go. + fn := v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value { + throwError(strErr) + + return nil + }) + + // Function that is passed a TypeError from JavaScript. + fn2 := v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value { + typeErr := info.Args()[0] + + throwError(typeErr) + + return nil + }) + + global := v8.NewObjectTemplate(iso) + global.Set("foo", fn) + global.Set("foo2", fn2) + + ctx := v8.NewContext(iso, global) + + _, e := ctx.RunScript("foo()", "foo.js") + + if e.Error() != "some type error" { + t.Errorf("expected \"some type error\" error but got: %v", e) + } + + _, e = ctx.RunScript("foo2(new TypeError('this is a test'))", "foo.js") + + if e.Error() != "TypeError: this is a test" { + t.Errorf("expected \"TypeError: this is a test\" error but got: %v", e) + } + + ctx.Close() + iso.Dispose() + if recoverPanic(func() { iso.ThrowException(strErr) }) == nil { + t.Error("expected panic") + } +} + func BenchmarkIsolateInitialization(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { diff --git a/v8go.cc b/v8go.cc index 269fd14ec..bd47588f2 100644 --- a/v8go.cc +++ b/v8go.cc @@ -258,6 +258,23 @@ RtnUnboundScript IsolateCompileUnboundScript(IsolatePtr iso, const char* s, cons return rtn; } +/********** Exceptions & Errors **********/ + +ValuePtr IsolateThrowException(IsolatePtr iso, ValuePtr value) { + ISOLATE_SCOPE(iso); + m_ctx* ctx = value->ctx; + + Local throw_ret_val = iso->ThrowException(value->ptr.Get(iso)); + + m_value* new_val = new m_value; + new_val->iso = iso; + new_val->ctx = ctx; + new_val->ptr = Persistent>( + iso, throw_ret_val); + + return tracked_value(ctx, new_val); +} + /********** CpuProfiler **********/ CPUProfiler* NewCPUProfiler(IsolatePtr iso_ptr) { diff --git a/v8go.h b/v8go.h index cdb96efc2..787a7328f 100644 --- a/v8go.h +++ b/v8go.h @@ -143,6 +143,8 @@ extern void IsolateTerminateExecution(IsolatePtr ptr); extern int IsolateIsExecutionTerminating(IsolatePtr ptr); extern IsolateHStatistics IsolationGetHeapStatistics(IsolatePtr ptr); +extern ValuePtr IsolateThrowException(IsolatePtr iso, ValuePtr value); + extern RtnUnboundScript IsolateCompileUnboundScript(IsolatePtr iso_ptr, const char* source, const char* origin,