Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
crebsy committed Nov 16, 2023
1 parent cee5f84 commit 3697b2a
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 3 deletions.
25 changes: 25 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"runtime"
"sync"
"unsafe"

"github.com/pkg/errors"
)

// Due to the limitations of passing pointers to C from Go we need to create
Expand Down Expand Up @@ -100,6 +102,19 @@ func (c *Context) RunScript(source string, origin string) (*Value, error) {
return valueResult(c, rtn)
}

// RunScript executes the source JavaScript; origin (a.k.a. filename) provides a
// reference for the script and used in the stack trace if there is an error.
// error will be of type `JSError` if not nil.
func (c *Context) RunScriptWithTimer(source string, origin string, expireMS int32) (*Value, error) {
cSource := C.CString(source)
cOrigin := C.CString(origin)
defer C.free(unsafe.Pointer(cSource))
defer C.free(unsafe.Pointer(cOrigin))

rtn := C.RunScriptWithTimer(c.ptr, cSource, cOrigin, C.int(expireMS))
return valueResult(c, rtn)
}

// Global returns the global proxy object.
// Global proxy object is a thin wrapper whose prototype points to actual
// context's global object with the properties like Object, etc. This is
Expand All @@ -119,6 +134,16 @@ func (c *Context) PerformMicrotaskCheckpoint() {
C.IsolatePerformMicrotaskCheckpoint(c.iso.ptr)
}

// PerformMicrotaskCheckpoint runs the default MicrotaskQueue until empty.
// This is used to make progress on Promises.
func (c *Context) PerformMicrotaskCheckpointWithTimer(expireMS int32) error {
errorCode := C.IsolatePerformMicrotaskCheckpointWithTimer(c.iso.ptr, C.int(expireMS))
if errorCode != 0 {
return errors.Errorf("Could not setup timer for microtask")
}
return nil
}

// Close will dispose the context and free the memory.
// Access to any values associated with the context after calling Close may panic.
func (c *Context) Close() {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module rogchap.com/v8go

go 1.17

require github.com/pkg/errors v0.9.1
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
58 changes: 55 additions & 3 deletions isolate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@ import (

var v8once sync.Once

type MicrotaskCallback func()

// Isolate is a JavaScript VM instance with its own heap and
// garbage collector. Most applications will create one isolate
// with many V8 contexts for execution.
type Isolate struct {
ptr C.IsolatePtr

cbMutex sync.RWMutex
cbSeq int
cbs map[int]FunctionCallback
cbMutex sync.RWMutex
cbSeq int
cbs map[int]FunctionCallback
mtCbs []MicrotaskCallback
mtScheduled bool

null *Value
undefined *Value
Expand Down Expand Up @@ -62,6 +66,24 @@ func NewIsolate() *Isolate {
return iso
}

// NewIsolateWithConstraints creates a new V8 isolate. Only one thread may access
// a given isolate at a time, but different threads may access
// different isolates simultaneously.
// When an isolate is no longer used its resources should be freed
// by calling iso.Dispose().
// An *Isolate can be used as a v8go.ContextOption to create a new
// Context, rather than creating a new default Isolate.
func NewIsolateWithConstraints(initialHeapSize uint64, maxHeapSize uint64) *Isolate {
initializeIfNecessary()
iso := &Isolate{
ptr: C.NewIsolateWithConstraints(C.uint64_t(initialHeapSize), C.uint64_t(maxHeapSize)),
cbs: make(map[int]FunctionCallback),
}
iso.null = newValueNull(iso)
iso.undefined = newValueUndefined(iso)
return iso
}

// TerminateExecution terminates forcefully the current thread
// of JavaScript execution in the given isolate.
func (i *Isolate) TerminateExecution() {
Expand Down Expand Up @@ -169,6 +191,17 @@ func (i *Isolate) apply(opts *contextOptions) {
opts.iso = i
}

func (i *Isolate) EnqueueMicrotask(ctx *Context, cb MicrotaskCallback) {
i.cbMutex.Lock()
defer i.cbMutex.Unlock()

i.mtCbs = append(i.mtCbs, cb)
if !i.mtScheduled {
C.IsolateEnqueueMicrotask(i.ptr, ctx.ptr)
i.mtScheduled = true
}
}

func (i *Isolate) registerCallback(cb FunctionCallback) int {
i.cbMutex.Lock()
i.cbSeq++
Expand All @@ -183,3 +216,22 @@ func (i *Isolate) getCallback(ref int) FunctionCallback {
defer i.cbMutex.RUnlock()
return i.cbs[ref]
}

//export isolateRunMicrotasks
func isolateRunMicrotasks(ctxRef int) {
ctx := getContext(ctxRef)
if ctx == nil {
return
}
iso := ctx.iso
iso.cbMutex.Lock()

mtCbs := iso.mtCbs
iso.mtScheduled = false
iso.mtCbs = nil
iso.cbMutex.Unlock()

for _, mtCb := range mtCbs {
mtCb()
}
}
147 changes: 147 additions & 0 deletions v8go.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#include <string>
#include <unordered_map>
#include <vector>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/syscall.h>

#include "_cgo_export.h"

Expand Down Expand Up @@ -124,6 +128,62 @@ m_unboundScript* tracked_unbound_script(m_ctx* ctx, m_unboundScript* us) {
return us;
}

//snyk
constexpr int sigNo = 42;
thread_local Isolate* volatile killTimerIso = nullptr;
static void timerHandler( int sig, siginfo_t *si, void *uc ) {
timer_t tidp;
tidp = si->si_value.sival_ptr;

std::cout << "timer expired " << tidp << std::endl;
killTimerIso->TerminateExecution();
std::cout << "terminated iso execution " << tidp << std::endl;
}

static int clearTimer(timer_t timerId) {
int ret = timer_delete(timerId);
if (ret == -1)
return errno;
return 0;
}

static int makeTimer(timer_t *timerID, int tid, int expireMS) {
struct sigevent te;
struct itimerspec its;

te.sigev_notify = SIGEV_SIGNAL | SIGEV_THREAD_ID;
te._sigev_un._tid = tid;
te.sigev_signo = sigNo;
te.sigev_value.sival_ptr = timerID;
int ret = timer_create(CLOCK_MONOTONIC, &te, timerID);
if (ret == -1)
return errno;

its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 0;
its.it_value.tv_sec = expireMS / 1000;
its.it_value.tv_nsec = (expireMS % 1000) * 1000000;
ret = timer_settime(*timerID, 0, &its, NULL);
if (ret == -1)
return errno;

return 0;
}

template<class Callable>
static auto RunWithTimer(v8::Isolate* isoPtr, int expireMS, Callable && cb) {
killTimerIso = isoPtr;
timer_t timerId;
pid_t tid = gettid();
int tRet = makeTimer(&timerId, tid, expireMS);
auto ret = cb(tRet);
clearTimer(timerId);
killTimerIso = nullptr;
return ret;
}



extern "C" {

/********** Isolate **********/
Expand Down Expand Up @@ -167,6 +227,48 @@ IsolatePtr NewIsolate() {
return iso;
}

IsolatePtr NewIsolateWithConstraints(size_t initialHeapSize, size_t maxHeapSize) {
Isolate::CreateParams params;
params.constraints.ConfigureDefaultsFromHeapSize(initialHeapSize, maxHeapSize);
params.array_buffer_allocator = default_allocator;
Isolate* iso = Isolate::New(params);
Locker locker(iso);
Isolate::Scope isolate_scope(iso);
HandleScope handle_scope(iso);

iso->SetCaptureStackTraceForUncaughtExceptions(true);
iso->AddNearHeapLimitCallback([](void* data, size_t current_heap_limit,
size_t initial_heap_limit) -> size_t {
Isolate* iso = reinterpret_cast<Isolate*>(data);
std::cout << "nearly ded" << std::endl;
v8::HeapStatistics hs;
iso->GetHeapStatistics(&hs);

std::cout << hs.total_heap_size() << ", "
<< hs.total_heap_size_executable()<< ", "
<< hs.total_physical_size()<< ", "
<< hs.total_available_size()<< ", "
<< hs.used_heap_size()<< ", "
<< hs.heap_size_limit()<< ", "
<< hs.malloced_memory()<< ", "
<< hs.external_memory()<< ", "
<< hs.peak_malloced_memory()<< ", "
<< hs.number_of_native_contexts()<< ", "
<< hs.number_of_detached_contexts()<< std::endl;
iso->TerminateExecution();

return current_heap_limit + 16*1024*1024;
}, iso);

// Create a Context for internal use
m_ctx* ctx = new m_ctx;
ctx->ptr.Reset(iso, Context::New(iso));
ctx->iso = iso;
iso->SetData(0, ctx);

return iso;
}

static inline m_ctx* isolateInternalContext(Isolate* iso) {
return static_cast<m_ctx*>(iso->GetData(0));
}
Expand All @@ -176,6 +278,29 @@ void IsolatePerformMicrotaskCheckpoint(IsolatePtr iso) {
iso->PerformMicrotaskCheckpoint();
}


void IsolateEnqueueMicrotask(IsolatePtr iso, ContextPtr ctx) {
ISOLATE_SCOPE(iso)
iso->EnqueueMicrotask([](void* data) {
ContextPtr ctx = static_cast<ContextPtr>(data);
int ctx_ref = ctx->ptr.Get(ctx->iso)->GetEmbedderData(1).As<Integer>()->Value();
std::cout << "ctx_ref= " << ctx_ref << "ctx= " << ctx << std::endl;
isolateRunMicrotasks(ctx_ref);

}, ctx);
}

int IsolatePerformMicrotaskCheckpointWithTimer(IsolatePtr iso, int expireMS) {
return RunWithTimer(iso, expireMS, [&](int errorCode) {
if (errorCode != 0)
return errorCode;

std::cout << "in IsolatePerformMicrotaskCheckpointWithTimer " << std::endl;
IsolatePerformMicrotaskCheckpoint(iso);
return 0;
});
}

void IsolateDispose(IsolatePtr iso) {
if (iso == nullptr) {
return;
Expand Down Expand Up @@ -662,6 +787,28 @@ RtnValue RunScript(ContextPtr ctx, const char* source, const char* origin) {
return rtn;
}

int setupSignalHandler() {
struct sigaction sa;

sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = timerHandler;
sigemptyset(&sa.sa_mask);
if (sigaction(sigNo, &sa, NULL) == -1)
return errno;

return 0;
}


RtnValue RunScriptWithTimer(ContextPtr ctx, const char* source, const char* origin, int expireMS) {
return RunWithTimer(ctx->iso, expireMS, [&](int errorCode) {
if (errorCode != 0)
return RtnValue{nullptr, RtnError{strdup("Could not create timer for RunScript"), strdup("v8go"), strdup("")}};

return RunScript(ctx, source, origin);
});
}

/********** UnboundScript & ScriptCompilerCachedData **********/

ScriptCompilerCachedData* UnboundScriptCreateCodeCache(
Expand Down
10 changes: 10 additions & 0 deletions v8go.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import "C"
import (
"strings"
"unsafe"

"github.com/pkg/errors"
)

// Version returns the version of the V8 Engine with the -v8go suffix
Expand All @@ -38,3 +40,11 @@ func initializeIfNecessary() {
C.Init()
})
}

func SetupSignalHandler() error {
res := C.setupSignalHandler()
if res == -1 {
return errors.Errorf("Could not setup signal handler for isolate time limit")
}
return nil
}
8 changes: 8 additions & 0 deletions v8go.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ typedef struct {

extern void Init();
extern IsolatePtr NewIsolate();
extern IsolatePtr NewIsolateWithConstraints(size_t initialHeapSize, size_t maxHeapSize);
extern void IsolatePerformMicrotaskCheckpoint(IsolatePtr ptr);
extern int IsolatePerformMicrotaskCheckpointWithTimer(IsolatePtr iso, int expireMS);
extern void IsolateDispose(IsolatePtr ptr);
extern void IsolateTerminateExecution(IsolatePtr ptr);
extern int IsolateIsExecutionTerminating(IsolatePtr ptr);
Expand Down Expand Up @@ -175,6 +177,11 @@ extern void ContextFree(ContextPtr ptr);
extern RtnValue RunScript(ContextPtr ctx_ptr,
const char* source,
const char* origin);
extern RtnValue RunScriptWithTimer(ContextPtr ctx_ptr,
const char* source,
const char* origin,
int expireMS);
extern int setupSignalHandler();
extern RtnValue JSONParse(ContextPtr ctx_ptr, const char* str);
const char* JSONStringify(ContextPtr ctx_ptr, ValuePtr val_ptr);
extern ValuePtr ContextGlobal(ContextPtr ctx_ptr);
Expand All @@ -196,6 +203,7 @@ extern void ObjectTemplateSetInternalFieldCount(TemplatePtr ptr,
extern int ObjectTemplateInternalFieldCount(TemplatePtr ptr);

extern TemplatePtr NewFunctionTemplate(IsolatePtr iso_ptr, int callback_ref);
extern void IsolateEnqueueMicrotask(IsolatePtr iso, ContextPtr ctx);
extern RtnValue FunctionTemplateGetFunction(TemplatePtr ptr,
ContextPtr ctx_ptr);

Expand Down

0 comments on commit 3697b2a

Please sign in to comment.